diff --git a/ROADMAP.md b/ROADMAP.md index d755e695..0c269fca 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -105,7 +105,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist - A canvas context menu: right-click selects the object under the pointer and lists what applies to the selection (Undo/Redo, clipboard, group, isolation, join, Remove Anchor Points when anchors are selected, masks, compound paths, guides, Transform, Arrange, Select, Export Selection), or the view commands on empty canvas. - Save / Don't Save / Cancel before closing or quitting with unsaved documents (tabs, Close, Close All, Quit, window close). - Four brightness themes, persistent preferences. - - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (off by default: on, a number typed with no unit into any length field is read in points whatever the field's unit; a typed unit still wins). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). About 33 stored options still do nothing yet (the rest of #394: Auto Size New Area Type, Show Font Names in English, Highlight Alternate Glyphs, Construction and Spacing Guides, performance, devices…). + - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). About 33 stored options still do nothing yet (the rest of #394: Auto Size New Area Type, Show Font Names in English, Highlight Alternate Glyphs, Construction and Spacing Guides, performance, devices…). - Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423). - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once. - Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels. Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off. diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 0eb13421..453b9b1d 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -397,8 +397,8 @@ pub struct Prefs { pub units_stroke: String, pub units_type: String, pub units_asian_type: String, - /// Numbers Without Units Are Points: a number typed with no unit into a length field is read - /// in points instead of the field's unit (off by default, as in Illustrator). + /// Numbers Without Units Are Points: a number typed with no unit into a length field in picas + /// is read in points (on by default; the reference app dims it unless a unit is Picas). pub numbers_without_units_are_points: bool, pub identify_objects_by: String, // Guides & Grid @@ -616,7 +616,7 @@ impl Default for Prefs { units_stroke: s("points"), units_type: s("points"), units_asian_type: s("points"), - numbers_without_units_are_points: false, + numbers_without_units_are_points: true, identify_objects_by: s("objectName"), guide_color: s("#4affff"), guide_style: s("lines"), diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 1f8498c2..7817a1bb 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -725,7 +725,6 @@ impl VectorcraftApp { fn logic_frame(&mut self, ctx: &egui::Context) { i18n::set_current(self.ui_language()); - widgets::set_bare_numbers_are_points(ctx, self.session.prefs.numbers_without_units_are_points); self.adopt_context(ctx); if !self.styled { theme::install_fonts(ctx); diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index 42abf2f1..3c706a5c 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -79,6 +79,7 @@ struct Applied { threads: i32, tool_tips: bool, scrub: bool, + bare_points: bool, } /// Per frame: push UI-side preferences into egui / the renderer when they change. @@ -95,6 +96,7 @@ pub fn apply_runtime(app: &mut VectorcraftApp, ctx: &egui::Context) { threads: p.render_threads, tool_tips: p.show_tool_tips, scrub: p.scrub_numeric_fields, + bare_points: p.numbers_without_units_are_points, }; let id = egui::Id::new("dc-applied-prefs"); let prev: Option = ctx.data(|d| d.get_temp::>(id)).flatten(); @@ -105,6 +107,7 @@ pub fn apply_runtime(app: &mut VectorcraftApp, ctx: &egui::Context) { let delay = if want.tool_tips { egui::style::Interaction::default().tooltip_delay } else { f32::INFINITY }; ctx.global_style_mut(|s| s.interaction.tooltip_delay = delay); crate::scrub::set_enabled(ctx, want.scrub); + crate::widgets::set_bare_numbers_are_points(ctx, want.bare_points); if want.white_canvas { let mut t = Tokens::get(ctx); t.pasteboard = egui::Color32::WHITE; @@ -231,6 +234,11 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) { first = false; let v = d.fields.get(sp.key).cloned().unwrap_or(Value::Null); match sp.kind { + // Units › Numbers Without Units Are Points only tells points from picas: dimmed + // unless a unit is Picas. + PrefKind::Bool if sp.key == "numbersWithoutUnitsArePoints" => { + ui.add_enabled_ui(picas_in_use(d), |ui| bool_row(ui, d, sp.key, sp.label)); + } PrefKind::Bool => bool_row(ui, d, sp.key, sp.label), PrefKind::Num { min, max, unit } => { labeled(ui, sp.label, |ui| { @@ -319,6 +327,11 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) { } } +/// Is one of the dialog's Units (General, Stroke, Type, East Asian Type) Picas? +fn picas_in_use(d: &Dialog) -> bool { + ["unitsGeneral", "unitsStroke", "unitsType", "unitsAsianType"].iter().any(|k| d.str(k) == vectorcraft_doc::Unit::Picas.key()) +} + fn bool_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str) { let mut b = d.bool(key); if ui.checkbox(&mut b, tl!(label)).changed() { @@ -473,6 +486,34 @@ mod tests { assert_eq!(saved["engine_prefs"]["gpuPreference"], json!("highPerformance")); } + /// Units › Numbers Without Units Are Points (#394): the preference reaches the fields when it + /// changes, and its checkbox is dimmed unless a unit is Picas. + #[test] + fn numbers_without_units_are_points_reaches_the_fields_and_dims_without_picas() { + let mut a = app(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let frame = |a: &mut VectorcraftApp| { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { + apply_runtime(a, ui.ctx()); + show(a, ui.ctx()); + }); + out.textures_delta.clear(); + }; + let read = |ctx: &egui::Context| widgets::typed_unit(ctx, vectorcraft_doc::Unit::Picas); + frame(&mut a); + assert_eq!(read(&ctx), vectorcraft_doc::Unit::Points, "on by default"); + a.run("prefs.set", json!({"key": "numbersWithoutUnitsArePoints", "value": false})).unwrap(); + frame(&mut a); + assert_eq!(read(&ctx), vectorcraft_doc::Unit::Picas, "off"); + // The checkbox: dimmed with every unit in points, enabled once a unit is Picas. + open(&mut a, Some("Units")); + let d = a.ui.dialog.as_mut().unwrap(); + assert!(!picas_in_use(d), "dimmed in points"); + d.fields.insert("unitsStroke".into(), json!("picas")); + assert!(picas_in_use(d), "enabled with a unit in picas"); + } + #[test] fn every_category_renders_fields() { for c in PREF_CATEGORIES { diff --git a/crates/ui-egui/src/tests_numfields.rs b/crates/ui-egui/src/tests_numfields.rs index 77223f65..99139df0 100644 --- a/crates/ui-egui/src/tests_numfields.rs +++ b/crates/ui-egui/src/tests_numfields.rs @@ -132,29 +132,29 @@ fn plain_fields_do_math_with_their_suffix() { assert_eq!(f.frame(vec![enter()], &draw), Some(90.0)); } -/// Preferences › Units › Numbers Without Units Are Points (#394): on, a number typed with no unit -/// is read in points whatever the field's unit; a typed unit still wins; off (the default), a bare -/// number is in the field's unit. +/// Preferences › Units › Numbers Without Units Are Points (#394): on (the default), a number typed +/// with no unit into a field in picas is read in points; a typed unit still wins (`2p6`, `1in`); +/// off, it is in picas. A field in any other unit reads it in its unit either way. #[test] -fn bare_numbers_are_points_when_the_preference_says_so() { +fn bare_numbers_in_picas_fields_are_points_when_the_preference_says_so() { let mut f = Field::new(); - let u = Unit::Millimeters; - let pt = Cell::new(u.to_pt(4.0)); - let draw = |ui: &mut egui::Ui| widgets::num_field(ui, "f", Some(pt.get()), u, 80.0).inspect(|&v| pt.set(v)); - let typed = |f: &mut Field, text: &str| { + let typed = |f: &mut Field, u: Unit, text: &str| { + let pt = Cell::new(u.to_pt(4.0)); + let draw = |ui: &mut egui::Ui| widgets::num_field(ui, "f", Some(pt.get()), u, 80.0).inspect(|&v| pt.set(v)); f.frame(vec![], &draw); f.frame(f.click(1), &draw); f.frame(vec![Event::Text(text.into())], &draw); f.frame(vec![enter()], &draw) }; - assert_eq!(typed(&mut f, "12"), Some(u.to_pt(12.0)), "off: a bare number is in the field's unit"); - widgets::set_bare_numbers_are_points(&f.ctx, true); - assert_eq!(typed(&mut f, "12"), Some(12.0), "on: points"); - assert_eq!(typed(&mut f, "12 mm"), Some(u.to_pt(12.0)), "a typed unit wins"); - assert_eq!(typed(&mut f, "1in"), Some(72.0)); - assert_eq!(typed(&mut f, "10+5"), Some(15.0), "arithmetic in points too"); + let (pc, mm) = (Unit::Picas, Unit::Millimeters); + assert_eq!(typed(&mut f, pc, "12"), Some(12.0), "on by default: points in a picas field"); + assert_eq!(typed(&mut f, pc, "2p6"), Some(30.0), "picas and points as typed"); + assert_eq!(typed(&mut f, pc, "1in"), Some(72.0), "a typed unit wins"); + assert_eq!(typed(&mut f, pc, "10+5"), Some(15.0), "arithmetic in points too"); + assert_eq!(typed(&mut f, mm, "12"), Some(mm.to_pt(12.0)), "a millimetre field keeps millimetres"); widgets::set_bare_numbers_are_points(&f.ctx, false); - assert_eq!(typed(&mut f, "12"), Some(u.to_pt(12.0))); + assert_eq!(typed(&mut f, pc, "12"), Some(pc.to_pt(12.0)), "off: picas"); + assert_eq!(typed(&mut f, mm, "12"), Some(mm.to_pt(12.0))); } /// ↑/↓ step a focused numeric field by one of its unit (Shift: ten, Ctrl/Cmd: a tenth) and apply it diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 06095010..a54a2965 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -146,22 +146,24 @@ pub fn dim_name(ui: &mut Ui, text: &str) -> Response { ui.label(egui::RichText::new(text).color(t.text).size(12.5)) } -/// Preferences › Units › Numbers Without Units Are Points (#394), for the frame: the app sets it -/// from the preference before drawing (`VectorcraftApp::logic`), the length fields read it. +/// Preferences › Units › Numbers Without Units Are Points (#394; on until set): the app sets it +/// when the preference changes (`prefs_dialog::apply_runtime`), the length fields read it. pub(crate) fn set_bare_numbers_are_points(ctx: &egui::Context, on: bool) { ctx.data_mut(|d| d.insert_temp(egui::Id::new("bare_numbers_are_points"), on)); } -/// The unit a number typed with no unit into a field in `unit` is read in: `unit`, or points with -/// Numbers Without Units Are Points on. A typed unit (`12 mm`, `1in`) always wins. +/// The unit a number typed with no unit into a field in `unit` is read in: `unit`, or for a field +/// in picas points with Numbers Without Units Are Points on (where `12` could be either). A typed +/// unit (`12 mm`, `2p6`, `1in`) always wins. pub(crate) fn typed_unit(ctx: &egui::Context, unit: Unit) -> Unit { - let bare_points = ctx.data(|d| d.get_temp::(egui::Id::new("bare_numbers_are_points"))).unwrap_or(false); - if bare_points { Unit::Points } else { unit } + let bare_points = ctx.data(|d| d.get_temp::(egui::Id::new("bare_numbers_are_points"))).unwrap_or(true); + if bare_points && unit == Unit::Picas { Unit::Points } else { unit } } /// A recessed numeric field showing `value` (points) in `unit`. Returns the new value (points) /// when the user commits (Enter / focus loss). Supports unit suffixes and arithmetic; a number -/// without a unit is in `unit`, or in points with Numbers Without Units Are Points on. +/// without a unit is in `unit` ([`typed_unit`]: in points in a picas field with Numbers Without +/// Units Are Points on). pub fn num_field(ui: &mut Ui, id: impl std::hash::Hash + std::fmt::Debug, value: Option, unit: Unit, width: f32) -> Option { let t = Tokens::get(ui.ctx()); let id = ui.id().with(id); diff --git a/docs/mcp.md b/docs/mcp.md index 62c34489..7bf02f65 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1326,9 +1326,10 @@ three: General (rulers, positions and sizes, the Info panel, dialog distances, c (`document.inspect` → `units`): `document.setUnits {units}` (Document Setup) sets it for that document, and `prefs.set {key: "unitsGeneral", value}` sets it for the active document too (one undo step) and is the units `file.new` starts in when it gets no `units`. Stroke and Type are the preferences `unitsStroke` and `unitsType`. -`numbersWithoutUnitsArePoints` (off by default) makes the desktop app's length fields read a number typed with no -unit in points whatever the field's unit (`12 mm` still reads as millimetres); command params are in points either -way. +`numbersWithoutUnitsArePoints` (on by default) makes the desktop app's length fields in picas read a number typed +with no unit in points (`12` is 12 pt, `2p6` and `1p` stay picas, `12 mm` millimetres); off, it is in picas. Fields in +other units read it in their unit either way, and the Preferences dialog dims the option unless a unit is Picas. +Command params are in points either way. `prefs.list` marks the length preferences (`keyboardIncrement`, `cornerRadius`, `pasteOffset`, `gridlineEvery`, `typeSizeIncrement`, `baselineShiftIncrement`) with `measure: "general"|"type"`; they are kept in points and also take a string with a unit. Unit names: `Points`, `Picas`, `Inches`, `Millimeters`, `Centimeters`, `Pixels`, `Feet & Inches`,