mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 10:02:38 +00:00
Merge pull request #5780 from dotCooCoo/fix/font-wheel-raised-layer
Keep font wheel input with the top native panel layer
This commit is contained in:
+161
-12
@@ -13,7 +13,7 @@
|
||||
//! 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.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::{Color32, Sense, Ui, vec2};
|
||||
@@ -363,12 +363,17 @@ pub fn font_menu(
|
||||
// These turns preceded the opener. The popup cancels back to the newly
|
||||
// applied family, and any preview drawn this pass has not been applied.
|
||||
st.current.clone_from(&own);
|
||||
st.highlight = st.rows.iter().position(|row| matches!(row, Row::Family(f) if f.eq_ignore_ascii_case(&own)));
|
||||
st.previewed = None;
|
||||
}
|
||||
out = Some(FontPick::Wheel(steps));
|
||||
}
|
||||
}
|
||||
if drawn || !field.contains_pointer() {
|
||||
let queued = ui.data(|d| {
|
||||
d.get_temp::<WheelFields>(wheel_fields_id(ui.ctx().viewport_id()))
|
||||
.is_some_and(|fields| fields.queued.iter().any(|event| event.field == field.id))
|
||||
});
|
||||
if drawn || (!field.contains_pointer() && !queued) {
|
||||
st.wheel_order = None;
|
||||
ui.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
}
|
||||
@@ -436,6 +441,26 @@ struct WheelField {
|
||||
layer: egui::LayerId,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct QueuedWheel {
|
||||
frame: u64,
|
||||
field: egui::Id,
|
||||
menu: egui::Id,
|
||||
position: egui::Pos2,
|
||||
rect: egui::Rect,
|
||||
event: egui::Event,
|
||||
amount: f32,
|
||||
reset_after: bool,
|
||||
positions_after: Vec<egui::Pos2>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ReplayWheel {
|
||||
target: egui::LayerId,
|
||||
position: egui::Pos2,
|
||||
event: egui::Event,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct WheelFields {
|
||||
pass: u64,
|
||||
@@ -445,6 +470,8 @@ struct WheelFields {
|
||||
pointer: Option<egui::Pos2>,
|
||||
fields: Vec<WheelField>,
|
||||
pending: HashMap<egui::Id, Vec<WheelInput>>,
|
||||
queued: Vec<QueuedWheel>,
|
||||
replay: Vec<ReplayWheel>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -461,6 +488,58 @@ impl egui::Plugin for WheelLayerSnapshot {
|
||||
"font wheel viewport layers"
|
||||
}
|
||||
|
||||
fn on_begin_pass(&mut self, ui: &mut Ui) {
|
||||
let ctx = ui.ctx();
|
||||
let viewport = ctx.viewport_id();
|
||||
if viewport == egui::ViewportId::ROOT {
|
||||
return;
|
||||
}
|
||||
let key = wheel_fields_id(viewport);
|
||||
let Some(mut fields) = ctx.data(|d| d.get_temp::<WheelFields>(key)) else { return };
|
||||
let frame = ctx.cumulative_frame_nr_for(viewport);
|
||||
let mut ready = Vec::new();
|
||||
fields.queued.retain(|event| {
|
||||
if event.frame < frame {
|
||||
ready.push(event.clone());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
let replayed = fields.replay.len();
|
||||
for event in ready {
|
||||
let current = fields.fields.iter().find(|field| field.id == event.field);
|
||||
let layer = current.map(|field| field.layer);
|
||||
let opened_after_wheel = current.is_none()
|
||||
&& ctx.data(|d| d.get_temp::<MenuState>(event.menu)).is_some_and(|menu| menu.opened > 0 && menu.opened == fields.pass);
|
||||
let active = opened_after_wheel
|
||||
|| current.is_some_and(|field| field.rect.contains(event.position) && ctx.layer_id_at(event.position) == Some(field.layer));
|
||||
let left = !active || event.reset_after || event.positions_after.iter().any(|position| ctx.layer_id_at(*position) != layer);
|
||||
let id = event.field.with("wheel");
|
||||
if !active {
|
||||
ctx.data_mut(|d| d.remove::<f32>(id));
|
||||
if let Some(target) = ctx.layer_id_at(event.position) {
|
||||
fields.replay.push(ReplayWheel { target, position: event.position, event: event.event });
|
||||
}
|
||||
} else {
|
||||
let notches = ctx.data(|d| d.get_temp::<f32>(id)).unwrap_or(0.0) + event.amount;
|
||||
let whole = notches.trunc();
|
||||
ctx.data_mut(|d| d.insert_temp(id, notches - whole));
|
||||
if whole != 0.0 {
|
||||
fields.pending.entry(event.field).or_default().push(WheelInput::Notches(whole as i32));
|
||||
}
|
||||
}
|
||||
if left {
|
||||
ctx.data_mut(|d| d.remove::<f32>(id));
|
||||
fields.pending.entry(event.field).or_default().push(WheelInput::Leave);
|
||||
}
|
||||
}
|
||||
if fields.replay.len() > replayed {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
ctx.data_mut(|d| d.insert_temp(key, fields));
|
||||
}
|
||||
|
||||
fn on_end_pass(&mut self, ui: &mut Ui) {
|
||||
let ctx = ui.ctx();
|
||||
let viewport = ctx.viewport_id();
|
||||
@@ -502,6 +581,36 @@ fn wheel_fields_id(viewport: egui::ViewportId) -> egui::Id {
|
||||
egui::Id::unique("font-wheel-fields").with(viewport)
|
||||
}
|
||||
|
||||
fn mark_queued_wheel_leaves(fields: &mut WheelFields, point: Option<egui::Pos2>) {
|
||||
let mut marked = HashSet::new();
|
||||
for event in fields.queued.iter_mut().rev() {
|
||||
if marked.insert(event.field) {
|
||||
match point {
|
||||
Some(position) if event.rect.contains(position) => event.positions_after.push(position),
|
||||
_ => event.reset_after = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn wheel_field_layer_for_test(ctx: &egui::Context, viewport: egui::ViewportId, point: egui::Pos2) -> Option<egui::LayerId> {
|
||||
ctx.data(|data| data.get_temp::<WheelFields>(wheel_fields_id(viewport)))
|
||||
.and_then(|fields| fields.fields.iter().find(|field| field.rect.contains(point)).map(|field| field.layer))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn offset_wheel_fields_for_test(ctx: &egui::Context, viewport: egui::ViewportId, offset: egui::Vec2) {
|
||||
let key = wheel_fields_id(viewport);
|
||||
ctx.data_mut(|data| {
|
||||
let mut fields = data.get_temp::<WheelFields>(key).expect("registered wheel fields");
|
||||
for field in &mut fields.fields {
|
||||
field.rect = field.rect.translate(offset);
|
||||
}
|
||||
data.insert_temp(key, fields);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct WheelTarget {
|
||||
input: crate::state::InputGeneration,
|
||||
@@ -653,8 +762,12 @@ pub(crate) fn take_raw_wheel(ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
}
|
||||
match event {
|
||||
egui::Event::PointerMoved(p) | egui::Event::PointerButton { pos: p, .. } => {
|
||||
mark_queued_wheel_leaves(&mut fields, Some(*p));
|
||||
for field in &fields.fields {
|
||||
if over(field, pointer) && !over(field, Some(*p)) {
|
||||
if pointer.is_some_and(|point| field.rect.contains(point))
|
||||
&& !field.rect.contains(*p)
|
||||
&& !fields.queued.iter().any(|event| event.field == field.id)
|
||||
{
|
||||
ctx.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
fields.pending.entry(field.id).or_default().push(WheelInput::Leave);
|
||||
}
|
||||
@@ -663,23 +776,42 @@ pub(crate) fn take_raw_wheel(ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
}
|
||||
egui::Event::PointerGone => {
|
||||
pointer = None;
|
||||
mark_queued_wheel_leaves(&mut fields, None);
|
||||
for field in &fields.fields {
|
||||
ctx.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
fields.pending.entry(field.id).or_default().push(WheelInput::Leave);
|
||||
if !fields.queued.iter().any(|event| event.field == field.id) {
|
||||
ctx.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
fields.pending.entry(field.id).or_default().push(WheelInput::Leave);
|
||||
}
|
||||
}
|
||||
}
|
||||
egui::Event::MouseWheel { unit, delta, modifiers, .. } if modifiers.is_none() && delta.y != 0.0 => {
|
||||
if let Some(field) = fields.fields.iter().rev().find(|f| !fields.activating.contains(&f.id) && over(f, pointer)).copied() {
|
||||
let id = field.id.with("wheel");
|
||||
let mut notches: f32 = ctx.data(|d| d.get_temp(id)).unwrap_or(0.0);
|
||||
notches -= match unit {
|
||||
let amount = -match unit {
|
||||
egui::MouseWheelUnit::Point => delta.y / line,
|
||||
egui::MouseWheelUnit::Line | egui::MouseWheelUnit::Page => delta.y,
|
||||
};
|
||||
let whole = notches.trunc();
|
||||
ctx.data_mut(|d| d.insert_temp(id, notches - whole));
|
||||
if whole != 0.0 {
|
||||
fields.pending.entry(field.id).or_default().push(WheelInput::Notches(whole as i32));
|
||||
if cfg!(not(target_arch = "wasm32")) && ctx.viewport_id() != raw.viewport_id {
|
||||
if let Some(position) = pointer {
|
||||
fields.queued.push(QueuedWheel {
|
||||
frame,
|
||||
field: field.id,
|
||||
menu: field.menu,
|
||||
position,
|
||||
rect: field.rect,
|
||||
event: event.clone(),
|
||||
amount,
|
||||
reset_after: false,
|
||||
positions_after: Vec::new(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let id = field.id.with("wheel");
|
||||
let notches = ctx.data(|d| d.get_temp::<f32>(id)).unwrap_or(0.0) + amount;
|
||||
let whole = notches.trunc();
|
||||
ctx.data_mut(|d| d.insert_temp(id, notches - whole));
|
||||
if whole != 0.0 {
|
||||
fields.pending.entry(field.id).or_default().push(WheelInput::Notches(whole as i32));
|
||||
}
|
||||
}
|
||||
captured = true;
|
||||
return false;
|
||||
@@ -689,6 +821,23 @@ pub(crate) fn take_raw_wheel(ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
}
|
||||
true
|
||||
});
|
||||
if !fields.replay.is_empty() {
|
||||
let mut replay = Vec::new();
|
||||
for wheel in std::mem::take(&mut fields.replay) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let target_still_visible = native_layers.as_ref().is_some_and(|layers| (layers.0)(wheel.position) == Some(wheel.target));
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let target_still_visible = false;
|
||||
if target_still_visible {
|
||||
replay.push(egui::Event::PointerMoved(wheel.position));
|
||||
replay.push(wheel.event);
|
||||
}
|
||||
}
|
||||
if !replay.is_empty() {
|
||||
replay.push(pointer.map_or(egui::Event::PointerGone, egui::Event::PointerMoved));
|
||||
raw.events.splice(0..0, replay);
|
||||
}
|
||||
}
|
||||
fields.pointer = pointer;
|
||||
ctx.data_mut(|d| d.insert_temp(key, fields));
|
||||
if captured {
|
||||
|
||||
@@ -34,6 +34,9 @@ struct Host {
|
||||
child_transition_overlay: Option<(ViewportId, Rect, bool)>,
|
||||
child_empty_overlay: Option<(ViewportId, Rect)>,
|
||||
child_pass_through_overlay: Option<(ViewportId, Rect)>,
|
||||
child_raise_overlay: Option<(ViewportId, Rect, bool)>,
|
||||
child_raise_layer: Option<(ViewportId, egui::LayerId)>,
|
||||
child_layer_order: HashMap<ViewportId, Vec<egui::LayerId>>,
|
||||
/// The window with the keyboard.
|
||||
focus: ViewportId,
|
||||
time: f64,
|
||||
@@ -98,8 +101,23 @@ impl Host {
|
||||
host.borrow().child_transition_overlay.filter(|(viewport, _, _)| *viewport == id).map(|(_, rect, painted)| (rect, painted));
|
||||
let child_empty_overlay = host.borrow().child_empty_overlay.filter(|(viewport, _)| *viewport == id).map(|(_, rect)| rect);
|
||||
let child_pass_through_overlay = host.borrow().child_pass_through_overlay.filter(|(viewport, _)| *viewport == id).map(|(_, rect)| rect);
|
||||
let child_raise_overlay = host.borrow().child_raise_overlay.filter(|(viewport, _, _)| *viewport == id).map(|(_, rect, raise)| (rect, raise));
|
||||
let child_raise_layer = host.borrow().child_raise_layer.filter(|(viewport, _)| *viewport == id).map(|(_, layer)| layer);
|
||||
let mut layer_order = Vec::new();
|
||||
let mut full = ctx.run_ui(input, |ui| {
|
||||
layer_order = ui.ctx().memory(|memory| memory.layer_ids().collect());
|
||||
if let Some((rect, _)) = child_raise_overlay {
|
||||
egui::Area::new(egui::Id::unique("Child raised overlay")).order(egui::Order::Background).fixed_pos(rect.min).show(ui.ctx(), |ui| {
|
||||
ui.allocate_space(rect.size());
|
||||
});
|
||||
}
|
||||
(viewport.viewport_ui_cb)(ui);
|
||||
if let Some(layer) = child_raise_layer {
|
||||
ui.ctx().move_to_top(layer);
|
||||
}
|
||||
if child_raise_overlay.is_some_and(|(_, raise)| raise) {
|
||||
ui.ctx().move_to_top(egui::LayerId::new(egui::Order::Background, egui::Id::unique("Child raised overlay")));
|
||||
}
|
||||
if let Some(rect) = child_overlay {
|
||||
egui::Window::new("Child overlay").fixed_pos(rect.min).fixed_size(rect.size()).show(ui.ctx(), |ui| {
|
||||
ui.label("Child overlay");
|
||||
@@ -151,6 +169,7 @@ impl Host {
|
||||
let mut shown = Vec::new();
|
||||
full.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
|
||||
let mut host = host.borrow_mut();
|
||||
host.child_layer_order.insert(id, layer_order);
|
||||
if let Some(window) = host.windows.get_mut(&id) {
|
||||
window.texts = shown;
|
||||
}
|
||||
@@ -174,6 +193,9 @@ impl Harness {
|
||||
child_transition_overlay: None,
|
||||
child_empty_overlay: None,
|
||||
child_pass_through_overlay: None,
|
||||
child_raise_overlay: None,
|
||||
child_raise_layer: None,
|
||||
child_layer_order: HashMap::new(),
|
||||
focus: ViewportId::ROOT,
|
||||
time: 0.0,
|
||||
commands: Vec::new(),
|
||||
@@ -528,6 +550,7 @@ fn wheel_over_a_floating_character_font_field_changes_the_font() {
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert_ne!(h.app.run("text.fonts", json!({})).unwrap(), covered);
|
||||
|
||||
h.host.borrow_mut().child_transparent_overlay = Some((viewport, field.expand(10.0)));
|
||||
@@ -560,6 +583,7 @@ fn wheel_over_a_floating_character_font_field_changes_the_font() {
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert_ne!(h.app.run("text.fonts", json!({})).unwrap(), covered);
|
||||
}
|
||||
|
||||
@@ -658,6 +682,380 @@ fn wheel_stays_under_an_empty_interactive_overlay() {
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), covered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_raised_overlay_blocks_the_next_font_wheel_event() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let pointer = pos2(field.min.x + 4.0, field.center().y);
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), false));
|
||||
h.frames(2);
|
||||
let field_layer = crate::font_menu::wheel_field_layer_for_test(&h.ctx, viewport, pointer).expect("the font field layer");
|
||||
h.host.borrow_mut().child_raise_layer = Some((viewport, field_layer));
|
||||
h.frame(vec![]);
|
||||
h.host.borrow_mut().child_raise_layer = None;
|
||||
let overlay_layer = egui::LayerId::new(egui::Order::Background, egui::Id::unique("Child raised overlay"));
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), true));
|
||||
h.frame(vec![]);
|
||||
let order = h.host.borrow().child_layer_order.get(&viewport).cloned().expect("child layer order");
|
||||
let field_index = order.iter().position(|layer| *layer == field_layer).expect("font field in layer order");
|
||||
let overlay_index = order.iter().position(|layer| *layer == overlay_layer).unwrap_or_else(|| panic!("overlay absent from {order:?}"));
|
||||
assert!(field_index > overlay_index, "the font field must start above the overlay: {order:?}");
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), false));
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(pointer),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
Event::PointerGone,
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
let order = h.host.borrow().child_layer_order.get(&viewport).cloned().expect("child layer order");
|
||||
let field_index = order.iter().position(|layer| *layer == field_layer).expect("font field in layer order");
|
||||
let overlay_index = order.iter().position(|layer| *layer == overlay_layer).unwrap_or_else(|| panic!("overlay absent from {order:?}"));
|
||||
assert!(overlay_index > field_index, "the overlay must finish above the font field: {order:?}");
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert!(
|
||||
h.ctx.input_for(viewport, |input| input.raw.events.iter().any(|event| matches!(event, Event::MouseWheel { .. }))),
|
||||
"the raised overlay must receive the wheel event"
|
||||
);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
assert_eq!(h.ctx.input_for(viewport, |input| input.pointer.latest_pos()), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_overlay_raised_with_the_wheel_event_does_not_change_the_font() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let pointer = pos2(field.min.x + 4.0, field.center().y);
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), false));
|
||||
h.frames(2);
|
||||
let field_layer = crate::font_menu::wheel_field_layer_for_test(&h.ctx, viewport, pointer).expect("the font field layer");
|
||||
h.host.borrow_mut().child_raise_layer = Some((viewport, field_layer));
|
||||
h.frame(vec![]);
|
||||
h.host.borrow_mut().child_raise_layer = None;
|
||||
h.frame(vec![]);
|
||||
let overlay_layer = egui::LayerId::new(egui::Order::Background, egui::Id::unique("Child raised overlay"));
|
||||
let order = h.host.borrow().child_layer_order.get(&viewport).cloned().expect("child layer order");
|
||||
let field_index = order.iter().position(|layer| *layer == field_layer).expect("font field in layer order");
|
||||
let overlay_index = order.iter().position(|layer| *layer == overlay_layer).expect("overlay in layer order");
|
||||
assert!(field_index > overlay_index, "the font field must be above the overlay before the wheel event: {order:?}");
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), true));
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(pointer),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert!(
|
||||
h.ctx.input_for(viewport, |input| input.raw.events.iter().any(|event| matches!(event, Event::MouseWheel { .. }))),
|
||||
"the raised overlay must receive the wheel event"
|
||||
);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_closed_overlay_does_not_replay_its_wheel_into_the_panel() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let pointer = pos2(field.min.x + 4.0, field.center().y);
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), false));
|
||||
h.frames(2);
|
||||
let field_layer = crate::font_menu::wheel_field_layer_for_test(&h.ctx, viewport, pointer).expect("the font field layer");
|
||||
h.host.borrow_mut().child_raise_layer = Some((viewport, field_layer));
|
||||
h.frame(vec![]);
|
||||
h.host.borrow_mut().child_raise_layer = None;
|
||||
h.frame(vec![]);
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, field.expand(10.0), true));
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(pointer),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.host.borrow_mut().child_raise_overlay = None;
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert!(
|
||||
!h.ctx.input_for(viewport, |input| input.raw.events.iter().any(|event| matches!(event, Event::MouseWheel { .. }))),
|
||||
"a closed overlay cannot receive a later wheel event"
|
||||
);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_wheel_before_opening_a_floating_font_menu_changes_the_font() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let pointer = pos2(field.min.x + 4.0, field.center().y);
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(pointer),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
Event::PointerButton { pos: pointer, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() },
|
||||
Event::PointerButton { pos: pointer, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() },
|
||||
]);
|
||||
h.frames(3);
|
||||
assert_ne!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_delayed_wheel_does_not_change_a_font_field_that_moved() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let pointer = pos2(field.min.x + 4.0, field.center().y);
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(pointer),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
crate::font_menu::offset_wheel_fields_for_test(&h.ctx, viewport, vec2(300.0, 0.0));
|
||||
h.frame(vec![]);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_events_on_both_sides_of_an_overlay_are_checked_separately() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let covered = pos2(field.min.x + 4.0, field.center().y);
|
||||
let clear = pos2(field.max.x - 4.0, field.center().y);
|
||||
let overlay = Rect::from_min_max(field.min - vec2(2.0, 2.0), pos2(field.center().x, field.max.y + 2.0));
|
||||
assert!(covered.x < overlay.max.x && clear.x > overlay.max.x);
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, overlay, false));
|
||||
h.frames(2);
|
||||
let field_layer = crate::font_menu::wheel_field_layer_for_test(&h.ctx, viewport, clear).expect("the font field layer");
|
||||
h.host.borrow_mut().child_raise_layer = Some((viewport, field_layer));
|
||||
h.frame(vec![]);
|
||||
h.host.borrow_mut().child_raise_layer = None;
|
||||
h.frame(vec![]);
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, overlay, true));
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(covered),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
Event::PointerMoved(clear),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert_ne!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
assert_eq!(h.ctx.input_for(viewport, |input| input.pointer.latest_pos()), Some(clear));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaving_a_floating_font_field_resets_queued_fractional_wheel_input() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let inside = pos2(field.min.x + 4.0, field.center().y);
|
||||
let outside = pos2(field.max.x + 500.0, field.center().y);
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(inside),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -0.6),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
Event::PointerMoved(outside),
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(inside),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -0.6),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_floating_wheel_turn_precedes_a_pointer_leave_in_the_menu_order() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
h.app.ui.recent_fonts = ["Source Sans 3", "Source Serif 4", "Inter"].map(str::to_string).into();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let inside = pos2(field.min.x + 4.0, field.center().y);
|
||||
let outside = pos2(field.max.x + 500.0, field.center().y);
|
||||
let wheel = Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
};
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([Event::PointerMoved(inside), wheel.clone()]);
|
||||
h.frame(vec![]);
|
||||
h.frames(2);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap()[0]["family"], "Source Serif 4");
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([wheel, Event::PointerMoved(outside)]);
|
||||
h.frame(vec![]);
|
||||
h.frames(2);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap()[0]["family"], "Inter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_under_an_overlay_resets_queued_fractional_font_wheel_input() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("text.create", json!({"x": 30, "y": 50, "text": "Hi", "font": "Source Sans 3"})).unwrap();
|
||||
let viewport = h.float("character");
|
||||
let field = h
|
||||
.host
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&viewport)
|
||||
.and_then(|w| w.texts.iter().find(|(text, _)| text == "Source Sans 3").map(|(_, rect)| *rect))
|
||||
.expect("the Character panel's font family field");
|
||||
let covered = pos2(field.min.x + 4.0, field.center().y);
|
||||
let clear = pos2(field.max.x - 4.0, field.center().y);
|
||||
let overlay = Rect::from_min_max(field.min - vec2(2.0, 2.0), pos2(field.center().x, field.max.y + 2.0));
|
||||
h.host.borrow_mut().child_raise_overlay = Some((viewport, overlay, true));
|
||||
h.frames(2);
|
||||
let before = h.app.run("text.fonts", json!({})).unwrap();
|
||||
h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([
|
||||
Event::PointerMoved(clear),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -0.6),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
Event::PointerMoved(covered),
|
||||
Event::PointerMoved(clear),
|
||||
Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -0.6),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert_eq!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel_without_a_move_uses_the_floating_viewports_pointer() {
|
||||
let mut h = Harness::new();
|
||||
@@ -684,5 +1082,6 @@ fn wheel_without_a_move_uses_the_floating_viewports_pointer() {
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
});
|
||||
h.frame(vec![]);
|
||||
h.frame(vec![]);
|
||||
assert_ne!(h.app.run("text.fonts", json!({})).unwrap(), before);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user