Font menu: a font field covered by an overlay needs the pointer moved again

A closed font family field stayed ready for the wheel as long as it kept
its place and drew in each pass. Coverage was checked only when a pointer
move or wheel turn arrived. When a window, modal or popup covered a still
pointer and went away with no input in between, the next plain wheel
turn changed the selected type's font.

The field now stops being ready, and a wheel stream it held ends, in any
pass where the last pointer position is over the field but on another
layer. The pointer has to move onto the field again before the wheel
steps the font.
This commit is contained in:
dotCooCoo
2026-11-09 18:47:38 -08:00
parent 008595aa69
commit 4f4a447b05
4 changed files with 48 additions and 8 deletions
+9 -6
View File
@@ -12,8 +12,8 @@
//! the mouse wheel turned over the closed field applies the next family (or previous one) to the
//! selected text, one undo step per notch. The menu's order at the first turn stays fixed until the
//! pointer leaves the field or the text selection changes; steps stop at its first and last family.
//! The field takes the wheel only when the pointer was moved onto it after the field last moved and
//! the wheel stream began there.
//! The field takes the wheel only when the pointer was moved onto it after the field last moved or
//! was covered by a window or overlay, and the wheel stream began there.
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
@@ -446,7 +446,7 @@ struct WheelField {
/// The seconds without wheel input that end a wheel stream. This is VectorCraft's own value.
pub(crate) const WHEEL_STREAM_GAP: f64 = 0.5;
/// The field the pointer was last moved onto, while that field stays where it was.
/// The field the pointer was last moved onto, while that field stays where it was and uncovered.
#[derive(Clone, Copy)]
struct ArmedWheelField {
id: egui::Id,
@@ -732,6 +732,9 @@ fn remember_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool) -> Vec<Wh
fn remember_owned_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool, menu: egui::Id, owner: Option<WheelOwner>) -> Vec<WheelInput> {
let key = wheel_fields_id(ui.ctx().viewport_id());
let pass = ui.ctx().cumulative_pass_nr();
// A window or overlay over the pointer covers the field even when no input arrives.
let pointer = ui.data_mut(|d| d.get_temp_mut_or_default::<WheelFields>(key).pointer).or_else(|| ui.ctx().input(|i| i.pointer.latest_pos()));
let covered = pointer.is_some_and(|p| resp.interact_rect.contains(p) && ui.ctx().layer_id_at(p) != Some(resp.layer_id));
ui.data_mut(|d| {
let mut fields: WheelFields = d.get_temp(key).unwrap_or_default();
if fields.pass != pass {
@@ -742,7 +745,7 @@ fn remember_owned_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool, men
if let Some(armed) = &mut fields.armed
&& armed.id == resp.id
{
if armed.rect == resp.interact_rect && pass.saturating_sub(armed.pass) <= 1 {
if armed.rect == resp.interact_rect && pass.saturating_sub(armed.pass) <= 1 && !covered {
armed.pass = pass;
} else {
fields.armed = None;
@@ -761,8 +764,8 @@ fn remember_owned_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool, men
/// Take the closed font fields' wheel events before egui smooths and combines them. Other
/// events stay in the input, so scrolling elsewhere keeps its own delta and event order. A field
/// takes a wheel stream only when the stream begins with the pointer over it and the pointer was
/// moved onto it after the field last moved. A stream that began anywhere else stays there until
/// it ends, and a stream that leaves its field does not return to it.
/// moved onto it after the field last moved or was covered. A stream that began anywhere else
/// stays there until it ends, and a stream that leaves its field does not return to it.
pub(crate) fn take_raw_wheel(ctx: &egui::Context, raw: &mut egui::RawInput) {
let key = wheel_fields_id(raw.viewport_id);
let Some(mut fields) = ctx.data(|d| d.get_temp::<WheelFields>(key)) else { return };
+37
View File
@@ -358,6 +358,11 @@ fn activating_the_family_popup_leaves_its_wheel_input_available() {
/// A family field at `top`, the input's clock at `time`. Returns the wheel events left in the
/// input, the notches the field took and the point in the middle of the field.
fn stream_frame(ctx: &egui::Context, top: Option<f32>, time: f64, events: Vec<egui::Event>) -> (usize, i32, egui::Pos2) {
covered_stream_frame(ctx, top, false, time, events)
}
/// [`stream_frame`], with an overlay over the whole field when `covered` is set.
fn covered_stream_frame(ctx: &egui::Context, top: Option<f32>, covered: bool, time: f64, events: Vec<egui::Event>) -> (usize, i32, egui::Pos2) {
let mut raw = egui::RawInput { time: Some(time), events, ..Default::default() };
super::take_raw_wheel(ctx, &mut raw);
let left = raw.events.iter().filter(|e| matches!(e, egui::Event::MouseWheel { .. })).count();
@@ -373,6 +378,12 @@ fn stream_frame(ctx: &egui::Context, top: Option<f32>, time: f64, events: Vec<eg
notches += n;
}
}
if covered {
let cover = rect.expand(5.0);
egui::Area::new(egui::Id::unique("stream overlay")).order(egui::Order::Foreground).fixed_pos(cover.min).show(ui.ctx(), |ui| {
ui.allocate_exact_size(cover.size(), egui::Sense::click());
});
}
});
out.textures_delta.clear();
(left, notches, point)
@@ -439,6 +450,32 @@ fn a_field_that_moves_away_and_back_needs_the_pointer_moved_again() {
assert_eq!((left, notches), (1, 0), "the pointer left the window");
}
/// An overlay that comes and goes over a still pointer, with no input while it is up, leaves the
/// field needing the pointer moved again; a stream the field held leaves it.
#[test]
fn a_field_covered_by_an_overlay_needs_the_pointer_moved_again() {
let (ctx, point) = stream_context(40.0);
stream_frame(&ctx, Some(40.0), 110.0, vec![egui::Event::PointerMoved(point)]);
covered_stream_frame(&ctx, Some(40.0), true, 111.0, vec![]);
covered_stream_frame(&ctx, Some(40.0), true, 112.0, vec![]);
stream_frame(&ctx, Some(40.0), 113.0, vec![]);
stream_frame(&ctx, Some(40.0), 114.0, vec![]);
let (left, notches, _) = stream_frame(&ctx, Some(40.0), 120.0, vec![plain_turn()]);
assert_eq!((left, notches), (1, 0), "the overlay covered the field");
let nudged = point + egui::vec2(2.0, 0.0);
let (left, notches, _) = stream_frame(&ctx, Some(40.0), 130.0, vec![egui::Event::PointerMoved(nudged), plain_turn()]);
assert_eq!((left, notches), (0, 1), "the pointer moved onto the field again");
let (ctx, point) = stream_context(40.0);
let (left, notches, _) = stream_frame(&ctx, Some(40.0), 110.0, vec![egui::Event::PointerMoved(point), plain_turn()]);
assert_eq!((left, notches), (0, 1));
covered_stream_frame(&ctx, Some(40.0), true, 110.1, vec![]);
covered_stream_frame(&ctx, Some(40.0), true, 110.2, vec![]);
stream_frame(&ctx, Some(40.0), 110.3, vec![]);
let (left, notches, _) = stream_frame(&ctx, Some(40.0), 110.4, vec![plain_turn()]);
assert_eq!((left, notches), (1, 0), "the overlay covered the field during the stream");
}
/// A field missing from a pass loses the pointer's placement while another font field keeps
/// drawing in that pass, as when a panel collapses and the Control bar's field stays.
#[test]
+1 -1
View File
@@ -18,7 +18,7 @@ against Illustrator's public documentation; **estimated** unless noted.
| Feature | Status | Notes |
|---|---|---|
| Mouse wheel over the closed font-family field | done | #5253: Character, Properties and Control step selected text through the family list using saved font filters (excluding search text), one undo step per notch; the style is matched and the list stops at either end. Escape and leaving the field keep the change. The field takes the wheel only when the pointer was moved onto it after the field last moved and the wheel stream began there; a stream that began elsewhere stays there until 0.5 s pass with no wheel input. Order, bounds, Undo grouping, this rule and its 0.5 s are VectorCraft's choices; scores unchanged. |
| Mouse wheel over the closed font-family field | done | #5253: Character, Properties and Control step selected text through the family list using saved font filters (excluding search text), one undo step per notch; the style is matched and the list stops at either end. Escape and leaving the field keep the change. The field takes the wheel only when the pointer was moved onto it after the field last moved or was covered by a window or overlay, and the wheel stream began there; a stream that began elsewhere stays there until 0.5 s pass with no wheel input. Order, bounds, Undo grouping, this rule and its 0.5 s are VectorCraft's choices; scores unchanged. |
| Soft return (forced line break, Shift+Enter) | done | Shift+Enter inserts a soft return (U+2028), breaking the line without starting a new paragraph; paragraph spacing (space before/after) and first-line indents apply only at paragraph ends; continuation lines in lists do not repeat the marker; in justified text a line a soft return ends is justified as a wrapped line is, only the paragraph's last line keeping its alignment, as Illustrator's own layout in a user's `.ai` sets it (evidence: `tests::typo::soft_return_in_justified_text_spreads_like_a_wrapped_line`; first done the other way in #2754) |
| Native font-family labels | partial | With Show Font Names in English off, localized legacy names are used when typographic names contain no native label (#2208); loaded-font and catalog-only regressions. Choosing among multiple native languages is unchanged. |
| Point, area and type-on-a-path type; vertical variants | done / partial | vertical point, area and path type have initial support; fullwidth `!` / `?` keep the nominal glyph upright when no vertical alternate exists (Unicode Tu); in a non-rectangular area a word too wide for the top lines moves down to the first line that holds it (#1615); Escape while typing ends the edit, keeps the type selected and switches back to the tool whose double-click on type chose the Type tool (kept until another tool is chosen or another document or window becomes active, so later edits with the Type tool return there too), or to the Selection tool when there is no such tool, as when the Type tool was chosen from the toolbar or after such a switch (#1802), and Cmd+Enter (Ctrl+Enter on Windows and Linux) does the same unless a command is bound to it; `text.create` and `text.createInPath` take `justify` (#1752); MCP `add_text` forwards `vertical` to both commands; Type › Type Orientation sets every frame of a thread, and a thread's frames share one writing direction; the Type and Vertical Type tools make area type when a closed path's outline is clicked and type on a path when an open one is, with pointers that say which, as the public help describes ([Create text](https://web.archive.org/web/20220127090642/https://helpx.adobe.com/illustrator/using/creating-text.html), [Create type on a path](https://web.archive.org/web/20220127090635/https://helpx.adobe.com/illustrator/using/creating-type-path.html)) |
+1 -1
View File
@@ -264,7 +264,7 @@ tools, never checked side by side) isn't captured row by row.
| Behaviour | Status | Notes |
|---|---|---|
| Wheel over a closed font-family field in Character, Properties or Control | done | #5253: applies the next or previous family to selected text, one undo step per notch; uses saved font filters, ignores search text and stops at the list's ends. Escape and pointer departure keep the change. The field takes the wheel only when the pointer was moved onto it after the field last moved and the wheel stream began there; a stream that began elsewhere stays there until 0.5 s pass with no wheel input. This rule and its 0.5 s are VectorCraft's choices. Other font-picking menus and modifier-wheel input keep their existing behavior. |
| Wheel over a closed font-family field in Character, Properties or Control | done | #5253: applies the next or previous family to selected text, one undo step per notch; uses saved font filters, ignores search text and stops at the list's ends. Escape and pointer departure keep the change. The field takes the wheel only when the pointer was moved onto it after the field last moved or was covered by a window or overlay, and the wheel stream began there; a stream that began elsewhere stays there until 0.5 s pass with no wheel input. This rule and its 0.5 s are VectorCraft's choices. Other font-picking menus and modifier-wheel input keep their existing behavior. |
| Arrow keys nudge by Keyboard Increment (0.001–1296 pt); Shift ×10; Alt copies | done | |
| Fields commit what was typed on a click away or a move to another field, as Enter does; Escape discards it | done | #1787 |
| Number fields: Up/Down step, Shift ×10, Cmd/Ctrl ÷10, wheel over a hovered field without a click; steps, the wheel and label scrubs stop at the field's bounds (a stroke weight at 0, a miter limit at 1) | done | Shift+arrow in the Stroke weight field doesn't step by 10 (#991); bounds #3370 |