Files
vectorcraft/crates/ui-egui/src/dialogs/layers_panel_options.rs
T
bflatasticandClaude Opus 5.5 517b166722 Fix #485: the mouse wheel steps the focused numeric field
The wheel did nothing over numeric fields. As in Illustrator, once a numeric
field has the focus, turning the wheel over it steps its value like Up/Down:
a notch a step, Shift ten, Ctrl/Cmd a tenth, applied at once with the new value
selected.

- widgets::step_value (was step_with_arrows) reads the arrows and the wheel
  through one path for num_field, plain/mixed fields and the spin fields built
  on them (panels, the Control bar, dialogs, the Font Size and Leading combo
  boxes), and now opt_field (Stroke dash/gap) too. Trackpad and fine-wheel
  points add up to notches; the rest carries to the next turn.
- While the pointer is over the focused field the panel under it doesn't
  scroll; an unfocused field, or the pointer anywhere else, leaves the wheel to
  the panel and the canvas as before.
- The dialogs' and preferences' egui drag values become the same recessed
  number fields (widgets::range_field: clamped to their range, rounded to their
  precision), so they type, step and scroll like every other numeric field.
- Headless tests for the wheel (units, modifiers, points, focus and pointer),
  and for the panel scrolling or not; docs/control-protocol.md and ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:21:39 +07:00

119 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Layers panel › Panel Options…: Show Layers Only, the row size and which rows show thumbnails.
//!
//! Fields: `layersOnly`, `rowSize` (`small`, `medium`, `large` or `other`), `otherSize` (points,
//! 12–100, for `other`), `thumbLayers`, `thumbGroups` and `thumbObjects`. OK applies them to the
//! panel (kept with the UI state).
use serde_json::{Value, json};
use super::DialogSpec;
use super::swatch_options::{grid, label};
use crate::panels::layers::{PanelOptions, ROW_LARGE, ROW_MEDIUM, ROW_SMALL};
use crate::state::Dialog;
use crate::{VectorcraftApp, widgets};
/// The dialog kind of the Layers Panel Options.
pub const KIND: &str = "layersPanelOptions";
pub(super) const SPEC: DialogSpec =
DialogSpec { heading: |_| tl!("Layers Panel Options").into(), body, confirm, min_width: 300.0, ..DialogSpec::FORM };
const SIZES: [(&str, &str, f32); 3] = [("small", "Small", ROW_SMALL), ("medium", "Medium", ROW_MEDIUM), ("large", "Large", ROW_LARGE)];
/// `ui.layersPanelOptions`: open the dialog on the panel's options.
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
let o = &app.ui.layers_panel;
let size = SIZES.iter().find(|s| (s.2 - o.row()).abs() < 0.01).map_or("other", |s| s.0);
let fields = json!({
"layersOnly": o.layers_only, "rowSize": size, "otherSize": o.row(),
"thumbLayers": o.thumb_layers, "thumbGroups": o.thumb_groups, "thumbObjects": o.thumb_objects,
});
app.ui.dialog = Some(Dialog::new(KIND, fields));
Ok(Value::Null)
}
fn check(ui: &mut egui::Ui, d: &mut Dialog, key: &str, text: &str) {
let v = d.bool(key);
if widgets::check(ui, text, v, true) {
d.fields.insert(key.into(), json!(!v));
}
}
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
check(ui, d, "layersOnly", tl!("Show Layers Only"));
ui.add_space(8.0);
grid(ui, |ui| {
label(ui, tl!("Row Size:"));
ui.horizontal(|ui| {
let cur = d.str("rowSize");
let labels: Vec<&str> = SIZES.iter().map(|s| s.1).chain(["Other"]).collect();
let shown = SIZES.iter().find(|s| s.0 == cur).map_or("Other", |s| s.1);
if let Some(i) = widgets::dropdown(ui, "layers-row-size", shown, &labels, 110.0) {
d.fields.insert("rowSize".into(), json!(SIZES.get(i).map_or("other", |s| s.0)));
}
let v = d.f64("otherSize", f64::from(ROW_MEDIUM));
let other = d.str("rowSize") == "other";
if let Some(v) = ui.add_enabled_ui(other, |ui| widgets::range_field(ui, "otherSize", v, 12.0..=100.0, " pt", 0, 60.0)).inner {
d.fields.insert("otherSize".into(), json!(v));
}
});
ui.end_row();
});
ui.add_space(8.0);
label(ui, tl!("Thumbnails:"));
check(ui, d, "thumbLayers", tl!("Layers"));
check(ui, d, "thumbGroups", tl!("Group"));
check(ui, d, "thumbObjects", tl!("Object"));
false
}
/// The options the fields describe.
pub(crate) fn options(d: &Dialog) -> PanelOptions {
let size = d.str("rowSize");
let row_size = SIZES.iter().find(|s| s.0 == size).map_or_else(|| (d.f64("otherSize", f64::from(ROW_MEDIUM)) as f32).clamp(12.0, 100.0), |s| s.2);
PanelOptions {
layers_only: d.bool("layersOnly"),
row_size: if row_size.is_finite() { row_size } else { ROW_MEDIUM },
thumb_layers: d.bool("thumbLayers"),
thumb_groups: d.bool("thumbGroups"),
thumb_objects: d.bool("thumbObjects"),
}
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
app.ui.layers_panel = options(d);
app.ui.dialog = None;
Ok(serde_json::to_value(&app.ui.layers_panel).unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::*;
use vectorcraft_engine::Session;
#[test]
fn panel_options_round_trip_and_apply() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
app.run("ui.layersPanelOptions", json!({})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("rowSize"), "medium");
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut out = ctx.run_ui(Default::default(), |ui| super::super::show(&mut app, ui.ctx()));
out.textures_delta.clear();
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("layersOnly".into(), json!(true));
d.fields.insert("rowSize".into(), json!("other"));
d.fields.insert("otherSize".into(), json!(500));
d.fields.insert("thumbObjects".into(), json!(false));
super::super::confirm(&mut app).unwrap();
let o = &app.ui.layers_panel;
assert!(o.layers_only && !o.thumb_objects && o.thumb_layers);
assert_eq!(o.row(), 100.0, "kept to 100 pt");
app.run("ui.layersPanelOptions", json!({})).unwrap();
app.ui.dialog.as_mut().unwrap().fields.insert("rowSize".into(), json!("large"));
super::super::confirm(&mut app).unwrap();
assert_eq!(app.ui.layers_panel.row(), ROW_LARGE);
}
}