forked from 1b-agent/opencadstudio-upstream
feat(model-space): theme-aware model space canvas and CAD selection/grip sysvars
Model space appearance now follows the active UI theme instead of a hardcoded dark background. Canvas mode is user-selectable (Match Theme / Classic CAD Dark / Custom RGB), with matching controls for the paper sheet and desk surround backgrounds and grid opacity. Implements industry-standard CAD system variables, all settable via command name, SETVAR, and the Options dialog: - COLORTHEME (0/1 dark/light UI scheme, preserves custom palettes) - COLORSCHEME (interactive named-theme selection) - BACKGROUND (THEME/CLASSIC/DEFAULT/DESK presets, RGB triplets) - SELECTIONAREA, SELECTIONAREAOPACITY (translucent marquee fill) - WINDOWSAREACOLOR/WINDOWAREACOLOR, CROSSINGAREACOLOR (ACI or theme) - SELECTIONEFFECT, SELECTIONEFFECTCOLOR (GPU highlight pass, driven by selection_generation invalidation — no retessellation) - SELECTIONPREVIEW (bit 0 = idle rollover, bit 1 = in-command hover) - GRIPSIZE, GRIPCOLOR, GRIPHOT, GRIPHOVER (grip overlay styling)
This commit is contained in:
1 parent
89b1a22850
commit
688785a6cf
18 files changed
+1949
-316
No files matched your search
+244
-79
@@ -71,6 +71,52 @@ pub struct CrosshairOptions {
|
||||
pub snap_angle_deg: f32,
|
||||
}
|
||||
|
||||
/// Rendering style for the viewport grid.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct GridStyle {
|
||||
pub opacity: u8,
|
||||
pub bg_luminance: f32,
|
||||
}
|
||||
|
||||
impl Default for GridStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
opacity: 18,
|
||||
bg_luminance: 0.15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Visual effect options for selection windows, crossings, entity highlights, and grips.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SelectionVisualOptions {
|
||||
pub area: bool,
|
||||
pub opacity: u8,
|
||||
pub window_color: u8,
|
||||
pub crossing_color: u8,
|
||||
pub highlight_color: u8,
|
||||
pub grip_size: f32,
|
||||
pub grip_color: u8,
|
||||
pub grip_hot: u8,
|
||||
pub grip_hover: u8,
|
||||
}
|
||||
|
||||
impl Default for SelectionVisualOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
area: true,
|
||||
opacity: 12,
|
||||
window_color: 0,
|
||||
crossing_color: 0,
|
||||
highlight_color: 0,
|
||||
grip_size: 5.0,
|
||||
grip_color: 0,
|
||||
grip_hot: 0,
|
||||
grip_hover: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Grip marker data ──────────────────────────────────────────────────────
|
||||
|
||||
/// Describes one grip to be drawn in the viewport overlay.
|
||||
@@ -147,13 +193,13 @@ impl GridGeometry {
|
||||
pub(crate) struct GridKey {
|
||||
pub grids: Vec<GridParams>,
|
||||
pub bounds: iced::Rectangle,
|
||||
pub style: GridStyle,
|
||||
}
|
||||
|
||||
impl GridKey {
|
||||
/// Build a key from the per-pane `GridParams` and the overlay bounds. The
|
||||
/// input slice is copied; callers can drop the original.
|
||||
pub(crate) fn from_grids(grids: &[GridParams], bounds: iced::Rectangle) -> Self {
|
||||
Self { grids: grids.to_vec(), bounds }
|
||||
/// Build a key from the per-pane `GridParams`, overlay bounds, and grid style.
|
||||
pub(crate) fn from_grids(grids: &[GridParams], bounds: iced::Rectangle, style: GridStyle) -> Self {
|
||||
Self { grids: grids.to_vec(), bounds, style }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,8 +327,9 @@ pub struct OstTrackPoint {
|
||||
|
||||
pub fn grid_overlay<'a>(
|
||||
grid: Vec<GridParams>,
|
||||
style: GridStyle,
|
||||
) -> Element<'a, Message> {
|
||||
canvas(GridCanvas { grid })
|
||||
canvas(GridCanvas { grid, style })
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
@@ -290,6 +337,7 @@ pub fn grid_overlay<'a>(
|
||||
|
||||
struct GridCanvas {
|
||||
grid: Vec<GridParams>,
|
||||
style: GridStyle,
|
||||
}
|
||||
|
||||
impl canvas::Program<Message> for GridCanvas {
|
||||
@@ -303,10 +351,10 @@ impl canvas::Program<Message> for GridCanvas {
|
||||
bounds: iced::Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let key = GridKey::from_grids(&self.grid, bounds);
|
||||
let key = GridKey::from_grids(&self.grid, bounds, self.style);
|
||||
|
||||
// Hit check: same params, same bounds ⇒ the cached geometry is still
|
||||
// valid. The key includes bounds, so a `should_reuse` match implies
|
||||
// Hit check: same params, same bounds, same style ⇒ the cached geometry is still
|
||||
// valid. The key includes bounds and style, so a `should_reuse` match implies
|
||||
// both are equal, and the fork's `draw_with_bounds` will return the
|
||||
// cached `Arc` clone (essentially free).
|
||||
let hit = should_reuse(state.key.borrow().as_ref(), &key);
|
||||
@@ -316,11 +364,8 @@ impl canvas::Program<Message> for GridCanvas {
|
||||
// returns the cached geometry without invoking the closure.
|
||||
state.cache.draw_with_bounds(renderer, bounds, |_frame| {})
|
||||
} else {
|
||||
// Params or bounds changed. Clear so the closure runs even when
|
||||
// bounds happen to match the previously-cached frame (e.g. a pan
|
||||
// within the same canvas size would otherwise leave stale
|
||||
// geometry served — the fork's bounds-equality test would return
|
||||
// the cached clone without calling our rebuild closure).
|
||||
// Params, style, or bounds changed. Clear so the closure runs even when
|
||||
// bounds happen to match the previously-cached frame.
|
||||
state.cache.clear();
|
||||
state.cache.draw_with_bounds(renderer, bounds, |frame| {
|
||||
for g in &self.grid {
|
||||
@@ -348,14 +393,14 @@ impl canvas::Program<Message> for GridCanvas {
|
||||
g.origin,
|
||||
g.axes,
|
||||
g.limits,
|
||||
self.style,
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// Update the stored key. Safe: the `borrow()` for `should_reuse` is
|
||||
// dropped at the end of the `if` expression above; no live Ref here.
|
||||
// Update the stored key.
|
||||
*state.key.borrow_mut() = Some(key);
|
||||
|
||||
vec![geometry]
|
||||
@@ -383,6 +428,7 @@ pub fn selection_overlay<'a>(
|
||||
hover_locked: bool,
|
||||
crosshair_bg: [f32; 4],
|
||||
crosshair: CrosshairOptions,
|
||||
selection_visual: SelectionVisualOptions,
|
||||
) -> Element<'a, Message> {
|
||||
canvas(SelectionCanvas {
|
||||
selection,
|
||||
@@ -405,6 +451,7 @@ pub fn selection_overlay<'a>(
|
||||
hover_locked,
|
||||
crosshair_bg,
|
||||
crosshair,
|
||||
selection_visual,
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
@@ -462,11 +509,17 @@ struct SelectionCanvas {
|
||||
/// rather than the UI theme, which may be light over a dark model viewport.
|
||||
crosshair_bg: [f32; 4],
|
||||
crosshair: CrosshairOptions,
|
||||
selection_visual: SelectionVisualOptions,
|
||||
}
|
||||
|
||||
fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker, theme: &Theme) {
|
||||
fn draw_grip_marker(
|
||||
frame: &mut canvas::Frame,
|
||||
grip: &GripMarker,
|
||||
theme: &Theme,
|
||||
visual: &SelectionVisualOptions,
|
||||
) {
|
||||
let sp = grip.pos;
|
||||
let h = crate::scene::pick::grip::GRIP_HALF_PX;
|
||||
let h = visual.grip_size.clamp(1.0, 25.0);
|
||||
let path = match grip.shape {
|
||||
GripShape::Square => canvas::Path::rectangle(
|
||||
Point::new(sp.x - h, sp.y - h),
|
||||
@@ -521,10 +574,28 @@ fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker, theme: &Theme)
|
||||
};
|
||||
|
||||
if grip.is_hot {
|
||||
frame.fill(&path, theme.palette().danger.base.color);
|
||||
let hot_color = if visual.grip_hot > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(visual.grip_hot) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
theme.palette().danger.base.color
|
||||
}
|
||||
} else {
|
||||
theme.palette().danger.base.color
|
||||
};
|
||||
frame.fill(&path, hot_color);
|
||||
} else if grip.is_hovered {
|
||||
let pair = theme.palette().primary.strong;
|
||||
frame.fill(&path, pair.color);
|
||||
let hover_color = if visual.grip_hover > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(visual.grip_hover) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
pair.color
|
||||
}
|
||||
} else {
|
||||
pair.color
|
||||
};
|
||||
frame.fill(&path, hover_color);
|
||||
frame.stroke(
|
||||
&path,
|
||||
canvas::Stroke {
|
||||
@@ -535,7 +606,15 @@ fn draw_grip_marker(frame: &mut canvas::Frame, grip: &GripMarker, theme: &Theme)
|
||||
);
|
||||
} else {
|
||||
let palette = theme.palette();
|
||||
let color = palette.primary.base.color;
|
||||
let color = if visual.grip_color > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(visual.grip_color) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
palette.primary.base.color
|
||||
}
|
||||
} else {
|
||||
palette.primary.base.color
|
||||
};
|
||||
let fill = if grip.shape == GripShape::Dropdown {
|
||||
color
|
||||
} else {
|
||||
@@ -715,20 +794,40 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
b: Point,
|
||||
crossing: bool,
|
||||
theme: &Theme,
|
||||
visual: &SelectionVisualOptions,
|
||||
) {
|
||||
let base = if crossing {
|
||||
theme.palette().success.base.color
|
||||
if visual.crossing_color > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(visual.crossing_color) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
theme.palette().success.base.color
|
||||
}
|
||||
} else {
|
||||
theme.palette().success.base.color
|
||||
}
|
||||
} else {
|
||||
theme.palette().primary.base.color
|
||||
if visual.window_color > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(visual.window_color) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
theme.palette().primary.base.color
|
||||
}
|
||||
} else {
|
||||
theme.palette().primary.base.color
|
||||
}
|
||||
};
|
||||
let fill = base.scale_alpha(0.12);
|
||||
let stroke = base.scale_alpha(0.9);
|
||||
let x0 = a.x.min(b.x);
|
||||
let y0 = a.y.min(b.y);
|
||||
let w = (a.x - b.x).abs();
|
||||
let h = (a.y - b.y).abs();
|
||||
let rect = canvas::Path::rectangle(Point::new(x0, y0), Size::new(w, h));
|
||||
frame.fill(&rect, fill);
|
||||
if visual.area && visual.opacity > 0 {
|
||||
let alpha = (visual.opacity as f32 / 100.0).clamp(0.0, 1.0);
|
||||
let fill = base.scale_alpha(alpha);
|
||||
frame.fill(&rect, fill);
|
||||
}
|
||||
let stroke = base.scale_alpha(0.9);
|
||||
frame.stroke(
|
||||
&rect,
|
||||
canvas::Stroke {
|
||||
@@ -740,33 +839,52 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
}
|
||||
|
||||
if let (Some(a), Some(b)) = (self.selection.borrow().box_anchor, self.selection.borrow().box_current) {
|
||||
draw_marquee(&mut frame, a, b, self.selection.borrow().box_crossing, theme);
|
||||
draw_marquee(&mut frame, a, b, self.selection.borrow().box_crossing, theme, &self.selection_visual);
|
||||
}
|
||||
// Preview marquee for point-picked windows (STRETCH) — same look, no pick.
|
||||
if let Some((a, b, crossing)) = self.selection.borrow().preview_box {
|
||||
draw_marquee(&mut frame, a, b, crossing, theme);
|
||||
draw_marquee(&mut frame, a, b, crossing, theme, &self.selection_visual);
|
||||
}
|
||||
|
||||
if self.selection.borrow().poly_active && self.selection.borrow().poly_points.len() > 1 {
|
||||
let base = if self.selection.borrow().poly_crossing {
|
||||
theme.palette().success.base.color
|
||||
} else {
|
||||
theme.palette().primary.base.color
|
||||
};
|
||||
let fill = base.scale_alpha(0.12);
|
||||
let stroke = base.scale_alpha(0.9);
|
||||
if let Some(cur) = self.selection.borrow().last_move_pos {
|
||||
let start = self.selection.borrow().poly_points[0];
|
||||
let fill_path = canvas::Path::new(|p| {
|
||||
p.move_to(start);
|
||||
for pt in &self.selection.borrow().poly_points[1..] {
|
||||
p.line_to(*pt);
|
||||
if self.selection_visual.crossing_color > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(self.selection_visual.crossing_color) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
theme.palette().success.base.color
|
||||
}
|
||||
p.line_to(cur);
|
||||
p.line_to(start);
|
||||
});
|
||||
frame.fill(&fill_path, fill);
|
||||
} else {
|
||||
theme.palette().success.base.color
|
||||
}
|
||||
} else {
|
||||
if self.selection_visual.window_color > 0 {
|
||||
if let Some((r, g, b)) = acadrust::types::aci_table::aci_to_rgb(self.selection_visual.window_color) {
|
||||
Color::from_rgb8(r, g, b)
|
||||
} else {
|
||||
theme.palette().primary.base.color
|
||||
}
|
||||
} else {
|
||||
theme.palette().primary.base.color
|
||||
}
|
||||
};
|
||||
if self.selection_visual.area && self.selection_visual.opacity > 0 {
|
||||
let alpha = (self.selection_visual.opacity as f32 / 100.0).clamp(0.0, 1.0);
|
||||
let fill = base.scale_alpha(alpha);
|
||||
if let Some(cur) = self.selection.borrow().last_move_pos {
|
||||
let start = self.selection.borrow().poly_points[0];
|
||||
let fill_path = canvas::Path::new(|p| {
|
||||
p.move_to(start);
|
||||
for pt in &self.selection.borrow().poly_points[1..] {
|
||||
p.line_to(*pt);
|
||||
}
|
||||
p.line_to(cur);
|
||||
p.line_to(start);
|
||||
});
|
||||
frame.fill(&fill_path, fill);
|
||||
}
|
||||
}
|
||||
let stroke = base.scale_alpha(0.9);
|
||||
let path = canvas::Path::new(|p| {
|
||||
p.move_to(self.selection.borrow().poly_points[0]);
|
||||
for pt in &self.selection.borrow().poly_points[1..] {
|
||||
@@ -840,7 +958,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
}
|
||||
}
|
||||
for grip in &self.grips {
|
||||
draw_grip_marker(frame, grip, theme);
|
||||
draw_grip_marker(frame, grip, theme, &self.selection_visual);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1242,6 +1360,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
ucs.origin_screen,
|
||||
ucs.hover,
|
||||
ucs.selected,
|
||||
self.crosshair_bg,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1341,6 +1460,7 @@ const MIN_GRID_PX: f32 = 20.0;
|
||||
/// Stop an infinite perspective grid before adjacent lines merge at the horizon.
|
||||
const MIN_HORIZON_GRID_PX: f32 = 5.0;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_grid(
|
||||
frame: &mut canvas::Frame,
|
||||
view_rot: Mat4,
|
||||
@@ -1350,12 +1470,25 @@ fn draw_grid(
|
||||
grid_origin: glam::DVec3,
|
||||
grid_axes: (Vec3, Vec3, Vec3),
|
||||
limits: Option<(glam::DVec2, glam::DVec2)>,
|
||||
style: GridStyle,
|
||||
) {
|
||||
let gc = Color {
|
||||
r: 0.28,
|
||||
g: 0.28,
|
||||
b: 0.28,
|
||||
a: 0.7,
|
||||
let alpha = (style.opacity as f32 / 100.0).clamp(0.02, 1.0);
|
||||
let gc = if style.bg_luminance > 0.5 {
|
||||
// Light background: subtle dark grid lines
|
||||
Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: alpha,
|
||||
}
|
||||
} else {
|
||||
// Dark background: subtle light grid lines
|
||||
Color {
|
||||
r: 0.80,
|
||||
g: 0.80,
|
||||
b: 0.80,
|
||||
a: alpha,
|
||||
}
|
||||
};
|
||||
let st = canvas::Stroke {
|
||||
width: 0.5,
|
||||
@@ -1375,7 +1508,7 @@ fn draw_grid(
|
||||
if geometry.axis_extent > 0.0 {
|
||||
let (gx, gy, gz) = grid_axes;
|
||||
let extent = (geometry.axis_extent + step) * 1.5;
|
||||
draw_axes(frame, view_rot, eye, bounds, extent.max(10.0), grid_origin, (gx, gy, gz));
|
||||
draw_axes(frame, view_rot, eye, bounds, extent.max(10.0), grid_origin, (gx, gy, gz), style.bg_luminance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1922,6 +2055,7 @@ pub(crate) fn grid_segments(
|
||||
|
||||
// ── Coloured UCS axes ──────────────────────────────────────────────────────
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_axes(
|
||||
frame: &mut canvas::Frame,
|
||||
view_rot: Mat4,
|
||||
@@ -1930,6 +2064,7 @@ fn draw_axes(
|
||||
extent: f32,
|
||||
origin: glam::DVec3,
|
||||
axes: (Vec3, Vec3, Vec3),
|
||||
bg_luminance: f32,
|
||||
) {
|
||||
let w2s = |world: glam::DVec3| -> Point {
|
||||
let ndc = view_rot.project_point3((world - eye).as_vec3());
|
||||
@@ -1955,8 +2090,9 @@ fn draw_axes(
|
||||
axis_stroke(r, g, b),
|
||||
);
|
||||
};
|
||||
let y_green = if bg_luminance > 0.5 { 0.60 } else { 0.85 };
|
||||
line(ax, 0.90, 0.20, 0.20); // X — red
|
||||
line(ay, 0.20, 0.85, 0.20); // Y — green
|
||||
line(ay, 0.20, y_green, 0.20); // Y — green
|
||||
line(az, 0.20, 0.40, 0.90); // Z — blue
|
||||
}
|
||||
|
||||
@@ -2090,6 +2226,7 @@ fn draw_ucs_icon(
|
||||
origin_screen: Option<Point>,
|
||||
hover: bool,
|
||||
selected: bool,
|
||||
crosshair_bg: [f32; 4],
|
||||
) {
|
||||
let Some((icon_origin, at_origin, geom)) =
|
||||
ucs_icon_geometry(vp, bounds, axes, origin_screen)
|
||||
@@ -2101,6 +2238,8 @@ fn draw_ucs_icon(
|
||||
// corner), so hover/selection highlight applies there too.
|
||||
let _ = at_origin;
|
||||
let highlight = hover || selected;
|
||||
let is_light_bg = 0.299 * crosshair_bg[0] + 0.587 * crosshair_bg[1] + 0.114 * crosshair_bg[2] > 0.5;
|
||||
let y_green = if is_light_bg { 0.60 } else { 0.85 };
|
||||
|
||||
struct AxisInfo {
|
||||
dx: f32,
|
||||
@@ -2129,7 +2268,7 @@ fn draw_ucs_icon(
|
||||
sc_len: geom[1].sc_len,
|
||||
depth: geom[1].depth,
|
||||
r: 0.22,
|
||||
g: 0.85,
|
||||
g: y_green,
|
||||
b: 0.22,
|
||||
label: "Y",
|
||||
},
|
||||
@@ -2228,15 +2367,23 @@ fn draw_ucs_icon(
|
||||
|
||||
// Origin dot.
|
||||
let circle = canvas::Path::circle(icon_origin, 3.5);
|
||||
frame.fill(
|
||||
&circle,
|
||||
let dot_lum = 0.299 * crosshair_bg[0] + 0.587 * crosshair_bg[1] + 0.114 * crosshair_bg[2];
|
||||
let dot_color = if dot_lum > 0.5 {
|
||||
Color {
|
||||
r: 0.15,
|
||||
g: 0.15,
|
||||
b: 0.15,
|
||||
a: 0.95,
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
r: 0.9,
|
||||
g: 0.9,
|
||||
b: 0.9,
|
||||
a: 0.95,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
frame.fill(&circle, dot_color);
|
||||
|
||||
// Draggable grips when selected: a square at the origin and at the X / Y
|
||||
// tips. Warm grip colour with a light border, like an entity grip.
|
||||
@@ -3043,11 +3190,11 @@ mod bench_grid_geometry_tests {
|
||||
// Pre-seed a `GridCanvasState` with the same key the bench will
|
||||
// build each iteration — guaranteed hit path.
|
||||
let state = GridCanvasState::default();
|
||||
let stored_key = GridKey::from_grids(&grids, canvas_bounds);
|
||||
let stored_key = GridKey::from_grids(&grids, canvas_bounds, GridStyle::default());
|
||||
*state.key.borrow_mut() = Some(stored_key);
|
||||
|
||||
for _ in 0..20 {
|
||||
let key = GridKey::from_grids(black_box(&grids), black_box(canvas_bounds));
|
||||
let key = GridKey::from_grids(black_box(&grids), black_box(canvas_bounds), GridStyle::default());
|
||||
let hit = should_reuse(state.key.borrow().as_ref(), &key);
|
||||
black_box(hit);
|
||||
}
|
||||
@@ -3056,7 +3203,7 @@ mod bench_grid_geometry_tests {
|
||||
let start = Instant::now();
|
||||
let mut hit_count = 0u32;
|
||||
for _ in 0..n {
|
||||
let key = GridKey::from_grids(black_box(&grids), black_box(canvas_bounds));
|
||||
let key = GridKey::from_grids(black_box(&grids), black_box(canvas_bounds), GridStyle::default());
|
||||
if should_reuse(state.key.borrow().as_ref(), &key) {
|
||||
hit_count += 1;
|
||||
}
|
||||
@@ -3098,17 +3245,17 @@ mod grid_key_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Same `Vec<GridParams>` + same bounds ⇒ keys compare equal.
|
||||
/// Same `Vec<GridParams>` + same bounds + same style ⇒ keys compare equal.
|
||||
#[test]
|
||||
fn grid_key_matches_identical_params() {
|
||||
let grids = vec![baseline_params(), baseline_params()];
|
||||
let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 720.0 };
|
||||
let a = GridKey::from_grids(&grids, bounds);
|
||||
let b = GridKey::from_grids(&grids, bounds);
|
||||
let a = GridKey::from_grids(&grids, bounds, GridStyle::default());
|
||||
let b = GridKey::from_grids(&grids, bounds, GridStyle::default());
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
/// One test, one baseline. Every change of any of the 7 inputs must produce
|
||||
/// One test, one baseline. Every change of any of the inputs must produce
|
||||
/// a key that differs from the baseline. This is the entire correctness
|
||||
/// contract for cache hit/miss — if any input is ignored, the cache serves
|
||||
/// a stale grid.
|
||||
@@ -3121,13 +3268,13 @@ mod grid_key_tests {
|
||||
height: 720.0,
|
||||
};
|
||||
let baseline_grids = vec![baseline_params()];
|
||||
let baseline_key = GridKey::from_grids(&baseline_grids, baseline_bounds);
|
||||
let baseline_key = GridKey::from_grids(&baseline_grids, baseline_bounds, GridStyle::default());
|
||||
|
||||
// view_rot: small extra rotation
|
||||
let mut p = baseline_params();
|
||||
p.view_rot = Mat4::from_rotation_x(0.15 + 0.01) * Mat4::from_rotation_y(0.05);
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&[p], baseline_bounds),
|
||||
GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"view_rot change must invalidate"
|
||||
);
|
||||
@@ -3136,7 +3283,7 @@ mod grid_key_tests {
|
||||
let mut p = baseline_params();
|
||||
p.eye = glam::DVec3::new(4.0, 3.5, 9.5);
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&[p], baseline_bounds),
|
||||
GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"eye change must invalidate"
|
||||
);
|
||||
@@ -3145,7 +3292,7 @@ mod grid_key_tests {
|
||||
let mut p = baseline_params();
|
||||
p.step = 40.0;
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&[p], baseline_bounds),
|
||||
GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"step change must invalidate"
|
||||
);
|
||||
@@ -3154,7 +3301,7 @@ mod grid_key_tests {
|
||||
let mut p = baseline_params();
|
||||
p.origin = glam::DVec3::new(100.0, 0.0, 0.0);
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&[p], baseline_bounds),
|
||||
GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"origin change must invalidate"
|
||||
);
|
||||
@@ -3163,7 +3310,7 @@ mod grid_key_tests {
|
||||
let mut p = baseline_params();
|
||||
p.axes = (Vec3::Y, Vec3::X, Vec3::Z);
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&[p], baseline_bounds),
|
||||
GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"axes change must invalidate"
|
||||
);
|
||||
@@ -3172,7 +3319,7 @@ mod grid_key_tests {
|
||||
let mut p = baseline_params();
|
||||
p.limits = Some((glam::DVec2::new(0.0, 0.0), glam::DVec2::new(100.0, 100.0)));
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&[p], baseline_bounds),
|
||||
GridKey::from_grids(&[p], baseline_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"limits change must invalidate"
|
||||
);
|
||||
@@ -3185,10 +3332,28 @@ mod grid_key_tests {
|
||||
height: 720.0,
|
||||
};
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&baseline_grids, other_bounds),
|
||||
GridKey::from_grids(&baseline_grids, other_bounds, GridStyle::default()),
|
||||
baseline_key,
|
||||
"bounds change must invalidate"
|
||||
);
|
||||
|
||||
// style opacity: change opacity
|
||||
let mut style = GridStyle::default();
|
||||
style.opacity = 50;
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&baseline_grids, baseline_bounds, style),
|
||||
baseline_key,
|
||||
"style opacity change must invalidate"
|
||||
);
|
||||
|
||||
// style bg_luminance: change luminance (dark to light)
|
||||
let mut style = GridStyle::default();
|
||||
style.bg_luminance = 0.9;
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&baseline_grids, baseline_bounds, style),
|
||||
baseline_key,
|
||||
"style bg_luminance change must invalidate"
|
||||
);
|
||||
}
|
||||
|
||||
/// In a 2-pane tiled layout, changing the second pane's params (with pane 1
|
||||
@@ -3200,13 +3365,13 @@ mod grid_key_tests {
|
||||
let pane1 = baseline_params();
|
||||
let pane2 = baseline_params();
|
||||
let both = vec![pane1.clone(), pane2.clone()];
|
||||
let baseline = GridKey::from_grids(&both, bounds);
|
||||
let baseline = GridKey::from_grids(&both, bounds, GridStyle::default());
|
||||
|
||||
let mut pane2_changed = pane2;
|
||||
pane2_changed.step = 160.0;
|
||||
let dirty = vec![pane1, pane2_changed];
|
||||
assert_ne!(
|
||||
GridKey::from_grids(&dirty, bounds),
|
||||
GridKey::from_grids(&dirty, bounds, GridStyle::default()),
|
||||
baseline,
|
||||
"second pane change must invalidate"
|
||||
);
|
||||
@@ -3217,7 +3382,7 @@ mod grid_key_tests {
|
||||
fn should_reuse_empty() {
|
||||
let grids = vec![baseline_params()];
|
||||
let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 720.0 };
|
||||
let key = GridKey::from_grids(&grids, bounds);
|
||||
let key = GridKey::from_grids(&grids, bounds, GridStyle::default());
|
||||
assert!(!should_reuse(None, &key));
|
||||
}
|
||||
|
||||
@@ -3226,7 +3391,7 @@ mod grid_key_tests {
|
||||
fn should_reuse_equal() {
|
||||
let grids = vec![baseline_params()];
|
||||
let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 720.0 };
|
||||
let key = GridKey::from_grids(&grids, bounds);
|
||||
let key = GridKey::from_grids(&grids, bounds, GridStyle::default());
|
||||
assert!(should_reuse(Some(&key), &key));
|
||||
}
|
||||
|
||||
@@ -3239,8 +3404,8 @@ mod grid_key_tests {
|
||||
pane2.step = 160.0;
|
||||
let grids_b = vec![pane2];
|
||||
let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 720.0 };
|
||||
let old = GridKey::from_grids(&grids_a, bounds);
|
||||
let new = GridKey::from_grids(&grids_b, bounds);
|
||||
let old = GridKey::from_grids(&grids_a, bounds, GridStyle::default());
|
||||
let new = GridKey::from_grids(&grids_b, bounds, GridStyle::default());
|
||||
assert!(!should_reuse(Some(&old), &new));
|
||||
}
|
||||
}
|
||||
@@ -3281,7 +3446,7 @@ mod grid_canvas_state_tests {
|
||||
axes: grid_axes,
|
||||
limits,
|
||||
};
|
||||
let key = GridKey::from_grids(&[params], bounds);
|
||||
let key = GridKey::from_grids(&[params], bounds, GridStyle::default());
|
||||
|
||||
let state = GridCanvasState::default();
|
||||
*state.key.borrow_mut() = Some(key.clone());
|
||||
@@ -3291,7 +3456,7 @@ mod grid_canvas_state_tests {
|
||||
|
||||
// Different bounds on the same params ⇒ different key, do not reuse.
|
||||
let other_bounds = iced::Rectangle { x: 0.0, y: 0.0, width: 640.0, height: 480.0 };
|
||||
let other_key = GridKey::from_grids(&[params], other_bounds);
|
||||
let other_key = GridKey::from_grids(&[params], other_bounds, GridStyle::default());
|
||||
assert!(!should_reuse(state.key.borrow().as_ref(), &other_key));
|
||||
}
|
||||
}
|
||||
+429
-44
@@ -27,6 +27,7 @@ impl<T> fmt::Display for Labelled<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn view_window<'a>(
|
||||
default_save_format: &'a str,
|
||||
file_assoc_enabled: bool,
|
||||
@@ -40,6 +41,10 @@ pub fn view_window<'a>(
|
||||
crosshair_color: Option<[u8; 3]>,
|
||||
crosshair_color_input: &'a str,
|
||||
lineweight_display_scale: i32,
|
||||
model_space: &'a crate::app::config::ModelSpaceThemeConfig,
|
||||
model_bg_input: &'a str,
|
||||
paper_bg_input: &'a str,
|
||||
desk_bg_input: &'a str,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
) -> Element<'a, Message> {
|
||||
let selected_format = crate::io::SAVE_FORMAT_OPTIONS
|
||||
@@ -204,7 +209,144 @@ pub fn view_window<'a>(
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let display = column![
|
||||
let model_bg_rgb = model_space.custom_bg.unwrap_or(crate::app::config::CLASSIC_CAD_DARK_BG);
|
||||
let model_bg_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
model_bg_rgb[0],
|
||||
model_bg_rgb[1],
|
||||
model_bg_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let paper_bg_rgb = model_space.custom_paper_bg.unwrap_or(crate::app::config::DEFAULT_PAPER_BG);
|
||||
let paper_bg_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
paper_bg_rgb[0],
|
||||
paper_bg_rgb[1],
|
||||
paper_bg_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let desk_bg_rgb = model_space.custom_desk_bg.unwrap_or(crate::app::config::DEFAULT_DESK_BG);
|
||||
let desk_bg_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
desk_bg_rgb[0],
|
||||
desk_bg_rgb[1],
|
||||
desk_bg_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mode_options = crate::app::config::ModelSpaceMode::ALL
|
||||
.into_iter()
|
||||
.map(|value| Labelled {
|
||||
label: crate::t!(value.label()).into_owned(),
|
||||
value,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_mode = mode_options
|
||||
.iter()
|
||||
.find(|choice| choice.value == model_space.mode)
|
||||
.cloned();
|
||||
|
||||
let color_choice_options = [
|
||||
(0u8, "0: Theme Default"),
|
||||
(1, "1: Red"),
|
||||
(2, "2: Yellow"),
|
||||
(3, "3: Green"),
|
||||
(4, "4: Cyan"),
|
||||
(5, "5: Blue"),
|
||||
(6, "6: Magenta"),
|
||||
(7, "7: White/Black"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(value, label)| Labelled {
|
||||
value,
|
||||
label: label.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let selected_window_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.selection_window_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.selection_window_color,
|
||||
label: format!("ACI {}", model_space.selection_window_color),
|
||||
});
|
||||
|
||||
let selected_crossing_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.selection_crossing_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.selection_crossing_color,
|
||||
label: format!("ACI {}", model_space.selection_crossing_color),
|
||||
});
|
||||
|
||||
let selected_highlight_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.selection_highlight_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.selection_highlight_color,
|
||||
label: format!("ACI {}", model_space.selection_highlight_color),
|
||||
});
|
||||
|
||||
let selected_grip_color = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.grip_color)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.grip_color,
|
||||
label: format!("ACI {}", model_space.grip_color),
|
||||
});
|
||||
|
||||
let selected_grip_hot = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.grip_hot)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.grip_hot,
|
||||
label: format!("ACI {}", model_space.grip_hot),
|
||||
});
|
||||
|
||||
let selected_grip_hover = color_choice_options
|
||||
.iter()
|
||||
.find(|c| c.value == model_space.grip_hover)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Labelled {
|
||||
value: model_space.grip_hover,
|
||||
label: format!("ACI {}", model_space.grip_hover),
|
||||
});
|
||||
|
||||
let mut display = column![
|
||||
text(crate::tr!("options", "theme-section")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
@@ -226,71 +368,176 @@ pub fn view_window<'a>(
|
||||
Space::new().height(12),
|
||||
color_controls,
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Crosshair")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Crosshair size")).size(12).width(140),
|
||||
slider(1..=100, cursor_size.clamp(1, 100), Message::CursorSizeChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", cursor_size.clamp(1, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
text(crate::t!("Model Space Appearance")).size(15),
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("Restore Defaults")).size(11))
|
||||
.on_press(Message::RestoreModelSpaceDisplayDefaults)
|
||||
.padding([4, 10])
|
||||
.style(button::secondary),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Cursor type")).size(12).width(140),
|
||||
text(crate::t!("Canvas mode")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
selected_cursor,
|
||||
cursor_options,
|
||||
selected_mode,
|
||||
mode_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::CursorTypeChanged(choice.value))
|
||||
.on_select(|choice| Message::ModelSpaceModeChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
];
|
||||
|
||||
if model_space.mode == crate::app::config::ModelSpaceMode::Custom {
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Model background")).size(12).width(140),
|
||||
model_bg_swatch,
|
||||
text_input("#RRGGBB", model_bg_input)
|
||||
.on_input(Message::ModelSpaceBgChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
}
|
||||
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Crosshair color")).size(12).width(140),
|
||||
crosshair_swatch,
|
||||
text_input(crate::t!("#RRGGBB or blank").as_ref(), crosshair_color_input)
|
||||
.on_input(Message::CrosshairColorChanged)
|
||||
text(crate::t!("Paper background")).size(12).width(140),
|
||||
paper_bg_swatch,
|
||||
text_input("#RRGGBB", paper_bg_input)
|
||||
.on_input(Message::PaperSpaceBgChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!("Leave the color blank to keep automatic viewport contrast."))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Lineweight")).size(15),
|
||||
Space::new().height(10),
|
||||
);
|
||||
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Model display scale")).size(12).width(140),
|
||||
slider(
|
||||
25..=200,
|
||||
lineweight_display_scale.clamp(25, 200),
|
||||
Message::LineweightDisplayScaleChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", lineweight_display_scale.clamp(25, 200)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
text(crate::t!("Desk surround")).size(12).width(140),
|
||||
desk_bg_swatch,
|
||||
text_input("#RRGGBB", desk_bg_input)
|
||||
.on_input(Message::DeskSpaceBgChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!("Changes the on-screen width in Model without affecting plotted output."))
|
||||
);
|
||||
|
||||
display = display
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Grid opacity")).size(12).width(140),
|
||||
slider(5..=100, model_space.grid_opacity.clamp(5, 100), Message::GridOpacityChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", model_space.grid_opacity.clamp(5, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(6))
|
||||
.push(
|
||||
text(match model_space.mode {
|
||||
crate::app::config::ModelSpaceMode::MatchTheme => {
|
||||
crate::t!("Canvas background, grid, and default line colors automatically adapt to the active theme.")
|
||||
}
|
||||
crate::app::config::ModelSpaceMode::ClassicDark => {
|
||||
crate::t!("Model space canvas remains locked to classic CAD dark charcoal (#212830).")
|
||||
}
|
||||
crate::app::config::ModelSpaceMode::Custom => {
|
||||
crate::t!("Custom background colors set above are applied to Model and Paper space.")
|
||||
}
|
||||
})
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
)
|
||||
.push(Space::new().height(24))
|
||||
.push(text(crate::t!("Crosshair")).size(15))
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Crosshair size")).size(12).width(140),
|
||||
slider(1..=100, cursor_size.clamp(1, 100), Message::CursorSizeChanged)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", cursor_size.clamp(1, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Cursor type")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
selected_cursor,
|
||||
cursor_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::CursorTypeChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Crosshair color")).size(12).width(140),
|
||||
crosshair_swatch,
|
||||
text_input(crate::t!("#RRGGBB or blank").as_ref(), crosshair_color_input)
|
||||
.on_input(Message::CrosshairColorChanged)
|
||||
.width(150),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(6))
|
||||
.push(
|
||||
text(crate::t!("Leave the color blank to keep automatic viewport contrast."))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
)
|
||||
.push(Space::new().height(24))
|
||||
.push(text(crate::t!("Lineweight")).size(15))
|
||||
.push(Space::new().height(10))
|
||||
.push(
|
||||
row![
|
||||
text(crate::t!("Model display scale")).size(12).width(140),
|
||||
slider(
|
||||
25..=200,
|
||||
lineweight_display_scale.clamp(25, 200),
|
||||
Message::LineweightDisplayScaleChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", lineweight_display_scale.clamp(25, 200)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.push(Space::new().height(6))
|
||||
.push(
|
||||
text(crate::t!("Changes the on-screen width in Model without affecting plotted output."))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
);
|
||||
|
||||
let display_element = display.spacing(0).width(sizing.width);
|
||||
|
||||
let selection = column![
|
||||
text(crate::t!("Selection")).size(15),
|
||||
@@ -310,13 +557,151 @@ pub fn view_window<'a>(
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(24),
|
||||
row![
|
||||
text(crate::t!("Visual Effect Settings")).size(15),
|
||||
Space::new().width(Fill),
|
||||
button(text(crate::t!("Restore Defaults")).size(11))
|
||||
.on_press(Message::RestoreSelectionVisualDefaults)
|
||||
.padding([4, 10])
|
||||
.style(button::secondary),
|
||||
]
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
iced::widget::checkbox(model_space.selection_area)
|
||||
.on_toggle(Message::SelectionAreaToggled)
|
||||
.size(15),
|
||||
text(crate::t!("Indicate selection area with transparent fill (SELECTIONAREA)"))
|
||||
.size(12),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selection area opacity")).size(12).width(140),
|
||||
slider(
|
||||
0..=100,
|
||||
model_space.selection_opacity.clamp(0, 100),
|
||||
Message::SelectionOpacityChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{}%", model_space.selection_opacity.clamp(0, 100)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(6),
|
||||
text(crate::t!(
|
||||
"Transparency of the window and crossing selection areas (SELECTIONAREAOPACITY)."
|
||||
))
|
||||
.size(11)
|
||||
.width(sizing.width),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Window selection color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_window_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::SelectionWindowColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Crossing selection color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_crossing_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::SelectionCrossingColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selection highlight color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_highlight_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::SelectionHighlightColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(24),
|
||||
text(crate::t!("Grip Settings")).size(15),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Grip size")).size(12).width(140),
|
||||
slider(
|
||||
1..=25,
|
||||
model_space.grip_size.clamp(1, 25),
|
||||
Message::GripSizeChanged,
|
||||
)
|
||||
.step(1)
|
||||
.width(Fill),
|
||||
text(format!("{} px", model_space.grip_size.clamp(1, 25)))
|
||||
.size(11)
|
||||
.width(44),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Unselected grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_grip_color),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::GripColorChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Selected/hot grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_grip_hot),
|
||||
color_choice_options.clone(),
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::GripHotChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
text(crate::t!("Hover grip color")).size(12).width(140),
|
||||
iced::widget::pick_list(
|
||||
Some(selected_grip_hover),
|
||||
color_choice_options,
|
||||
|choice| choice.label.clone(),
|
||||
)
|
||||
.on_select(|choice| Message::GripHoverChanged(choice.value))
|
||||
.width(Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(iced::Center),
|
||||
]
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
let content: Element<'a, Message> = match active_tab {
|
||||
OptionsTab::General => general.into(),
|
||||
OptionsTab::Display => display.into(),
|
||||
OptionsTab::Display => display_element.into(),
|
||||
OptionsTab::Selection => selection.into(),
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user