mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Merge pull request #1339 from haranishi/fix/issue-1326-numeric-drag-selection
fix(ui): 数値欄のフォーカス時にドラッグ選択を保つ (#1326)
This commit is contained in:
6 files changed
+115
-7
No files matched your search
@@ -1,5 +1,6 @@
|
||||
//! Numeric fields: math typed before the unit suffix, and the whole text (unit included) selected
|
||||
//! on focus or double-click so typing replaces it.
|
||||
//! on keyboard focus, a released focus click or double-click so typing replaces it; a drag
|
||||
//! keeps the pressed character as its selection anchor.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
@@ -49,6 +50,12 @@ impl Field {
|
||||
std::iter::once(Event::PointerMoved(at)).chain((0..count).flat_map(|_| [button(true), button(false)])).collect()
|
||||
}
|
||||
|
||||
fn text_point(&self, text: &str, index: usize, margin: f32) -> Pos2 {
|
||||
let galley = self.ctx.fonts_mut(|f| f.layout_no_wrap(text.to_owned(), egui::FontId::proportional(12.5), egui::Color32::BLACK));
|
||||
let x = galley.pos_from_cursor(egui::text::CCursor::new(index)).left();
|
||||
Pos2::new(self.rect.get().left() + margin + x + 0.1, self.rect.get().center().y)
|
||||
}
|
||||
|
||||
/// The selected character range of the field's text.
|
||||
fn selection(&self) -> Option<(usize, usize)> {
|
||||
let st = egui::TextEdit::load_state(&self.ctx, self.id.get())?;
|
||||
@@ -292,3 +299,76 @@ fn the_wheel_scrolls_the_panel_unless_over_the_focused_field() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn numeric_fixture(ui: &mut egui::Ui, mode: usize) -> Option<f64> {
|
||||
match mode {
|
||||
0 => widgets::num_field(ui, "f", Some(123456.0), Unit::Pixels, 100.0),
|
||||
1 => widgets::num_field(ui, "f", Some(123456.0), Unit::Points, 100.0),
|
||||
2 => widgets::plain_field(ui, "f", 123456.0, "°", 0, 100.0),
|
||||
_ => widgets::opt_field(ui, "f", Some(123456.0), Unit::Pixels, 100.0).flatten(),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_text(mode: usize) -> &'static str {
|
||||
["123456 px", "123456 pt", "123456°", "123456"][mode]
|
||||
}
|
||||
|
||||
fn pointer_button(at: Pos2, pressed: bool) -> Event {
|
||||
Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numeric_drag_keeps_its_press_position_with_or_without_prior_focus() {
|
||||
for mode in 0..4 {
|
||||
for focused in [false, true] {
|
||||
for (start, end) in [(1, 4), (4, 1)] {
|
||||
let mut f = Field::new();
|
||||
let draw = |ui: &mut egui::Ui| numeric_fixture(ui, mode);
|
||||
f.frame(vec![], &draw);
|
||||
if focused {
|
||||
f.frame(f.click(1), &draw);
|
||||
f.frame(vec![], &draw);
|
||||
f.time += 1.0;
|
||||
}
|
||||
let margin = if mode == 3 { 5.0 } else { 7.0 };
|
||||
let at = f.text_point(field_text(mode), start, margin);
|
||||
let to = f.text_point(field_text(mode), end, margin);
|
||||
f.frame(vec![Event::PointerMoved(at), pointer_button(at, true)], &draw);
|
||||
assert_eq!(f.selection(), Some((start, start)), "mode {mode}, prior focus {focused}");
|
||||
f.frame(vec![], &draw); // Holding still must leave the press position alone.
|
||||
f.frame(vec![Event::PointerMoved(to)], &draw);
|
||||
f.frame(vec![pointer_button(to, false)], &draw);
|
||||
assert_eq!(f.selection(), Some((1, 4)), "mode {mode}, {start}->{end}, prior focus {focused}");
|
||||
f.frame(vec![Event::Text("7".into())], &draw);
|
||||
assert_eq!(f.frame(vec![enter()], &draw), Some(1756.0), "replace only the dragged text");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_numeric_click_selects_all_on_release_without_moving_its_press_caret() {
|
||||
let mut f = Field::new();
|
||||
f.num(vec![]);
|
||||
let at = f.rect.get().center();
|
||||
f.num(vec![Event::PointerMoved(at), pointer_button(at, true)]);
|
||||
assert!(f.selection().is_some_and(|(a, b)| a == b), "a hold can still start a text drag");
|
||||
f.num(vec![]);
|
||||
assert!(f.selection().is_some_and(|(a, b)| a == b));
|
||||
f.num(vec![pointer_button(at, false)]);
|
||||
assert_eq!(f.selection(), Some((0, "100 px".len())));
|
||||
f.num(vec![Event::Text("7".into())]);
|
||||
assert_eq!(f.num(vec![enter()]), Some(7.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyboard_focus_keeps_selecting_the_entire_numeric_field() {
|
||||
for mode in 0..4 {
|
||||
let mut f = Field::new();
|
||||
let draw = |ui: &mut egui::Ui| numeric_fixture(ui, mode);
|
||||
f.frame(vec![], &draw);
|
||||
f.frame(vec![key(Key::Tab, Modifiers::NONE)], &draw);
|
||||
f.frame(vec![], &draw);
|
||||
assert_eq!(f.selection(), Some((0, field_text(mode).chars().count())), "mode {mode}");
|
||||
}
|
||||
}
|
||||
@@ -309,13 +309,33 @@ fn select_all(ui: &Ui, id: egui::Id, text: &str) {
|
||||
st.store(ui.ctx(), id);
|
||||
}
|
||||
|
||||
/// Numeric fields take the whole text, unit included, when they gain focus or are double-clicked
|
||||
/// (egui would select one word): typing a number or an expression replaces it.
|
||||
/// Numeric fields select the whole text on keyboard focus, a completed focus click or a
|
||||
/// double-click. A pointer press keeps its caret so dragging selects from the pressed position.
|
||||
fn select_all_on_focus(ui: &Ui, resp: &Response, text: &str) {
|
||||
if !(resp.gained_focus() || resp.double_clicked()) {
|
||||
let pending = resp.id.with("select-all-on-focus-click");
|
||||
let (pressed, released) =
|
||||
ui.input(|i| (i.pointer.button_pressed(egui::PointerButton::Primary), i.pointer.button_released(egui::PointerButton::Primary)));
|
||||
if resp.gained_focus() {
|
||||
ui.data_mut(|d| {
|
||||
if pressed {
|
||||
d.insert_temp(pending, true);
|
||||
} else {
|
||||
d.remove_temp::<bool>(pending);
|
||||
}
|
||||
});
|
||||
}
|
||||
let clicked_focus = released && ui.data_mut(|d| d.remove_temp::<bool>(pending)).unwrap_or(false) && resp.clicked_by(egui::PointerButton::Primary);
|
||||
if resp.lost_focus() {
|
||||
ui.data_mut(|d| d.remove_temp::<bool>(pending));
|
||||
}
|
||||
if !(resp.gained_focus() && !pressed || resp.double_clicked() || clicked_focus) {
|
||||
return;
|
||||
}
|
||||
if egui::TextEdit::load_state(ui.ctx(), resp.id).is_some() {
|
||||
if let Some(st) = egui::TextEdit::load_state(ui.ctx(), resp.id) {
|
||||
// Preserve a text selection if the focus click already produced one.
|
||||
if clicked_focus && !resp.double_clicked() && st.cursor.char_range().is_some_and(|r| !r.as_sorted_char_range().is_empty()) {
|
||||
return;
|
||||
}
|
||||
select_all(ui, resp.id, text);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
|
||||
## G11 Correctness bugs in core tools
|
||||
|
||||
- **Done:** Numeric fields keep a text drag's start when the pointer gains focus and movement exceeds the click threshold (#1326, in part). A completed focus click selects the whole value; keyboard focus, double-click and numeric stepping remain regression-tested. Short movements still classified as clicks can select all; that case remains open.
|
||||
|
||||
- **Done:** DXF reads control-point SPLINE closure (#1299 follow-up after #1320); open curves keep their open state. Escape leaves Artboard editing for Selection; during a drag it first cancels the gesture. Headless egui key events cover idle Escape, active-drag cancellation and a subsequent Escape in `tests_distortkeys.rs`.
|
||||
|
||||
- **Done:** Simplify and Add Anchor Points clear stale component selections on rewritten paths; Simplify previews and reports original/new point counts (#1272).
|
||||
@@ -243,6 +245,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
| 2026-10-12 | minor | G1: Eyedropper reads visible locked and outside-isolation sources (#1251); estimate unchanged |
|
||||
| 2026-10-12 | minor | G11: Direct Selection segment Delete retains neighboring geometry and appearance (#1278); estimates unchanged |
|
||||
| 2026-10-12 | minor | G1: painted strokes regain the existing default after None/zero-width inheritance (#1253); estimate unchanged |
|
||||
| 2026-10-12 | minor | G11: numeric fields keep the text-drag start instead of replacing it with select-all on press (#1326, in part); keyboard and click selection regression-tested; estimates unchanged; short movements below the click threshold remain open |
|
||||
| 2026-10-11 | minor | G15: large-selection edits, cached live effects and faster Pathfinder (#1173); a 25k-path perf run meets every budget; estimate unchanged |
|
||||
| 2026-10-11 | minor | G4: Middle Eastern digit types done (Character panel menu › Digits); estimate unchanged |
|
||||
| 2026-10-11 | minor | G4: proportional vertical metrics (#966) done; estimate unchanged |
|
||||
|
||||
+2
-1
@@ -62,7 +62,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V
|
||||
| M11 | Artboards & views (artboard escape routing and document setup modal handoff done; artboard panel/tool with Move Artwork for X/Y fields and Artboard Options, Trim View, middle-button pan, print tiling and the status bar's artboard navigator done; multiple windows, presentation polish) | 🟡 | 15–25 |
|
||||
| M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (13 presets, 18 ms/1k² image; the Flat Logo mode places edges to a fraction of a pixel, restores sharp corners, snaps lines and circles, and refuses gradients), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, group-selected series colours persist through regenerate/save/open) done; Graph Design and Marker designs, Transpose and Switch x/y, `|` label line breaks, the pie options, drop shadows, the legend across the top, overlapping widths, First Row/Column in Front, filled lines, kept label formatting and the Graph Data worksheet done; graph Column designs pending | 6–10 |
|
||||
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 | 18–27 |
|
||||
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) | 🟡 RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed), workspaces, a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
|
||||
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) | 🟡 Numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed), workspaces, a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
|
||||
| — | Interaction fidelity pass (every tool's modifiers, Properties panel per context, isolation, nuance) | ⬜ | 60–90 |
|
||||
| — | Hardening at scale (big-file corpus, fuzzing, cross-platform + browser QA) | 🟡 | 50–80 |
|
||||
| | **Total to full parity** (one agent; from [target-app-parity.md](target-app-parity.md)) | | **~360–590** |
|
||||
@@ -103,6 +103,7 @@ After placed documents (File › Place of a `.vectorcraft` file, linked), M4.14
|
||||
| 2026-10-11 | minor | M12: Object › Graph › Design… and Marker… (graph designs, marker designs); column designs remain. Scores/hours unchanged. |
|
||||
| 2026-10-12 | minor | M14: native macOS save Format menu (#866); filename regression tests, actual SVG save and PDF selection/cancel verified; estimates unchanged |
|
||||
| 2026-10-12 | minor | M6: compound paths inherit the backmost appearance; selected members read and change the compound fill rule (#1197); estimates unchanged |
|
||||
| 2026-10-12 | minor | M14: numeric-field focus and text-drag selection regression-tested (#1326, in part); estimates unchanged; short movements below the click threshold remain open |
|
||||
| 2026-10-11 | minor | M9: PDF raster-effect resolution-cap warnings verified with real capped/uncapped exports; scores and hours unchanged |
|
||||
| 2026-10-11 | minor | #893 (Shape Builder seams on grids of lines) fixed; G11 keeps #937 and its 8–15 h |
|
||||
| 2026-10-10 | minor | M7: document-point character sizes, leading and baseline shift, and resettable horizontal stretching after object/group scaling (#1034). Optional group font-size preservation remains separate; scores/hours unchanged. |
|
||||
|
||||
@@ -87,7 +87,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de
|
||||
| Export for Screens, Asset Export, slices, Save for Web | 3 | 85% | anti-aliasing ghost borders (#983), TIFF in Export for Screens (30.2) | 2–4 | estimated |
|
||||
| Print, colour management, separations, flattener | 3 | 80% | print preview fidelity | 2–4 | estimated |
|
||||
| Automation (Actions, Variables, scripting, batch) | 3 | 60% | a scripting surface (Illustrator: JavaScript/ExtendScript), Batch, image and graph variables, dataset import/export | 8–14 | estimated |
|
||||
| UI chrome (panels, Properties, workspaces, preferences) | 6 | 80% | Properties per context, ~16 Preferences options stored but unused (#394), panels outside the window, SVG Interactivity, context menus beyond the canvas, accessibility | 13–20 | estimated; macOS save Format menu implemented (#866), score and hours unchanged; Eyedropper cursor and precise setting checked (#1269), OS visual QA unverified |
|
||||
| UI chrome (panels, Properties, workspaces, preferences) | 6 | 80% | Properties per context, ~16 Preferences options stored but unused (#394), panels outside the window, SVG Interactivity, context menus beyond the canvas, accessibility | 13–20 | estimated; macOS save Format menu implemented (#866), score and hours unchanged; Eyedropper cursor and precise setting checked (#1269), OS visual QA unverified; numeric-field focus and normal text drags regression-tested (#1326, in part); movements below the click threshold still select all |
|
||||
| Libraries, Links, Package | 2 | 82% | shared libraries, linked graphics, gradients and patterns as library items (no cloud by design) | 2–3 | estimated |
|
||||
| Generative AI (Text to Vector, Generative Recolor, Shape Fill, Concept to Vector, Prompt to Edit, Rewrite Text) | 2 | 0% | a pluggable provider and the panels; needs an owner decision on models | 30–60 | measured (absent) |
|
||||
| **Feature depth** | **105** | **~75%** | | **~250–420** | |
|
||||
@@ -387,6 +387,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **UI:** Illustrator 2026 layout restyled to measured values:
|
||||
- Medium Dark theme, categorized and Advanced toolbars (a tool group's flyout opens on a right press, a long press or a press on its corner triangle), 35 pt document tabs, 33 pt panel tabs.
|
||||
- Hint bar, contextual task bar, 19 dock panels with ≡ menus.
|
||||
- Numeric fields keep the pointer's pressed character as the text-drag start when movement exceeds the click threshold (#1326, in part); a completed focus click selects the unit too. Pixel/point, plain and optional fields are regression-tested in both directions, before/after focus, with replacement text; keyboard focus, double-click, arrows and wheel keep their prior behavior. Native egui input confirms partial replacement gives 17 px instead of 700 px, one Undo/Redo, window captures and an exact saved-document read in a separate CLI process. Short movements still counted as clicks can select all; that case remains open.
|
||||
- The contextual task bar's handle drags it anywhere on the canvas (kept whole on it as the window resizes); unpinned it keeps that offset as it follows the selection, and its More Options menu hides it, pins its position across selection changes (`window.taskBar.pin`), resets it under the selection (`window.taskBar.reset`) and shows the Properties panel. As in Illustrator the position isn't kept across launches. Not done: the bar's tool-specific variants (only the selection tools show it).
|
||||
- Tool flyouts tear off: dragging or clicking the tear-off bar on a flyout's right side (or releasing the long press over it) floats its group as a strip of tool buttons, moved by the same bar and put back in the toolbar by the × at its top (`window.floatTools`). While a group floats, the presses that would open its flyout raise the strip instead. Floating strips are kept with the preferences and in user workspaces. Not done: turning a strip vertical, docking it.
|
||||
- Panels undock (#495): a dock tab, a panel icon or a popped-out panel's title dragged out of the dock floats that panel inside the window, and the strip right of the dock's tabs floats the whole tabbed group. A floating group moves by its title bar, a tab dragged out of it floats on its own, and dropped on another group's title bar or tabs it stacks with that group; dropped on the dock, which lights up under the pointer, its panels go back where they live (the tabbed group or the icon column), as its × and the panel menus' Float Panel / Dock Panel do (`window.panel.float`, `window.panel.dock`). The Tools panel floats by its title bar and docks again on the window's left edge. Floating groups stack into sets: dropped on the upper or lower half of another group (a line shows where), a group joins it right above or below it, and the set's groups stack top to bottom, each showing one panel, moving together by the top group's title bar, whose × docks them all; a group dragged by its tab strip leaves the set (`window.panel.float {below, above}`). A double-click on a tab collapses a floating group to its tabs (`collapsed`). Sets dropped on the left edge of the icon column or of another dock column dock as dock columns (a multi-column dock, `window.panel.dock {column}`), whose groups stack, tab and tear out as a floating set's do, and whose top strip floats the column again. Floating panels, their sets and the Tools panel's position are kept with the preferences and in user workspaces, and clamped into a smaller window. Not done: panels in separate OS windows, docking a panel into the default tabbed group it doesn't belong to, reordering tabs by dragging, collapsing a set or dock column to icons, dock columns right of the default dock, resizing floating panels and the groups of a set.
|
||||
@@ -535,6 +536,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
| 2026-10-11 | minor | Graph designs and marker designs (Object › Graph › Design… and Marker…): menu stubs 27 → 25; the graphs row's missing items are column designs and Image Trace depth. Scores unchanged within rounding |
|
||||
| 2026-10-12 | minor | UI chrome: native macOS save sheets gain a Format menu (#866), with extension regression tests and actual save/cancel verification; score and hours unchanged (estimated) |
|
||||
| 2026-10-12 | minor | Compound-path appearance and fill-rule edits through directly selected members (#1197) verified with rendered holes and Undo/Redo; scores and hours unchanged |
|
||||
| 2026-10-12 | minor | UI chrome: numeric focus clicks select on release while drags retain their press position (#1326, in part); keyboard focus, stepping and double-click regressions pass; scores and hours unchanged; short movements below the click threshold remain open |
|
||||
| 2026-10-12 | minor | DXF reads control-point SPLINE closure (#1299 follow-up after #1320); open curves stay open and curves retained; rational/non-rational import and export versions regression-tested; scores/hours unchanged |
|
||||
| 2026-10-11 | minor | Type core: Middle Eastern digit types (Character panel menu › Digits: Default, Arabic, Hindi, Farsi; `{digits}`) leave the missing list; score and hours unchanged at this rounding (estimated) |
|
||||
| 2026-10-11 | minor | PDF raster-effect cap reductions are reported per original object with requested/achieved ppi; scores and hours unchanged |
|
||||
|
||||
@@ -118,6 +118,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Arrow keys nudge by Keyboard Increment (0.001–1296 pt); Shift ×10; Alt copies | done | |
|
||||
| Number fields: Up/Down step, Shift ×10, Cmd/Ctrl ÷10, wheel over a focused field | done | Shift+arrow in the Stroke weight field doesn't step by 10 (#991) |
|
||||
| Arithmetic in fields (`+ - * /`) and unit suffixes (`3 mm`, `1in`, `2p6`) | done | `Unit::parse`; mixed units in one expression not verified |
|
||||
| Numeric fields: focus click selects the whole value; dragging selects from the pressed character | partial | #1326; select-all waits for click release; px/pt/plain/optional fields, both directions and replacement text tested; native API input, 17 px partial replacement, Undo/Redo, saved-file round trip and screenshots verified; Tab, double-click, arrows and wheel preserved; short movements still classified as clicks can select all and remain open |
|
||||
| Units: pt, pc, in, mm, cm, px, ft-in, m, yd, ft; General/Stroke/Type units | done | units preference reverting to points on SVG output reported (#864) |
|
||||
| Field precision: 3 decimals for steps; values kept as f64 | done | |
|
||||
| Scrubby labels (beyond Illustrator) | done | #400 |
|
||||
@@ -194,6 +195,7 @@ re-measure of the panels added since.
|
||||
| 2026-10-12 | minor | Adding paint to an unpainted zero-width stroke restores the 1 pt default (#1253); scores unchanged |
|
||||
| 2026-10-12 | minor | macOS native save sheets gain a localised Format menu (#866); filename regression tests and an actual SVG save, PDF selection and cancellation verified; scores unchanged (estimated) |
|
||||
| 2026-10-12 | minor | Attributes: directly selected compound members read and change the compound fill rule; path direction stays member-local (#1197); score unchanged |
|
||||
| 2026-10-12 | minor | Numeric entry: click-to-focus selection waits for release so text drags keep their start (#1326, in part); keyboard focus, double-click, arrows and wheel preserved; scores and hours unchanged; short movements below the click threshold remain open |
|
||||
| 2026-10-11 | minor | A marquee inside a filled shape no longer selects it: only touching its path does (#1103); score unchanged (estimated) |
|
||||
| 2026-10-11 | minor | Live Corners: corners between curved sides round too, the curve trimmed to what the cut leaves (#1144); score unchanged (estimated) |
|
||||
| 2026-10-11 | minor | Artboards: Shift-click selects several, which move, copy and delete together (#1139); score unchanged (estimated) |
|
||||
|
||||
Reference in new issue
Block a user