From 4f4a447b05d072f594aad61188942e778413eb22 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 9 Nov 2026 18:47:38 -0800 Subject: [PATCH] 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. --- crates/ui-egui/src/font_menu.rs | 15 ++++++----- crates/ui-egui/src/font_menu/tests.rs | 37 +++++++++++++++++++++++++++ docs/type-parity.md | 2 +- docs/ui-parity.md | 2 +- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/font_menu.rs b/crates/ui-egui/src/font_menu.rs index d0750d373..19d103a74 100644 --- a/crates/ui-egui/src/font_menu.rs +++ b/crates/ui-egui/src/font_menu.rs @@ -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) -> Vec { 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::(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::(key)) else { return }; diff --git a/crates/ui-egui/src/font_menu/tests.rs b/crates/ui-egui/src/font_menu/tests.rs index 6e0d3bba6..87c3feb06 100644 --- a/crates/ui-egui/src/font_menu/tests.rs +++ b/crates/ui-egui/src/font_menu/tests.rs @@ -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, time: f64, events: Vec) -> (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, covered: bool, time: f64, events: Vec) -> (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, time: f64, events: Vec