mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 14:33:34 +00:00
Type: route font wheel input before scroll smoothing
This commit is contained in:
@@ -63,7 +63,7 @@ impl VectorcraftApp {
|
||||
|
||||
/// Identify the document, modal dialog and focused widget that the current accepted input batch
|
||||
/// belongs to.
|
||||
pub(super) fn input_generation(&self) -> InputGeneration {
|
||||
pub(crate) fn input_generation(&self) -> InputGeneration {
|
||||
InputGeneration {
|
||||
document: self.session.active().map_or(0, |document| document.uid),
|
||||
dialog: self.ui.dialog.as_ref().map(|dialog| egui::Id::unique(&dialog.kind)),
|
||||
|
||||
@@ -175,6 +175,12 @@ impl VectorcraftApp {
|
||||
/// injected until the batch was actually applied, so a burst of hidden ticks cannot queue
|
||||
/// several steps of a drag or replay them together later.
|
||||
pub fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
self.fill_raw_input(ctx, raw);
|
||||
crate::font_menu::sync_wheel_target(self, ctx, raw.viewport_id);
|
||||
crate::font_menu::take_raw_wheel(ctx, raw);
|
||||
}
|
||||
|
||||
fn fill_raw_input(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
// Both hosts call this hook once per host iteration, before deciding whether a UI pass
|
||||
// follows; it is the iteration boundary, not a UI-phase marker.
|
||||
self.begin_host_iteration();
|
||||
|
||||
+188
-45
@@ -249,6 +249,7 @@ struct MenuState {
|
||||
/// kept while the pointer stays on it: each step makes its family a recent one, which would
|
||||
/// otherwise move it in the order.
|
||||
wheel_order: Option<Arc<[String]>>,
|
||||
wheel_target: Option<crate::state::InputGeneration>,
|
||||
}
|
||||
|
||||
/// The font family menu showing `current`, `width` points wide, rows sampling `sample` (the
|
||||
@@ -263,6 +264,12 @@ pub fn font_menu(
|
||||
) -> Option<FontPick> {
|
||||
let state_id = ui.scope_id().with(&id).with("font-menu");
|
||||
let mut st: MenuState = ui.data(|d| d.get_temp(state_id)).unwrap_or_default();
|
||||
let target = ui.data(|d| d.get_temp(wheel_fields_id(ui.ctx().viewport_id()).with("target")));
|
||||
let target_changed = st.wheel_target != target;
|
||||
if target_changed {
|
||||
st.wheel_order = None;
|
||||
st.wheel_target = target;
|
||||
}
|
||||
let height = st.header + LIST_HEIGHT;
|
||||
let state = &mut st;
|
||||
// The search field and filters stay in place over the rows, which scroll. The rows are all
|
||||
@@ -277,35 +284,53 @@ pub fn font_menu(
|
||||
)
|
||||
});
|
||||
let drawn = shown.is_some();
|
||||
if target_changed {
|
||||
ui.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
}
|
||||
let mut out = shown.flatten();
|
||||
if !drawn && out.is_none() && st.previewed.take().is_some() {
|
||||
// Closed (Escape, a click outside) with a preview showing.
|
||||
out = Some(FontPick::EndPreview);
|
||||
}
|
||||
if !look.wheel || drawn || !field.contains_pointer() {
|
||||
let turns = remember_wheel_field(ui, &field, look.wheel && !drawn && field.enabled());
|
||||
if !look.wheel || !field.enabled() {
|
||||
st.wheel_order = None;
|
||||
ui.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
} else if out.is_none() {
|
||||
let notches = wheel_notches(ui, &field);
|
||||
if !notches.is_empty() {
|
||||
} else if out.is_none() || drawn {
|
||||
if !turns.is_empty() {
|
||||
let mut own = vectorcraft_text::FontDb::global().canonical(current, "").0;
|
||||
if st.wheel_order.is_none() {
|
||||
st.wheel_order = Some(wheel_order(&st, look).into());
|
||||
}
|
||||
let mut steps = Vec::new();
|
||||
if let Some(order) = st.wheel_order.as_deref() {
|
||||
for notch in notches {
|
||||
let next = wheel_steps(order, &own, notch);
|
||||
if let Some(last) = next.last() {
|
||||
own.clone_from(last);
|
||||
for turn in turns {
|
||||
match turn {
|
||||
WheelInput::Leave => st.wheel_order = None,
|
||||
WheelInput::Notches(notches) => {
|
||||
if st.wheel_order.is_none() {
|
||||
st.wheel_order = Some(wheel_order(&st, look).into());
|
||||
}
|
||||
if let Some(order) = st.wheel_order.as_deref() {
|
||||
let next = wheel_steps(order, &own, notches);
|
||||
if let Some(last) = next.last() {
|
||||
own.clone_from(last);
|
||||
}
|
||||
steps.extend(next);
|
||||
}
|
||||
}
|
||||
steps.extend(next);
|
||||
}
|
||||
}
|
||||
if !steps.is_empty() {
|
||||
if drawn {
|
||||
// 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.previewed = None;
|
||||
}
|
||||
out = Some(FontPick::Wheel(steps));
|
||||
}
|
||||
}
|
||||
if drawn || !field.contains_pointer() {
|
||||
st.wheel_order = None;
|
||||
ui.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
}
|
||||
}
|
||||
if matches!(out, Some(FontPick::Chosen(..))) {
|
||||
st.previewed = None;
|
||||
@@ -355,40 +380,158 @@ fn wheel_steps(order: &[String], current: &str, notches: i32) -> Vec<String> {
|
||||
if to >= at { order.iter().skip(at + 1).take(to - at).cloned().collect() } else { order.iter().take(at).skip(to).rev().cloned().collect() }
|
||||
}
|
||||
|
||||
/// Whole notches of the mouse wheel turned over closed field `resp` this frame, positive when it
|
||||
/// turns down (towards the user). A wheel notch is a line, or `line_scroll_speed` points of a
|
||||
/// trackpad or fine wheel; what is left of a notch carries over to the next turn. The wheel over
|
||||
/// the field is the field's: the panel under it does not scroll. A turn with a modifier key is
|
||||
/// left to the panel and the canvas.
|
||||
fn wheel_notches(ui: &Ui, resp: &egui::Response) -> Vec<i32> {
|
||||
let (line, id) = (ui.ctx().options(|o| o.input_options.line_scroll_speed), resp.id.with("wheel"));
|
||||
let mut notches: f32 = ui.data(|d| d.get_temp(id)).unwrap_or(0.0);
|
||||
let mut completed = Vec::new();
|
||||
let mut turned = false;
|
||||
ui.input_mut(|i| {
|
||||
i.events.retain(|e| match e {
|
||||
egui::Event::MouseWheel { unit, delta, modifiers, .. } if modifiers.is_none() => {
|
||||
notches -= match unit {
|
||||
egui::MouseWheelUnit::Point => delta.y / line.max(1.0),
|
||||
egui::MouseWheelUnit::Line | egui::MouseWheelUnit::Page => delta.y,
|
||||
};
|
||||
let whole = notches.trunc();
|
||||
if whole != 0.0 {
|
||||
completed.push(whole as i32);
|
||||
notches -= whole;
|
||||
}
|
||||
turned = true;
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
if turned || i.modifiers.is_none() {
|
||||
// The rest of a turn egui spreads over the next frames scrolls nothing either.
|
||||
i.smooth_scroll_delta = egui::Vec2::ZERO;
|
||||
#[derive(Clone)]
|
||||
enum WheelInput {
|
||||
Leave,
|
||||
Notches(i32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct WheelField {
|
||||
id: egui::Id,
|
||||
rect: egui::Rect,
|
||||
layer: egui::LayerId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct WheelFields {
|
||||
pass: u64,
|
||||
pending_frame: u64,
|
||||
activation_pass: u64,
|
||||
activating: Vec<egui::Id>,
|
||||
pointer: Option<egui::Pos2>,
|
||||
fields: Vec<WheelField>,
|
||||
pending: HashMap<egui::Id, Vec<WheelInput>>,
|
||||
}
|
||||
|
||||
fn wheel_fields_id(viewport: egui::ViewportId) -> egui::Id {
|
||||
egui::Id::unique("font-wheel-fields").with(viewport)
|
||||
}
|
||||
|
||||
/// Pending turns belong to the document, dialog and focus that received their input batch.
|
||||
pub(crate) fn sync_wheel_target(app: &VectorcraftApp, ctx: &egui::Context, viewport: egui::ViewportId) {
|
||||
let target = app.input_generation();
|
||||
let key = wheel_fields_id(viewport);
|
||||
ctx.data_mut(|d| {
|
||||
if d.get_temp::<crate::state::InputGeneration>(key.with("target")).is_some_and(|old| old != target) {
|
||||
d.remove::<WheelFields>(key);
|
||||
}
|
||||
d.insert_temp(key.with("target"), target);
|
||||
});
|
||||
ui.data_mut(|d| d.insert_temp(id, notches));
|
||||
completed
|
||||
}
|
||||
|
||||
/// Remember the visible closed fields for the next input batch and take this field's turns.
|
||||
fn remember_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool) -> Vec<WheelInput> {
|
||||
let key = wheel_fields_id(ui.ctx().viewport_id());
|
||||
let pass = ui.ctx().cumulative_pass_nr();
|
||||
ui.data_mut(|d| {
|
||||
let mut fields: WheelFields = d.get_temp(key).unwrap_or_default();
|
||||
if fields.pass != pass {
|
||||
fields.pass = pass;
|
||||
fields.fields.clear();
|
||||
}
|
||||
fields.fields.retain(|f| f.id != resp.id);
|
||||
if enabled {
|
||||
fields.fields.push(WheelField { id: resp.id, rect: resp.interact_rect, layer: resp.layer_id });
|
||||
}
|
||||
let turns = fields.pending.remove(&resp.id).unwrap_or_default();
|
||||
d.insert_temp(key, fields);
|
||||
turns
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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 };
|
||||
if !ctx.input(|i| i.raw.viewports.contains_key(&raw.viewport_id)) {
|
||||
ctx.data_mut(|d| d.remove::<WheelFields>(key));
|
||||
return;
|
||||
}
|
||||
let frame = ctx.cumulative_frame_nr_for(raw.viewport_id);
|
||||
let pass = ctx.cumulative_pass_nr_for(raw.viewport_id);
|
||||
if pass < fields.pass || pass.saturating_sub(fields.pass) > 1 {
|
||||
fields.fields.clear();
|
||||
fields.pointer = None;
|
||||
}
|
||||
if fields.pending_frame != frame {
|
||||
fields.pending_frame = frame;
|
||||
fields.pending.clear();
|
||||
}
|
||||
let line = ctx.options(|o| o.input_options.line_scroll_speed).max(1.0);
|
||||
let mut pointer = fields.pointer.or_else(|| ctx.input(|i| i.pointer.latest_pos()));
|
||||
let over =
|
||||
|field: &WheelField, point: Option<egui::Pos2>| point.is_some_and(|p| field.rect.contains(p) && ctx.layer_id_at(p) == Some(field.layer));
|
||||
// An activation batch belongs to the popup, including a release after a prior press.
|
||||
if fields.activation_pass != pass {
|
||||
fields.activation_pass = pass;
|
||||
fields.activating.clear();
|
||||
let pressed = ctx.input(|i| if i.pointer.button_down(egui::PointerButton::Primary) { i.pointer.press_origin() } else { None });
|
||||
if let Some(field) = fields.fields.iter().find(|f| over(f, pressed)) {
|
||||
fields.activating.push(field.id);
|
||||
}
|
||||
}
|
||||
let focused = ctx.memory(|m| m.focused());
|
||||
let mut captured = false;
|
||||
raw.events.retain(|event| {
|
||||
let opening = match event {
|
||||
egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: true, .. } => {
|
||||
fields.fields.iter().rev().find(|f| over(f, Some(*pos))).map(|f| f.id)
|
||||
}
|
||||
egui::Event::Key { key: egui::Key::Enter | egui::Key::Space, pressed: true, .. } if raw.focused => {
|
||||
focused.filter(|id| fields.fields.iter().any(|f| f.id == *id))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(id) = opening
|
||||
&& !fields.activating.contains(&id)
|
||||
{
|
||||
fields.activating.push(id);
|
||||
}
|
||||
match event {
|
||||
egui::Event::PointerMoved(p) | egui::Event::PointerButton { pos: p, .. } => {
|
||||
for field in &fields.fields {
|
||||
if over(field, pointer) && !over(field, Some(*p)) {
|
||||
ctx.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
fields.pending.entry(field.id).or_default().push(WheelInput::Leave);
|
||||
}
|
||||
}
|
||||
pointer = Some(*p);
|
||||
}
|
||||
egui::Event::PointerGone => {
|
||||
pointer = 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);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
captured = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
true
|
||||
});
|
||||
fields.pointer = pointer;
|
||||
ctx.data_mut(|d| d.insert_temp(key, fields));
|
||||
if captured {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/// What the menu's header hands its rows.
|
||||
|
||||
@@ -262,6 +262,99 @@ fn the_wheel_follows_the_menus_order_and_filters() {
|
||||
assert_eq!(order, favorites, "the favourites filter");
|
||||
}
|
||||
|
||||
/// A field removed by a later layout pass must not intercept the next wheel event.
|
||||
#[test]
|
||||
fn a_family_field_omitted_from_the_last_pass_leaves_the_wheel_to_its_owner() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut point = egui::Pos2::ZERO;
|
||||
for _ in 0..3 {
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
let resp = ui.button("Family");
|
||||
point = resp.rect.center();
|
||||
super::remember_wheel_field(ui, &resp, true);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
let mut pass = 0;
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
if pass == 0 {
|
||||
let resp = ui.button("Family");
|
||||
point = resp.rect.center();
|
||||
super::remember_wheel_field(ui, &resp, true);
|
||||
ui.ctx().request_discard("the next pass removes the field");
|
||||
}
|
||||
pass += 1;
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
assert!(pass > 1);
|
||||
let mut raw = egui::RawInput {
|
||||
events: vec![
|
||||
egui::Event::PointerMoved(point),
|
||||
egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: egui::vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
super::take_raw_wheel(&ctx, &mut raw);
|
||||
assert!(raw.events.iter().any(|e| matches!(e, egui::Event::MouseWheel { .. })));
|
||||
}
|
||||
|
||||
/// Activation leaves later wheel input to the popup; an earlier turn still belongs to the field.
|
||||
#[test]
|
||||
fn activating_the_family_popup_leaves_its_wheel_input_available() {
|
||||
let activations = [None, Some((egui::Key::Enter, egui::Modifiers::NONE)), Some((egui::Key::Space, egui::Modifiers::SHIFT))];
|
||||
for activation in activations {
|
||||
let ctx = egui::Context::default();
|
||||
let frame = |events| {
|
||||
let mut raw = egui::RawInput { events, ..Default::default() };
|
||||
super::take_raw_wheel(&ctx, &mut raw);
|
||||
let mut point = egui::Pos2::ZERO;
|
||||
let mut out = ctx.run_ui(raw, |ui| {
|
||||
let resp = ui.button("Family");
|
||||
point = resp.rect.center();
|
||||
if activation.is_some() {
|
||||
resp.request_focus();
|
||||
}
|
||||
super::remember_wheel_field(ui, &resp, true);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
point
|
||||
};
|
||||
let mut point = egui::Pos2::ZERO;
|
||||
for _ in 0..3 {
|
||||
point = frame(vec![]);
|
||||
}
|
||||
let button =
|
||||
|pressed| egui::Event::PointerButton { pos: point, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
let opener = if let Some((key, modifiers)) = activation {
|
||||
egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers }
|
||||
} else {
|
||||
frame(vec![egui::Event::PointerMoved(point), button(true)]);
|
||||
button(false)
|
||||
};
|
||||
let turn = || egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: egui::vec2(0.0, -1.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
};
|
||||
let mut events = vec![egui::Event::PointerMoved(point)];
|
||||
if activation.is_some() {
|
||||
events.push(turn());
|
||||
}
|
||||
events.extend([opener, turn()]);
|
||||
let mut raw = egui::RawInput { events, ..Default::default() };
|
||||
super::take_raw_wheel(&ctx, &mut raw);
|
||||
assert_eq!(raw.events.iter().filter(|e| matches!(e, egui::Event::MouseWheel { .. })).count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Font Technology filter's Variable is the font adjective, not the Variables panel's noun.
|
||||
#[test]
|
||||
fn the_variable_filter_is_named_as_fonts_are() {
|
||||
|
||||
@@ -804,6 +804,7 @@ fn font_height_reference(app: &mut VectorcraftApp, ui: &mut Ui, width: f32) {
|
||||
/// Control bar), with their `ids` and `widths`. The mouse wheel over the closed family field steps
|
||||
/// the text through the families.
|
||||
pub(crate) fn font_pickers(app: &mut VectorcraftApp, ui: &mut Ui, s: &CharStyle, ids: (&str, &str), widths: (f32, f32)) {
|
||||
crate::font_menu::sync_wheel_target(app, ui.ctx(), ui.ctx().viewport_id());
|
||||
let sample = crate::font_menu::sample_text(app);
|
||||
let look = crate::font_menu::MenuLook { wheel: true, ..crate::font_menu::MenuLook::of(app) };
|
||||
if let Some(pick) = crate::font_menu::font_menu(ui, ids.0, &s.font_family, widths.0, sample.as_deref(), look) {
|
||||
|
||||
@@ -232,8 +232,9 @@ fn panel_frame(
|
||||
family: &str,
|
||||
draw: &dyn Fn(&mut VectorcraftApp, &mut egui::Ui),
|
||||
) -> (Option<egui::Pos2>, bool) {
|
||||
let input =
|
||||
let mut input =
|
||||
egui::RawInput { events, screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(600.0, 800.0))), ..Default::default() };
|
||||
app.raw_input_hook(ctx, &mut input);
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
crate::font_menu::end_stale_preview(app, ui.ctx());
|
||||
draw(app, ui);
|
||||
@@ -336,6 +337,119 @@ fn opposite_wheel_notches_in_one_frame_keep_their_undo_steps() {
|
||||
assert_eq!(first_style(&app, id).font_family, "Inter");
|
||||
}
|
||||
|
||||
/// A turn before opening applies once; the popup keeps the later wheel and cancels to that font.
|
||||
#[test]
|
||||
fn a_wheel_before_opening_the_family_popup_keeps_its_committed_change() {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
let id = selected_inter(&mut app);
|
||||
let draw = |app: &mut VectorcraftApp, ui: &mut egui::Ui| crate::panels::character::show(app, ui);
|
||||
let mut field = None;
|
||||
for _ in 0..3 {
|
||||
field = panel_frame(&ctx, &mut app, vec![], "Inter", &draw).0;
|
||||
}
|
||||
let field = field.unwrap();
|
||||
let before = app.session.active().unwrap().history.undo.len();
|
||||
let button = |pressed| egui::Event::PointerButton { pos: field, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
let turn = || wheel(egui::MouseWheelUnit::Line, -1.0, Default::default());
|
||||
panel_frame(&ctx, &mut app, vec![egui::Event::PointerMoved(field), turn(), button(true), button(false), turn()], "Inter", &draw);
|
||||
let applied = first_style(&app, id).font_family;
|
||||
assert_ne!(applied, "Inter");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
|
||||
for _ in 0..3 {
|
||||
panel_frame(&ctx, &mut app, vec![], &applied, &draw);
|
||||
}
|
||||
panel_frame(&ctx, &mut app, key(egui::Key::Escape), &applied, &draw);
|
||||
assert_eq!(first_style(&app, id).font_family, applied);
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(first_style(&app, id).font_family, "Inter");
|
||||
}
|
||||
|
||||
/// A later pointer move must not change which field owns an earlier wheel event.
|
||||
#[test]
|
||||
fn wheel_events_use_the_pointer_position_when_they_occurred() {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
let id = selected_inter(&mut app);
|
||||
let draw = |app: &mut VectorcraftApp, ui: &mut egui::Ui| crate::panels::character::show(app, ui);
|
||||
let mut field = None;
|
||||
for _ in 0..3 {
|
||||
field = panel_frame(&ctx, &mut app, vec![], "Inter", &draw).0;
|
||||
}
|
||||
let field = field.expect("the Character panel shows the family field");
|
||||
let outside = egui::pos2(580.0, 780.0);
|
||||
panel_frame(&ctx, &mut app, vec![egui::Event::PointerMoved(outside)], "Inter", &draw);
|
||||
let before = app.session.active().unwrap().history.undo.len();
|
||||
let line = egui::MouseWheelUnit::Line;
|
||||
panel_frame(&ctx, &mut app, vec![wheel(line, -1.0, Default::default()), egui::Event::PointerMoved(field)], "Inter", &draw);
|
||||
assert_eq!(first_style(&app, id).font_family, "Inter", "the wheel was outside the field");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before);
|
||||
assert!(ctx.input(|i| i.events.iter().any(|e| matches!(e, egui::Event::MouseWheel { .. }))), "outside wheel input is left for its owner");
|
||||
panel_frame(&ctx, &mut app, vec![wheel(line, -1.0, Default::default()), egui::Event::PointerMoved(outside)], "Inter", &draw);
|
||||
assert_ne!(first_style(&app, id).font_family, "Inter", "the wheel was over the field before the pointer left");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(first_style(&app, id).font_family, "Inter");
|
||||
let button = |pos| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() };
|
||||
panel_frame(&ctx, &mut app, vec![button(field), wheel(line, -1.0, Default::default()), button(outside)], "Inter", &draw);
|
||||
assert_ne!(first_style(&app, id).font_family, "Inter", "button events also carry the pointer's position");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
let end = egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: egui::Vec2::ZERO,
|
||||
modifiers: Default::default(),
|
||||
phase: egui::TouchPhase::End,
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
};
|
||||
panel_frame(&ctx, &mut app, vec![end], "Inter", &draw);
|
||||
panel_frame(
|
||||
&ctx,
|
||||
&mut app,
|
||||
vec![
|
||||
wheel(egui::MouseWheelUnit::Point, -2.0, Default::default()),
|
||||
egui::Event::PointerMoved(field),
|
||||
wheel(line, -1.0, Default::default()),
|
||||
egui::Event::PointerMoved(outside),
|
||||
],
|
||||
"Inter",
|
||||
&draw,
|
||||
);
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
|
||||
assert_eq!(ctx.input(|i| i.smooth_scroll_delta.y), -2.0, "only the outside wheel contributes to panel scrolling");
|
||||
}
|
||||
|
||||
/// A turn queued for one document cannot restyle another document opened before its UI pass.
|
||||
#[test]
|
||||
fn a_wheel_queued_for_another_document_is_discarded() {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
selected_inter(&mut app);
|
||||
let draw = |app: &mut VectorcraftApp, ui: &mut egui::Ui| crate::panels::character::show(app, ui);
|
||||
let mut field = None;
|
||||
for _ in 0..3 {
|
||||
field = panel_frame(&ctx, &mut app, vec![], "Inter", &draw).0;
|
||||
}
|
||||
let field = field.expect("the Character panel shows the family field");
|
||||
let mut raw = egui::RawInput {
|
||||
events: vec![egui::Event::PointerMoved(field), wheel(egui::MouseWheelUnit::Line, -1.0, Default::default())],
|
||||
screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(600.0, 800.0))),
|
||||
..Default::default()
|
||||
};
|
||||
app.raw_input_hook(&ctx, &mut raw);
|
||||
assert!(!raw.events.iter().any(|e| matches!(e, egui::Event::MouseWheel { .. })));
|
||||
let id = selected_inter(&mut app);
|
||||
let before = app.session.active().unwrap().history.undo.len();
|
||||
let mut out = ctx.run_ui(raw, |ui| draw(&mut app, ui));
|
||||
out.textures_delta.clear();
|
||||
assert_eq!(first_style(&app, id).font_family, "Inter");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before);
|
||||
}
|
||||
|
||||
/// #5253: the Properties panel and the Control bar have the same family field, and the wheel steps
|
||||
/// the families over it too.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user