mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
Type: stop hidden font fields from capturing wheel input
This commit is contained in:
@@ -41,6 +41,42 @@ pub enum FontPick {
|
||||
Wheel(Vec<String>),
|
||||
}
|
||||
|
||||
/// The UI that contains a font family field.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum WheelOwner {
|
||||
Character,
|
||||
Properties,
|
||||
Control,
|
||||
/// The Character popover attached to this Control bar link.
|
||||
ControlPopover(egui::Id),
|
||||
}
|
||||
|
||||
impl WheelOwner {
|
||||
fn visible(self, app: &VectorcraftApp, ctx: &egui::Context) -> bool {
|
||||
if crate::panels::character::text_style(app).is_none() {
|
||||
return false;
|
||||
}
|
||||
let panel = match self {
|
||||
Self::Control => return app.ui.control_bar && app.ui.screen_mode < 2,
|
||||
Self::ControlPopover(anchor) => {
|
||||
return app.ui.control_bar && app.ui.screen_mode < 2 && widgets::popover_is_open(ctx, anchor);
|
||||
}
|
||||
Self::Character => "character",
|
||||
Self::Properties => {
|
||||
let selected = app.session.active().and_then(|document| document.selection.objects.first().and_then(|id| document.doc.node(*id)));
|
||||
if !selected.is_some_and(|n| matches!(&n.kind, vectorcraft_doc::NodeKind::Text(_))) {
|
||||
return false;
|
||||
}
|
||||
"properties"
|
||||
}
|
||||
};
|
||||
app.ui.dock
|
||||
&& app.ui.screen_mode < 3
|
||||
&& (crate::panel_docking::showing(&app.ui, panel)
|
||||
|| (app.ui.transient_panel.as_deref() == Some(panel) && crate::panel_docking::showing(&app.ui, "appearance")))
|
||||
}
|
||||
}
|
||||
|
||||
/// How the menu looks (from the preferences and the favourites) and whether its closed field
|
||||
/// takes the mouse wheel.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -58,12 +94,14 @@ pub struct MenuLook<'a> {
|
||||
/// The mouse wheel turned over the closed field steps through the families in the menu's
|
||||
/// order ([`FontPick::Wheel`]): the Character and Properties panels and the Control bar.
|
||||
pub wheel: bool,
|
||||
/// Its owner, so a field hidden before the next layout cannot capture wheel input.
|
||||
pub wheel_owner: Option<WheelOwner>,
|
||||
}
|
||||
|
||||
impl Default for MenuLook<'static> {
|
||||
/// Samples on, English names, medium rows, no favourites, no wheel.
|
||||
fn default() -> Self {
|
||||
Self { samples: true, english_names: true, row: ROW, favorites: &[], recent: &[], wheel: false }
|
||||
Self { samples: true, english_names: true, row: ROW, favorites: &[], recent: &[], wheel: false, wheel_owner: None }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +119,7 @@ impl<'a> MenuLook<'a> {
|
||||
favorites: &app.ui.favorite_fonts,
|
||||
recent: app.recent_fonts(),
|
||||
wheel: false,
|
||||
wheel_owner: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,7 +289,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>,
|
||||
wheel_target: Option<WheelTarget>,
|
||||
}
|
||||
|
||||
/// The font family menu showing `current`, `width` points wide, rows sampling `sample` (the
|
||||
@@ -293,7 +332,7 @@ pub fn font_menu(
|
||||
// Closed (Escape, a click outside) with a preview showing.
|
||||
out = Some(FontPick::EndPreview);
|
||||
}
|
||||
let turns = remember_wheel_field(ui, &field, look.wheel && !drawn && field.enabled());
|
||||
let turns = remember_owned_wheel_field(ui, &field, look.wheel && !drawn && field.enabled(), state_id, look.wheel_owner);
|
||||
if !look.wheel || !field.enabled() {
|
||||
st.wheel_order = None;
|
||||
ui.data_mut(|d| d.remove::<f32>(field.id.with("wheel")));
|
||||
@@ -390,6 +429,8 @@ enum WheelInput {
|
||||
#[derive(Clone, Copy)]
|
||||
struct WheelField {
|
||||
id: egui::Id,
|
||||
menu: egui::Id,
|
||||
owner: Option<WheelOwner>,
|
||||
rect: egui::Rect,
|
||||
layer: egui::LayerId,
|
||||
}
|
||||
@@ -409,20 +450,58 @@ 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.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
struct WheelTarget {
|
||||
input: crate::state::InputGeneration,
|
||||
workspace: u64,
|
||||
}
|
||||
|
||||
/// Pending turns and field rectangles belong to their input target and visible workspace.
|
||||
pub(crate) fn sync_wheel_target(app: &VectorcraftApp, ctx: &egui::Context, viewport: egui::ViewportId) {
|
||||
let target = app.input_generation();
|
||||
let target = WheelTarget { input: app.input_generation(), workspace: app.ui.docking_generation };
|
||||
let key = wheel_fields_id(viewport);
|
||||
let (previous, mut fields) = ctx.data(|d| (d.get_temp::<WheelTarget>(key.with("target")), d.get_temp::<WheelFields>(key)));
|
||||
if previous.is_some_and(|old| old != target) {
|
||||
fields = None;
|
||||
}
|
||||
let mut hidden = Vec::new();
|
||||
if let Some(fields) = &mut fields {
|
||||
fields.fields.retain(|field| {
|
||||
if field.owner.is_none_or(|owner| owner.visible(app, ctx)) {
|
||||
true
|
||||
} else {
|
||||
hidden.push(*field);
|
||||
false
|
||||
}
|
||||
});
|
||||
for field in &hidden {
|
||||
fields.pending.remove(&field.id);
|
||||
}
|
||||
}
|
||||
ctx.data_mut(|d| {
|
||||
if d.get_temp::<crate::state::InputGeneration>(key.with("target")).is_some_and(|old| old != target) {
|
||||
if let Some(fields) = fields {
|
||||
d.insert_temp(key, fields);
|
||||
} else {
|
||||
d.remove::<WheelFields>(key);
|
||||
}
|
||||
for field in hidden {
|
||||
d.remove::<f32>(field.id.with("wheel"));
|
||||
if let Some(mut state) = d.get_temp::<MenuState>(field.menu) {
|
||||
state.wheel_order = None;
|
||||
d.insert_temp(field.menu, state);
|
||||
}
|
||||
}
|
||||
d.insert_temp(key.with("target"), target);
|
||||
});
|
||||
}
|
||||
|
||||
/// Remember the visible closed fields for the next input batch and take this field's turns.
|
||||
#[cfg(test)]
|
||||
fn remember_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool) -> Vec<WheelInput> {
|
||||
remember_owned_wheel_field(ui, resp, enabled, resp.id, None)
|
||||
}
|
||||
|
||||
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();
|
||||
ui.data_mut(|d| {
|
||||
@@ -433,7 +512,7 @@ fn remember_wheel_field(ui: &Ui, resp: &egui::Response, enabled: bool) -> Vec<Wh
|
||||
}
|
||||
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 });
|
||||
fields.fields.push(WheelField { id: resp.id, menu, owner, rect: resp.interact_rect, layer: resp.layer_id });
|
||||
}
|
||||
let turns = fields.pending.remove(&resp.id).unwrap_or_default();
|
||||
d.insert_temp(key, fields);
|
||||
|
||||
@@ -804,9 +804,26 @@ 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)) {
|
||||
let wheel_owner = match ids.0 {
|
||||
"ch-font" => Some(crate::font_menu::WheelOwner::Character),
|
||||
"font" => Some(crate::font_menu::WheelOwner::Properties),
|
||||
"cb-font" => Some(crate::font_menu::WheelOwner::Control),
|
||||
_ => None,
|
||||
};
|
||||
font_pickers_owned(app, ui, s, ids, widths, wheel_owner);
|
||||
}
|
||||
|
||||
fn font_pickers_owned(
|
||||
app: &mut VectorcraftApp,
|
||||
ui: &mut Ui,
|
||||
s: &CharStyle,
|
||||
ids: (&str, &str),
|
||||
widths: (f32, f32),
|
||||
wheel_owner: Option<crate::font_menu::WheelOwner>,
|
||||
) {
|
||||
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) };
|
||||
let look = crate::font_menu::MenuLook { wheel: true, wheel_owner, ..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) {
|
||||
crate::font_menu::apply(app, ui.ctx(), pick);
|
||||
}
|
||||
@@ -895,7 +912,7 @@ pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
.on_hover_text(tl!("Character options"));
|
||||
widgets::popover(&link, link.clicked(), |ui| {
|
||||
ui.set_width(260.0);
|
||||
show(app, ui);
|
||||
show_owned(app, ui, crate::font_menu::WheelOwner::ControlPopover(link.id));
|
||||
});
|
||||
font_pickers(app, ui, &s, ("cb-font", "cb-font-style"), (150.0, 96.0));
|
||||
size_field(app, ui, "cb-font-size", 96.0);
|
||||
@@ -907,7 +924,11 @@ pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
const SNAP_TO_GLYPH_OPTIONS: &str = "ch-snap-to-glyph-options";
|
||||
|
||||
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
attributes(app, ui);
|
||||
show_owned(app, ui, crate::font_menu::WheelOwner::Character);
|
||||
}
|
||||
|
||||
fn show_owned(app: &mut VectorcraftApp, ui: &mut Ui, owner: crate::font_menu::WheelOwner) {
|
||||
attributes(app, ui, owner);
|
||||
// Drawing over type needs no type selected, so the options show without one too.
|
||||
if pstate::<bool>(ui.ctx(), SNAP_TO_GLYPH_OPTIONS) {
|
||||
snap_to_glyph_options(app, ui);
|
||||
@@ -945,7 +966,7 @@ fn snap_to_glyph_options(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
|
||||
/// The character attributes of the type selected (an empty state without any).
|
||||
fn attributes(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
fn attributes(app: &mut VectorcraftApp, ui: &mut Ui, owner: crate::font_menu::WheelOwner) {
|
||||
let Some((s, _)) = text_style(app) else {
|
||||
app.character_focus = None;
|
||||
super::empty_state(ui, "type", tl!("No text selected"), tl!("Select a text object to edit its character attributes."));
|
||||
@@ -957,7 +978,7 @@ fn attributes(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if let Some(f) = app.character_focus {
|
||||
widgets::arm_field_focus(ui.ctx(), f.salt());
|
||||
}
|
||||
font_pickers(app, ui, &s, ("ch-font", "ch-style"), (w - 4.0, w - 4.0));
|
||||
font_pickers_owned(app, ui, &s, ("ch-font", "ch-style"), (w - 4.0, w - 4.0), Some(owner));
|
||||
variable_font(app, ui, &s, w - 4.0);
|
||||
ui.add_space(2.0);
|
||||
let hidden = pstate::<bool>(ui.ctx(), "ch-hide-options");
|
||||
|
||||
@@ -208,6 +208,9 @@ fn selected_inter(app: &mut VectorcraftApp) -> u64 {
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
let id = app.session.execute("text.create", &json!({"x": 20, "y": 50, "text": "Gagaku", "font": "Inter"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
if !crate::panel_docking::showing(&app.ui, "character") {
|
||||
app.run("window.panel", json!({"panel": "character"})).unwrap();
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
@@ -450,6 +453,34 @@ fn a_wheel_queued_for_another_document_is_discarded() {
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), before);
|
||||
}
|
||||
|
||||
/// Presentation mode hides an Appearance row's Character popup even if its state is still open.
|
||||
#[test]
|
||||
fn presentation_mode_leaves_a_transient_font_fields_wheel_available() {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
selected_inter(&mut app);
|
||||
app.run("window.panel.close", json!({"panel": "character"})).unwrap();
|
||||
app.run("window.panel.close", json!({"panel": "properties"})).unwrap();
|
||||
app.run("window.panel", json!({"panel": "appearance"})).unwrap();
|
||||
assert!(!crate::panel_docking::showing(&app.ui, "character"));
|
||||
assert!(!crate::panel_docking::showing(&app.ui, "properties"));
|
||||
assert!(crate::panel_docking::showing(&app.ui, "appearance"));
|
||||
app.ui.transient_panel = Some("character".into());
|
||||
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;
|
||||
}
|
||||
app.ui.screen_mode = 3;
|
||||
let mut raw = egui::RawInput {
|
||||
events: vec![egui::Event::PointerMoved(field.unwrap()), wheel(egui::MouseWheelUnit::Line, -1.0, Default::default())],
|
||||
..Default::default()
|
||||
};
|
||||
app.raw_input_hook(&ctx, &mut raw);
|
||||
assert!(raw.events.iter().any(|e| matches!(e, egui::Event::MouseWheel { .. })));
|
||||
}
|
||||
|
||||
/// #5253: the Properties panel and the Control bar have the same family field, and the wheel steps
|
||||
/// the families over it too.
|
||||
#[test]
|
||||
@@ -465,6 +496,7 @@ fn the_wheel_steps_the_families_in_properties_and_the_control_bar() {
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
let id = selected_inter(&mut app);
|
||||
app.ui.control_bar = true;
|
||||
let mut field = None;
|
||||
for _ in 0..3 {
|
||||
field = panel_frame(&ctx, &mut app, vec![], "Inter", &draw).0;
|
||||
|
||||
@@ -43,7 +43,7 @@ pub use drag::{
|
||||
};
|
||||
#[cfg(test)]
|
||||
use dropdowns::combo_button_id;
|
||||
pub(crate) use dropdowns::{combo, combo_menu, combo_menu_field, combo_named};
|
||||
pub(crate) use dropdowns::{combo, combo_menu, combo_menu_field, combo_named, popover_is_open};
|
||||
pub use dropdowns::{
|
||||
dropdown, dropdown_named, dropdown_names, dropdown_names_named, dropdown_with, dropdown_with_named, list_scroll_style, menu_room, menu_scroll,
|
||||
panel_scroll, popover, set_menu_floor, strip_scroll,
|
||||
|
||||
@@ -217,7 +217,7 @@ pub fn popover<R>(resp: &Response, toggle: bool, content: impl FnOnce(&mut Ui) -
|
||||
let ctx = &resp.ctx;
|
||||
let id = resp.id.with("popover");
|
||||
let frame = ctx.cumulative_frame_nr();
|
||||
let was_open = ctx.data(|d| d.get_temp::<u64>(id)).is_some_and(|f| f + 1 >= frame);
|
||||
let was_open = popover_is_open(ctx, resp.id);
|
||||
let mut open = if toggle {
|
||||
!was_open
|
||||
} else {
|
||||
@@ -246,6 +246,12 @@ pub fn popover<R>(resp: &Response, toggle: bool, content: impl FnOnce(&mut Ui) -
|
||||
inner
|
||||
}
|
||||
|
||||
/// Whether the popover for this anchor was open in the latest completed frame.
|
||||
pub(crate) fn popover_is_open(ctx: &egui::Context, anchor: egui::Id) -> bool {
|
||||
let frame = ctx.cumulative_frame_nr();
|
||||
ctx.data(|d| d.get_temp::<u64>(anchor.with("popover"))).is_some_and(|f| f + 1 >= frame)
|
||||
}
|
||||
|
||||
/// Records where the bottom bars (status and hint bars) start this frame, so menus can stop above
|
||||
/// them (#2086). Without bars it is the bottom of the window.
|
||||
pub fn set_menu_floor(ctx: &egui::Context, y: f32) {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
//! Wheel routing when a font field disappears before the next layout pass.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use egui::{Event, MouseWheelSource, MouseWheelUnit, Pos2, RawInput, Rect, Shape, TouchPhase, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_engine::Session;
|
||||
use vectorcraft_ui_egui::VectorcraftApp;
|
||||
|
||||
#[test]
|
||||
fn hiding_control_between_passes_leaves_its_old_fields_wheel_available() {
|
||||
assert!(control_wheel_available_after("window.control", json!({})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hiding_properties_preserves_the_visible_control_fields_wheel() {
|
||||
assert!(!control_wheel_available_after("window.panel.close", json!({"panel": "properties"})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deselecting_type_leaves_the_missing_control_fields_wheel_available() {
|
||||
assert!(control_wheel_available_after("select.set", json!({"ids": []})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_control_character_popover_keeps_its_visible_font_wheel() {
|
||||
let (ctx, mut app) = selected_type_with_control();
|
||||
app.run("window.panel.close", json!({"panel": "character"})).unwrap();
|
||||
let mut texts = Vec::new();
|
||||
for _ in 0..8 {
|
||||
texts = frame(&mut app, &ctx, vec![]);
|
||||
}
|
||||
let link = texts.iter().find(|(t, p)| t == "Character:" && p.y < 100.0).unwrap().1;
|
||||
let button = |pressed| Event::PointerButton { pos: link, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame(&mut app, &ctx, vec![Event::PointerMoved(link), button(true)]);
|
||||
frame(&mut app, &ctx, vec![button(false)]);
|
||||
for _ in 0..3 {
|
||||
texts = frame(&mut app, &ctx, vec![]);
|
||||
}
|
||||
let point = texts
|
||||
.iter()
|
||||
.find(|(t, p)| t == "Inter" && p.y > link.y + 10.0 && p.y < link.y + 100.0 && (p.x - link.x).abs() < 100.0)
|
||||
.expect("the Character popover shows its family field")
|
||||
.1;
|
||||
let mut raw = wheel_input(point);
|
||||
app.raw_input_hook(&ctx, &mut raw);
|
||||
assert!(!raw.events.iter().any(|e| matches!(e, Event::MouseWheel { .. })), "the visible popover keeps font wheel input");
|
||||
app.run("window.control", json!({})).unwrap();
|
||||
let mut raw = wheel_input(point);
|
||||
app.raw_input_hook(&ctx, &mut raw);
|
||||
assert!(raw.events.iter().any(|e| matches!(e, Event::MouseWheel { .. })), "hiding Control also hides its popover");
|
||||
}
|
||||
|
||||
fn selected_type_with_control() -> (egui::Context, VectorcraftApp) {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 250})).unwrap();
|
||||
let text = app.run("text.create", json!({"x": 30, "y": 70, "text": "Control wheel", "font": "Inter"})).unwrap();
|
||||
app.run("select.set", json!({"ids": [text["id"]]})).unwrap();
|
||||
app.run("window.control", json!({})).unwrap();
|
||||
(ctx, app)
|
||||
}
|
||||
|
||||
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<Event>) -> Vec<(String, Pos2)> {
|
||||
let mut raw = RawInput { events, screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1440.0, 900.0))), ..Default::default() };
|
||||
app.raw_input_hook(ctx, &mut raw);
|
||||
let mut out = ctx.run_ui(raw, |ui| app.ui(ui));
|
||||
let texts = out
|
||||
.shapes
|
||||
.iter()
|
||||
.filter_map(|s| match &s.shape {
|
||||
Shape::Text(t) => Some((t.galley.text().to_string(), t.pos + vec2(4.0, 6.0))),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
out.textures_delta.clear();
|
||||
texts
|
||||
}
|
||||
|
||||
fn control_wheel_available_after(command: &str, params: Value) -> bool {
|
||||
let (ctx, mut app) = selected_type_with_control();
|
||||
let mut texts = Vec::new();
|
||||
for _ in 0..8 {
|
||||
texts = frame(&mut app, &ctx, vec![]);
|
||||
}
|
||||
let point = texts.iter().find(|(t, p)| t == "Inter" && p.y < 150.0).expect("the Control bar's family field is rendered").1;
|
||||
app.run(command, params).unwrap();
|
||||
let mut raw = wheel_input(point);
|
||||
app.raw_input_hook(&ctx, &mut raw);
|
||||
raw.events.iter().any(|e| matches!(e, Event::MouseWheel { .. }))
|
||||
}
|
||||
|
||||
fn wheel_input(point: Pos2) -> RawInput {
|
||||
RawInput {
|
||||
events: vec![
|
||||
Event::PointerMoved(point),
|
||||
Event::MouseWheel {
|
||||
unit: MouseWheelUnit::Line,
|
||||
delta: vec2(0.0, -1.0),
|
||||
phase: TouchPhase::Move,
|
||||
modifiers: Default::default(),
|
||||
source: MouseWheelSource::Unknown,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user