diff --git a/crates/ui-egui/src/canvas/input.rs b/crates/ui-egui/src/canvas/input.rs index e0bd9f029..c4b8239b2 100644 --- a/crates/ui-egui/src/canvas/input.rs +++ b/crates/ui-egui/src/canvas/input.rs @@ -202,7 +202,9 @@ fn wheel_view( ) -> (egui::PointerState, egui::Modifiers, bool, Option) { let line = ui.ctx().options(|o| o.input_options.line_scroll_speed); let wheel_zooms = app.session.prefs.zoom_with_mouse_wheel; - let animate = animated_zoom(&app.session.prefs); + // A docked field may be drawn later and has not consumed its wheel event yet. Only canvas + // hover may start a zoom glide; existing glides still decay outside the canvas in `wheel`. + let animate = animated_zoom(&app.session.prefs) && resp.hovered(); let turn_id = egui::Id::unique("canvas-wheel-turn"); let turn_before: WheelTurn = ui.data(|d| d.get_temp(turn_id)).unwrap_or_default(); let mut turn = turn_before; diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 84788bb70..f90234860 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -4355,6 +4355,80 @@ fn zoom_with_mouse_wheel_preference() { assert!((zoom - 1.0).abs() < 1e-9 && scroll.x < -1.0 && scroll.y == 0.0, "on: Cmd-wheel scrolls sideways ({zoom}, {scroll:?})"); } +/// A numeric panel drawn after the canvas owns its wheel turn even when the pointer enters the +/// canvas on the next frame (#5949). Only a subsequent turn over the canvas starts a zoom glide. +#[test] +fn wheel_over_a_numeric_panel_field_does_not_start_a_canvas_zoom_glide() { + use std::cell::Cell; + for focused in [false, true] { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + for key in ["zoomWithMouseWheel", "animatedZoom", "gpuPerformance"] { + app.run("prefs.set", json!({"key": key, "value": true})).unwrap(); + } + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let value = Cell::new(10.0); + let field_rect = Cell::new(egui::Rect::NOTHING); + let field_id = Cell::new(egui::Id::NULL); + let mut time = 0.0; + let mut run = |app: &mut VectorcraftApp, events| { + time += 0.016; + let raw = egui::RawInput { + time: Some(time), + screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), + events, + ..Default::default() + }; + let mut out = ctx.run_ui(raw, |ui| { + // Match dock rendering order: canvas first, the numeric panel afterward. + let mut canvas_ui = ui.new_child(egui::UiBuilder::new().max_rect(egui::Rect::from_min_size(Pos2::ZERO, vec2(550.0, 500.0)))); + show(app, &mut canvas_ui); + let mut panel_ui = ui.new_child(egui::UiBuilder::new().max_rect(egui::Rect::from_min_size(pos2(600.0, 20.0), vec2(150.0, 100.0)))); + field_id.set(panel_ui.scope_id().with("number")); + if let Some(v) = crate::widgets::plain_field(&mut panel_ui, "number", value.get(), "", 0, 80.0) { + value.set(v); + } + field_rect.set(panel_ui.min_rect()); + }); + out.textures_delta.clear(); + }; + run(&mut app, vec![]); + if focused { + ctx.memory_mut(|m| m.request_focus(field_id.get())); + } + let over_field = field_rect.get().center(); + run(&mut app, vec![egui::Event::PointerMoved(over_field)]); + let before = *app.view().unwrap(); + let turn = || egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Line, + delta: vec2(0.0, 1.0), + phase: egui::TouchPhase::Move, + modifiers: egui::Modifiers::NONE, + source: egui::MouseWheelSource::Unknown, + }; + run(&mut app, vec![turn()]); + assert_eq!(value.get(), 11.0, "the hovered field steps, focused: {focused}"); + assert_eq!(ctx.memory(|m| m.has_focus(field_id.get())), focused, "hover does not take focus"); + assert_eq!(app.view().unwrap().zoom, before.zoom); + let over_canvas = app.canvas_rect.unwrap().center(); + run(&mut app, vec![egui::Event::PointerMoved(over_canvas)]); + for _ in 0..20 { + run(&mut app, vec![]); + assert_eq!(app.view().unwrap().zoom, before.zoom, "a panel turn cannot leave a canvas glide, focused: {focused}"); + assert_eq!(app.view().unwrap().center, before.center); + } + run(&mut app, vec![turn()]); + let first = app.view().unwrap().zoom; + assert!(first > before.zoom, "a turn over the canvas still zooms"); + for _ in 0..20 { + run(&mut app, vec![]); + } + assert!(app.view().unwrap().zoom > first, "the canvas turn still glides over subsequent frames"); + assert_eq!(value.get(), 11.0, "the canvas turn does not step the field"); + } +} + /// #888: with Zoom with Mouse Wheel, a wheel notch's zoom glides in over a few frames, as a /// scroll does, instead of jumping on the frame it arrives; it ends at the same zoom. #[test] diff --git a/crates/ui-egui/src/tests/numfields.rs b/crates/ui-egui/src/tests/numfields.rs index 4ba4b4df1..f9ad5764e 100644 --- a/crates/ui-egui/src/tests/numfields.rs +++ b/crates/ui-egui/src/tests/numfields.rs @@ -732,6 +732,7 @@ fn the_wheel_scrolls_the_panel_unless_over_a_numeric_field() { } } +/// A wheel turn over an unfocused field steps it without consuming an arrow key in the same frame. #[test] fn hover_wheel_keeps_arrow_keys_for_the_keyboard_focus() { let mut f = Field::new(); @@ -752,6 +753,7 @@ fn hover_wheel_keeps_arrow_keys_for_the_keyboard_focus() { assert!(!f.ctx.memory(|m| m.has_focus(f.id.get()))); } +/// Hover steps stop at both bounds, leave mixed values blank, and never edit disabled fields. #[test] fn hover_wheel_preserves_limits_mixed_values_and_disabled_fields() { for (value, enabled, dy, want) in [ diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index d2bd2b15b..ac98575ab 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,10 +1,10 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (numeric wheel stepping on hover, #5949; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (panel wheel turns cannot seed a later canvas zoom glide, #5949; numeric wheel stepping on hover, #5949; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence -Numeric fields step on wheel hover without taking keyboard focus (#5949). Arrow keys still require focus; modifiers, units, limits, mixed values and existing rounding stay in the shared stepping path. Headless regressions in `tests/numfields.rs` cover hovered unfocused fields, focused fields, trackpad accumulation and panel scroll exclusion. Scores and estimates unchanged. +Numeric fields step on wheel hover without taking keyboard focus (#5949). Arrow keys still require focus; modifiers, units, limits, mixed values and existing rounding stay in the shared stepping path. Headless regressions in `tests/numfields.rs` cover hovered unfocused fields, focused fields, trackpad accumulation and panel scroll exclusion. A canvas-first/panel-second regression also checks that a field turn cannot start a canvas zoom glide when the pointer enters the canvas on the next frame. Scores and estimates unchanged. Raster rendering fits and preflattens curved stroke outlines more tightly only below four times the raster tolerance, retaining thin-band coverage without changing stroke weight or vector export geometry. Current cached character/object outlines, cropped long strokes, gradient clips and effect rasters share the helper; cropped-region reuse and guarded flattening remain. The unchanged `fractional_curved_strokes_retain_coverage_between_notches` regression covers fractional weights, rotation, phase, opacity and f32-rounded endpoints. This is a quality/cost tradeoff, not a speed claim. Scores and estimates unchanged. @@ -725,7 +725,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Performance › Real-time Drawing and Editing off defers a drag's edits to its release, but a ruler guide dragged out of a ruler or moved still shows as it is dragged, where only its position label showed (#4646; `a_guide_dragged_out_of_a_ruler_shows_while_dragged`, `real_time_drawing_off_still_shows_a_dragged_guide`). - With Real-time Drawing and Editing off, a dragged selection whose outlines have more than 100,000 points shows its moved bounds instead, as the selection at rest does (#525, #2460; `real_time_drawing_off_draws_the_bounds_of_a_selection_too_big_to_outline`). - 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…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the hovered field without requiring focus (#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. + - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the hovered field without requiring focus (#485, #5949: a notch a step; the panel under it then stays put, and a disabled field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once. - While Alt (Option on macOS) is held, the Flare Tool Options' Cancel button reads Reset, which puts every option back to the Flare tool's default and leaves the dialog open (`ui.dialogReset`). - A plain step of a number field (Up/Down or a wheel notch) goes to the next whole unit in its direction and a Ctrl/Cmd step goes to the next tenth, so 5.1 mm steps up to 6 mm and down to 5 mm (#2881). A Shift step adds or takes ten and keeps the fraction. - In the number, text and color fields of the panels and the Control bar, Shift+Enter (Shift+Return) applies the value and keeps the field active with its value selected; a text box of several lines takes it as a line break. In a dialog, Shift+Enter applies the value and confirms the dialog, as Enter does. @@ -1727,6 +1727,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| +| 2026-11-08 | minor | Panel numeric wheel turns do not seed canvas zoom glides; corrected the disabled-field scroll description (#5949). Scores and estimates unchanged. | | 2026-11-07 | minor | Numeric fields step on wheel hover without taking focus; keyboard focus and existing rounding rules remain (#5949). Scores and estimates unchanged. | | 2026-11-06 | minor | Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid (#3835); evidence added; scores unchanged. | | 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: the corners of each glyph's box are points a drawn point lands on, also on a curved side (`B`), and the box turns with rotated type, its edges then Angular Guides (#5112; [Snap to Glyph options](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/snap-to-glyph-options.html)); scores and estimates unchanged. |