mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Only repaint on cursor movements of area, or if dragging outside (#4730)
* Closes https://github.com/emilk/egui/issues/4723 Also fix some small bugs in the touch input on web
This commit is contained in:
@@ -15,7 +15,6 @@ pub struct AppRunner {
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
last_save_time: f64,
|
||||
pub(crate) text_agent: TextAgent,
|
||||
pub(crate) mutable_text_under_cursor: bool,
|
||||
|
||||
// Output for the last run:
|
||||
textures_delta: TexturesDelta,
|
||||
@@ -121,7 +120,6 @@ impl AppRunner {
|
||||
needs_repaint,
|
||||
last_save_time: now_sec(),
|
||||
text_agent,
|
||||
mutable_text_under_cursor: false,
|
||||
textures_delta: Default::default(),
|
||||
clipped_primitives: None,
|
||||
};
|
||||
@@ -275,7 +273,7 @@ impl AppRunner {
|
||||
#[cfg(not(web_sys_unstable_apis))]
|
||||
let _ = copied_text;
|
||||
|
||||
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
||||
self.text_agent.set_focus(mutable_text_under_cursor);
|
||||
|
||||
if let Err(err) = self.text_agent.move_to(ime, self.canvas()) {
|
||||
log::error!(
|
||||
|
||||
@@ -12,10 +12,7 @@ use super::percent_decode;
|
||||
#[derive(Default)]
|
||||
pub(crate) struct WebInput {
|
||||
/// Required because we don't get a position on touched
|
||||
pub latest_touch_pos: Option<egui::Pos2>,
|
||||
|
||||
/// Required to maintain a stable touch position for multi-touch gestures.
|
||||
pub latest_touch_pos_id: Option<egui::TouchId>,
|
||||
pub primary_touch: Option<egui::TouchId>,
|
||||
|
||||
/// The raw input to `egui`.
|
||||
pub raw: egui::RawInput,
|
||||
@@ -46,8 +43,7 @@ impl WebInput {
|
||||
self.raw.modifiers = egui::Modifiers::default(); // Avoid sticky modifier keys on alt-tab:
|
||||
self.raw.focused = focused;
|
||||
self.raw.events.push(egui::Event::WindowFocused(focused));
|
||||
self.latest_touch_pos = None;
|
||||
self.latest_touch_pos_id = None;
|
||||
self.primary_touch = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -405,15 +405,24 @@ fn install_mousedown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the cursor is above the canvas, or if we're dragging something.
|
||||
fn is_interested_in_pointer_event(egui_ctx: &egui::Context, pos: egui::Pos2) -> bool {
|
||||
egui_ctx.input(|i| i.screen_rect().contains(pos) || i.pointer.any_down() || i.any_touches())
|
||||
}
|
||||
|
||||
fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "mousemove", |event: web_sys::MouseEvent, runner| {
|
||||
let modifiers = modifiers_from_mouse_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
|
||||
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
|
||||
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
|
||||
if is_interested_in_pointer_event(runner.egui_ctx(), pos) {
|
||||
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -422,29 +431,31 @@ fn install_mouseup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), J
|
||||
let modifiers = modifiers_from_mouse_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
|
||||
if let Some(button) = button_from_mouse_event(&event) {
|
||||
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
pressed: false,
|
||||
modifiers,
|
||||
});
|
||||
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
if is_interested_in_pointer_event(runner.egui_ctx(), pos) {
|
||||
if let Some(button) = button_from_mouse_event(&event) {
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
pressed: false,
|
||||
modifiers,
|
||||
});
|
||||
|
||||
runner
|
||||
.text_agent
|
||||
.set_focus(runner.mutable_text_under_cursor);
|
||||
// In Safari we are only allowed to do certain things
|
||||
// (like playing audio, start a download, etc)
|
||||
// on user action, such as a click.
|
||||
// So we need to run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
event.prevent_default();
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -466,22 +477,14 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
|
||||
target,
|
||||
"touchstart",
|
||||
|event: web_sys::TouchEvent, runner| {
|
||||
let mut latest_touch_pos_id = runner.input.latest_touch_pos_id;
|
||||
let pos = pos_from_touch_event(
|
||||
runner.canvas(),
|
||||
&event,
|
||||
&mut latest_touch_pos_id,
|
||||
runner.egui_ctx(),
|
||||
);
|
||||
runner.input.latest_touch_pos_id = latest_touch_pos_id;
|
||||
runner.input.latest_touch_pos = Some(pos);
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: true,
|
||||
modifiers,
|
||||
});
|
||||
if let Some(pos) = primary_touch_pos(runner, &event) {
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: true,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
});
|
||||
}
|
||||
|
||||
push_touches(runner, egui::TouchPhase::Start, &event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
@@ -493,47 +496,39 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
|
||||
|
||||
fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "touchmove", |event: web_sys::TouchEvent, runner| {
|
||||
let mut latest_touch_pos_id = runner.input.latest_touch_pos_id;
|
||||
let pos = pos_from_touch_event(
|
||||
runner.canvas(),
|
||||
&event,
|
||||
&mut latest_touch_pos_id,
|
||||
runner.egui_ctx(),
|
||||
);
|
||||
runner.input.latest_touch_pos_id = latest_touch_pos_id;
|
||||
runner.input.latest_touch_pos = Some(pos);
|
||||
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
|
||||
if let Some(pos) = primary_touch_pos(runner, &event) {
|
||||
if is_interested_in_pointer_event(runner.egui_ctx(), pos) {
|
||||
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
|
||||
|
||||
push_touches(runner, egui::TouchPhase::Move, &event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
push_touches(runner, egui::TouchPhase::Move, &event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "touchend", |event: web_sys::TouchEvent, runner| {
|
||||
if let Some(pos) = runner.input.latest_touch_pos {
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
// First release mouse to click:
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers,
|
||||
});
|
||||
// Then remove hover effect:
|
||||
runner.input.raw.events.push(egui::Event::PointerGone);
|
||||
if let Some(pos) = primary_touch_pos(runner, &event) {
|
||||
if is_interested_in_pointer_event(runner.egui_ctx(), pos) {
|
||||
// First release mouse to click:
|
||||
runner.input.raw.events.push(egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
});
|
||||
// Then remove hover effect:
|
||||
runner.input.raw.events.push(egui::Event::PointerGone);
|
||||
|
||||
push_touches(runner, egui::TouchPhase::End, &event);
|
||||
push_touches(runner, egui::TouchPhase::End, &event);
|
||||
|
||||
runner
|
||||
.text_agent
|
||||
.set_focus(runner.mutable_text_under_cursor);
|
||||
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
runner.needs_repaint.repaint_asap();
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,33 +27,44 @@ pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option<egui::Poin
|
||||
/// A single touch is translated to a pointer movement. When a second touch is added, the pointer
|
||||
/// should not jump to a different position. Therefore, we do not calculate the average position
|
||||
/// of all touches, but we keep using the same touch as long as it is available.
|
||||
///
|
||||
/// `touch_id_for_pos` is the [`TouchId`](egui::TouchId) of the [`Touch`](web_sys::Touch) we previously used to determine the
|
||||
/// pointer position.
|
||||
pub fn pos_from_touch_event(
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
pub fn primary_touch_pos(
|
||||
runner: &mut AppRunner,
|
||||
event: &web_sys::TouchEvent,
|
||||
touch_id_for_pos: &mut Option<egui::TouchId>,
|
||||
egui_ctx: &egui::Context,
|
||||
) -> egui::Pos2 {
|
||||
let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos {
|
||||
// search for the touch we previously used for the position
|
||||
// (unfortunately, `event.touches()` is not a rust collection):
|
||||
(0..event.touches().length())
|
||||
.map(|i| event.touches().get(i).unwrap())
|
||||
.find(|touch| egui::TouchId::from(touch.identifier()) == *touch_id_for_pos)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Use the touch found above or pick the first, or return a default position if there is no
|
||||
// touch at all. (The latter is not expected as the current method is only called when there is
|
||||
// at least one touch.)
|
||||
touch_for_pos
|
||||
.or_else(|| event.touches().get(0))
|
||||
.map_or(Default::default(), |touch| {
|
||||
*touch_id_for_pos = Some(egui::TouchId::from(touch.identifier()));
|
||||
pos_from_touch(canvas_content_rect(canvas), &touch, egui_ctx)
|
||||
})
|
||||
) -> Option<egui::Pos2> {
|
||||
let all_touches: Vec<_> = (0..event.touches().length())
|
||||
.filter_map(|i| event.touches().get(i))
|
||||
// On touchend we don't get anything in `touches`, but we still get `changed_touches`, so include those:
|
||||
.chain((0..event.changed_touches().length()).filter_map(|i| event.changed_touches().get(i)))
|
||||
.collect();
|
||||
|
||||
if let Some(primary_touch) = runner.input.primary_touch {
|
||||
// Is the primary touch is gone?
|
||||
if !all_touches
|
||||
.iter()
|
||||
.any(|touch| primary_touch == egui::TouchId::from(touch.identifier()))
|
||||
{
|
||||
runner.input.primary_touch = None;
|
||||
}
|
||||
}
|
||||
|
||||
if runner.input.primary_touch.is_none() {
|
||||
runner.input.primary_touch = all_touches
|
||||
.first()
|
||||
.map(|touch| egui::TouchId::from(touch.identifier()));
|
||||
}
|
||||
|
||||
let primary_touch = runner.input.primary_touch;
|
||||
|
||||
if let Some(primary_touch) = primary_touch {
|
||||
for touch in all_touches {
|
||||
if primary_touch == egui::TouchId::from(touch.identifier()) {
|
||||
let canvas_rect = canvas_content_rect(runner.canvas());
|
||||
return Some(pos_from_touch(canvas_rect, &touch, runner.egui_ctx()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn pos_from_touch(
|
||||
|
||||
Reference in New Issue
Block a user