Background dialog preview: mirrored images, device pixels; faint inactive values

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-13 11:05:01 +03:00
1 parent 0ee95fa4ba
commit 2a808c4526
4 files changed
+61 -9

No files matched your search

+3 -3
View File
@@ -453,7 +453,7 @@ impl OpenCADStudio {
offset: ["0".into(), "0".into()],
scale: ["1".into(), "1".into()],
scale_base: [1.0, 1.0],
image_size: None,
preview: None,
viewport: {
let (width, height) = self.tabs[i].scene.selection.borrow().view.vp_size;
[width as f32, height as f32]
@@ -487,7 +487,7 @@ impl OpenCADStudio {
state.offset = [value.offset.x, value.offset.y].map(crate::ui::window::pdf_dialogs::short);
state.scale = [value.scale.x, value.scale.y].map(crate::ui::window::pdf_dialogs::short);
state.scale_base = [value.scale.x, value.scale.y];
state.image_size = image::image_dimensions(&value.filename).ok().map(|(w, h)| [w, h]);
state.preview = crate::ui::window::pdf_dialogs::bg_preview(&value.filename);
}
_ => {}
}
@@ -513,7 +513,7 @@ impl OpenCADStudio {
M::BgThree(on) => state.three = on,
M::BgRotation(value) => state.rotation = value,
M::BgImage(value) => {
state.image_size = image::image_dimensions(value.trim()).ok().map(|(w, h)| [w, h]);
state.preview = crate::ui::window::pdf_dialogs::bg_preview(&value);
state.image = value;
}
M::BgPosition(value) => state.position = value,
+1
View File
@@ -3,3 +3,4 @@ pub mod dispatch;
pub mod render;
pub mod transform;
pub mod viewport_pane;
pub use render::device_scale;
+9
View File
@@ -385,6 +385,7 @@ impl shader::Primitive for Primitive {
pipeline.gpu_error_epoch = errors_at_entry;
}
let scale = viewport.scale_factor() as f32;
DEVICE_SCALE.store(scale.to_bits(), std::sync::atomic::Ordering::Relaxed);
let instance_ids: Vec<u64> = self.viewports.iter().map(|vp| vp.instance_id).collect();
let slots = pipeline.resolve_slots(device, queue, &instance_ids);
for (i, vp) in self.viewports.iter().enumerate() {
@@ -5440,3 +5441,11 @@ mod layer0_inherit_tests {
assert!(scene.document_render_env_cache.borrow().is_some());
}
}
/// The display's device pixels per logical pixel, as the viewport last drew.
static DEVICE_SCALE: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0x3f80_0000);
/// Device pixels per logical pixel of the drawing viewport.
pub fn device_scale() -> f32 {
f32::from_bits(DEVICE_SCALE.load(std::sync::atomic::Ordering::Relaxed))
}
+48 -6
View File
@@ -2185,6 +2185,25 @@ impl fmt::Display for BgPosition {
}
}
/// A background image as the preview draws it.
pub struct BgPreview {
pub size: [u32; 2],
/// Unmirrored, mirrored in x, in y, in both.
pub handles: [image::Handle; 4],
}
/// Loads a background image for the preview, at most 512 pixels a side.
pub fn bg_preview(path: &str) -> Option<BgPreview> {
let decoded = ::image::open(path.trim()).ok()?;
let size = [decoded.width(), decoded.height()];
let small = if size[0].max(size[1]) > 512 { decoded.thumbnail(512, 512) } else { decoded }.to_rgba8();
let handle = |pixels: ::image::RgbaImage| image::Handle::from_rgba(pixels.width(), pixels.height(), pixels.into_raw());
let flip_x = ::image::imageops::flip_horizontal(&small);
let flip_y = ::image::imageops::flip_vertical(&small);
let flip_xy = ::image::imageops::flip_vertical(&flip_x);
Some(BgPreview { size, handles: [handle(small), handle(flip_x), handle(flip_y), handle(flip_xy)] })
}
/// Image offsets the reference's Adjust Image sliders reach, in pixels.
pub const BG_OFFSET_LIMIT: f64 = 2000.0;
/// Image scales the reference's sliders reach (slider value / 10).
@@ -2222,8 +2241,9 @@ pub struct ViewBackgroundState {
pub offset: [String; 2],
pub scale: [String; 2],
pub scale_base: [f64; 2],
/// The chosen image's size in pixels, for the preview.
pub image_size: Option<[u32; 2]>,
/// The chosen image for the preview: its size in pixels and a small copy
/// as it is and mirrored (x, y, both), for negative scales.
pub preview: Option<BgPreview>,
/// The drawing viewport's size, which the preview stands for.
pub viewport: [f32; 2],
/// The drawing area colour, shown for no background.
@@ -2285,12 +2305,15 @@ pub fn view_background<'a>(
}
BgKind::Image => {
if std::path::Path::new(state.image.trim()).is_file() {
let handle = image::Handle::from_path(state.image.trim());
let canvas = Background::Color(rgb(state.canvas));
if state.position == BgPosition::Stretch {
// Filling the view; keeping the aspect ratio it covers
// the view and the overflow is cut off.
let fit = if state.aspect { iced::ContentFit::Cover } else { iced::ContentFit::Fill };
let handle = state.preview.as_ref().map_or_else(
|| image::Handle::from_path(state.image.trim()),
|preview| preview.handles[0].clone(),
);
container(image(handle).content_fit(fit).width(Fill).height(Fill))
.center(Fill)
.clip(true)
@@ -2305,11 +2328,20 @@ pub fn view_background<'a>(
let v = parse(&state.scale[k], state.scale_base[k]);
if v.abs() < 1e-9 { state.scale_base[k] } else { v }
});
let [iw, ih] = state.image_size.unwrap_or([1, 1]);
let [iw, ih] = state.preview.as_ref().map_or([1, 1], |preview| preview.size);
// A negative scale mirrors the image.
let mirrored = usize::from(scale[0] < 0.0) | usize::from(scale[1] < 0.0) << 1;
let handle = state.preview.as_ref().map_or_else(
|| image::Handle::from_path(state.image.trim()),
|preview| preview.handles[mirrored].clone(),
);
let viewport = state.viewport;
// The image's pixels and offsets are device pixels; the
// viewport is measured in logical ones.
let device = crate::scene::view::device_scale().max(0.1);
let tile = state.position == BgPosition::Tile;
container(iced::widget::responsive(move |size| {
let factor = size.height / viewport[1].max(1.0);
let factor = size.height / viewport[1].max(1.0) / device;
let w = (iw as f32 * scale[0].abs() as f32 * factor).max(1.0);
let h = (ih as f32 * scale[1].abs() as f32 * factor).max(1.0);
let x = size.width / 2.0 + offset[0] as f32 * factor - w / 2.0;
@@ -2502,7 +2534,17 @@ fn adjust_card<'a>(state: &'a ViewBackgroundState) -> Element<'a, Message> {
};
let spin = |field: u8| -> Element<'a, Message> {
let value = if field < 2 { &state.offset[field as usize] } else { &state.scale[field as usize - 2] };
let mut input = text_input("", value).size(11).padding([4, 8]).width(Length::Fixed(64.0)).style(field_style);
let mut input = text_input("", value)
.size(11)
.padding([4, 8])
.width(Length::Fixed(64.0))
.style(move |theme: &Theme, status| {
let mut style = field_style(theme, status);
if !enabled {
style.value = theme.palette().background.strong.color;
}
style
});
if enabled {
input = input.on_input(move |v| {
if field < 2 {