Support Cmd+A ^W ^U ^K and shift-click

This commit is contained in:
Emil Ernerfeldt 2020-11-15 14:21:21 +01:00
parent b920822b6b
commit fe0d159324
8 changed files with 174 additions and 77 deletions

View file

@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* You must now be explicit when creating a `TextEdit` if you want it to be singeline or multiline. * You must now be explicit when creating a `TextEdit` if you want it to be singeline or multiline.
* Improved automatic `Id` generation, making `Id` clashes less likely. * Improved automatic `Id` generation, making `Id` clashes less likely.
* Egui now requires modifier key state from the integration * Egui now requires modifier key state from the integration
* Renamed and removed some keys in the `Key` enum. * Added, renamed and removed some keys in the `Key` enum.
### Fixed 🐛 ### Fixed 🐛

15
TODO.md
View file

@ -4,7 +4,6 @@ TODO-list for the Egui project. If you looking for something to do, look here.
## Top priority ## Top priority
* Text input: text selection etc
* Refactor graphics layers and areas so one don't have to register LayerId:s. * Refactor graphics layers and areas so one don't have to register LayerId:s.
## Other ## Other
@ -18,8 +17,9 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [x] Input * [x] Input
* [x] Text focus * [x] Text focus
* [x] Cursor movement * [x] Cursor movement
* [ ] Text selection * [x] Text selection
* [ ] Clipboard copy/paste * [x] Clipboard copy/paste
* [ ] Text edit undo
* [ ] Move focus with tab * [ ] Move focus with tab
* [ ] Vertical slider * [ ] Vertical slider
* [/] Color picker * [/] Color picker
@ -43,15 +43,16 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [x] Distinguish between clicks and drags * [x] Distinguish between clicks and drags
* [x] Double-click * [x] Double-click
* [x] Text * [x] Text
* [x] Get modifier keys
* [ ] Support all mouse buttons * [ ] Support all mouse buttons
* [ ] Distinguish between touch input and mouse input * [ ] Distinguish between touch input and mouse input
* [ ] Get modifier keys * [ ] Keyboard shortcuts (copy, paste, undo, select-all, ...?)
* [ ] Keyboard shortcuts
* [ ] Copy, paste, undo, ...
* Text * Text
* [/] Unicode * [/] Unicode
* [x] Shared mutable expanding texture map * [x] Shared mutable expanding texture map
* [ ] Text editing of unicode * [/] Text editing of unicode (needs more testing)
* [ ] Font with some more unicode characters
* [ ] Emoji support (great for things like ▶️⏸⏹⚠︎)
* [ ] Change text style/color and continue in same layout * [ ] Change text style/color and continue in same layout
* Menu bar (File, Edit, etc) * Menu bar (File, Edit, etc)
* [ ] Sub-menus * [ ] Sub-menus

View file

@ -33,6 +33,9 @@ pub struct RawInput {
/// Time in seconds. Relative to whatever. Used for animations. /// Time in seconds. Relative to whatever. Used for animations.
pub time: f64, pub time: f64,
/// Which modifier keys are down at the start of the frame?
pub modifiers: Modifiers,
/// In-order events received this frame /// In-order events received this frame
pub events: Vec<Event>, pub events: Vec<Event>,
} }
@ -47,6 +50,7 @@ impl RawInput {
screen_size: self.screen_size, screen_size: self.screen_size,
pixels_per_point: self.pixels_per_point, pixels_per_point: self.pixels_per_point,
time: self.time, time: self.time,
modifiers: self.modifiers,
events: std::mem::take(&mut self.events), events: std::mem::take(&mut self.events),
} }
} }
@ -80,6 +84,9 @@ pub struct InputState {
/// Should be set to the expected time between frames when painting at vsync speeds. /// Should be set to the expected time between frames when painting at vsync speeds.
pub predicted_dt: f32, pub predicted_dt: f32,
/// Which modifier keys are down at the start of the frame?
pub modifiers: Modifiers,
/// In-order events received this frame /// In-order events received this frame
pub events: Vec<Event>, pub events: Vec<Event>,
} }
@ -200,6 +207,12 @@ pub enum Key {
PageDown, PageDown,
PageUp, PageUp,
Tab, Tab,
A, // Used for cmd+A (select All)
K, // Used for ctrl+K (delete text after cursor)
U, // Used for ctrl+U (delete text before cursor)
W, // Used for ctrl+W (delete previous word)
Z, // Used for cmd+Z (undo)
} }
impl InputState { impl InputState {
@ -214,7 +227,8 @@ impl InputState {
pixels_per_point: new.pixels_per_point.or(self.pixels_per_point), pixels_per_point: new.pixels_per_point.or(self.pixels_per_point),
time: new.time, time: new.time,
unstable_dt, unstable_dt,
predicted_dt: 1.0 / 60.0, // TODO: remove this hack predicted_dt: 1.0 / 60.0, // TODO: remove this hack
modifiers: new.modifiers,
events: new.events.clone(), // TODO: remove clone() and use raw.events events: new.events.clone(), // TODO: remove clone() and use raw.events
raw: new, raw: new,
} }
@ -357,6 +371,7 @@ impl RawInput {
screen_size, screen_size,
pixels_per_point, pixels_per_point,
time, time,
modifiers,
events, events,
} = self; } = self;
@ -371,6 +386,7 @@ impl RawInput {
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.", "Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
); );
ui.label(format!("time: {:.3} s", time)); ui.label(format!("time: {:.3} s", time));
ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("events: {:?}", events)) ui.label(format!("events: {:?}", events))
.on_hover_text("key presses etc"); .on_hover_text("key presses etc");
} }
@ -387,6 +403,7 @@ impl InputState {
time, time,
unstable_dt, unstable_dt,
predicted_dt, predicted_dt,
modifiers,
events, events,
} = self; } = self;
@ -411,6 +428,7 @@ impl InputState {
1e3 * unstable_dt 1e3 * unstable_dt
)); ));
ui.label(format!("expected dt: {:.1} ms", 1e3 * predicted_dt)); ui.label(format!("expected dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("events: {:?}", events)) ui.label(format!("events: {:?}", events))
.on_hover_text("key presses etc"); .on_hover_text("key presses etc");
} }

View file

@ -192,6 +192,10 @@ impl Row {
*self.x_offsets.last().unwrap() *self.x_offsets.last().unwrap()
} }
pub fn height(&self) -> f32 {
self.y_max - self.y_min
}
/// Closest char at the desired x coordinate. /// Closest char at the desired x coordinate.
/// Returns something in the range `[0, char_count_excluding_newline()]`. /// Returns something in the range `[0, char_count_excluding_newline()]`.
pub fn char_at(&self, desired_x: f32) -> usize { pub fn char_at(&self, desired_x: f32) -> usize {
@ -233,7 +237,7 @@ impl Galley {
fn end_pos(&self) -> Rect { fn end_pos(&self) -> Rect {
if let Some(row) = self.rows.last() { if let Some(row) = self.rows.last() {
let x = row.max_x(); let x = row.max_x();
return Rect::from_min_max(pos2(x, row.y_min), pos2(x, row.y_max)); Rect::from_min_max(pos2(x, row.y_min), pos2(x, row.y_max))
} else { } else {
// Empty galley // Empty galley
Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0)) Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
@ -636,6 +640,13 @@ impl Galley {
#[test] #[test]
fn test_text_layout() { fn test_text_layout() {
impl PartialEq for Cursor {
fn eq(&self, other: &Cursor) -> bool {
(self.ccursor, self.rcursor, self.pcursor)
== (other.ccursor, other.rcursor, other.pcursor)
}
}
use crate::mutex::Mutex; use crate::mutex::Mutex;
use crate::paint::{font::Font, *}; use crate::paint::{font::Font, *};

View file

@ -267,7 +267,15 @@ impl<'t> Widget for TextEdit<'t> {
}); });
} else if response.hovered && ui.input().mouse.pressed { } else if response.hovered && ui.input().mouse.pressed {
ui.memory().request_kb_focus(id); ui.memory().request_kb_focus(id);
state.cursorp = Some(CursorPair::one(cursor_at_mouse)); if ui.input().modifiers.shift {
if let Some(cursorp) = &mut state.cursorp {
cursorp.primary = cursor_at_mouse;
} else {
state.cursorp = Some(CursorPair::one(cursor_at_mouse));
}
} else {
state.cursorp = Some(CursorPair::one(cursor_at_mouse));
}
} else if ui.input().mouse.down && response.active { } else if ui.input().mouse.down && response.active {
if let Some(cursorp) = &mut state.cursorp { if let Some(cursorp) = &mut state.cursorp {
cursorp.primary = cursor_at_mouse; cursorp.primary = cursor_at_mouse;
@ -332,7 +340,7 @@ impl<'t> Widget for TextEdit<'t> {
&& text_to_insert != "\n" && text_to_insert != "\n"
&& text_to_insert != "\r" && text_to_insert != "\r"
{ {
let mut ccursor = delete_selected(text, &cursorp).into(); let mut ccursor = delete_selected(text, &cursorp);
insert_text(&mut ccursor, text, text_to_insert); insert_text(&mut ccursor, text, text_to_insert);
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} else { } else {
@ -345,7 +353,7 @@ impl<'t> Widget for TextEdit<'t> {
.. ..
} => { } => {
if multiline { if multiline {
let mut ccursor = delete_selected(text, &cursorp).into(); let mut ccursor = delete_selected(text, &cursorp);
insert_text(&mut ccursor, text, "\n"); insert_text(&mut ccursor, text, "\n");
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} else { } else {
@ -452,7 +460,12 @@ fn paint_cursor_selection(
let right = if ri == max.row { let right = if ri == max.row {
row.x_offset(max.column) row.x_offset(max.column)
} else { } else {
row.max_x() let newline_size = if row.ends_with_newline {
row.height() / 2.0 // visualize that we select the newline
} else {
0.0
};
row.max_x() + newline_size
}; };
let rect = Rect::from_min_max(pos + vec2(left, row.y_min), pos + vec2(right, row.y_max)); let rect = Rect::from_min_max(pos + vec2(left, row.y_min), pos + vec2(right, row.y_max));
ui.painter().rect_filled(rect, 0.0, color); ui.painter().rect_filled(rect, 0.0, color);
@ -499,7 +512,7 @@ fn byte_index_from_char_index(s: &str, char_index: usize) -> usize {
return bi; return bi;
} }
} }
return s.len(); s.len()
} }
fn insert_text(ccursor: &mut CCursor, text: &mut String, text_to_insert: &str) { fn insert_text(ccursor: &mut CCursor, text: &mut String, text_to_insert: &str) {
@ -555,12 +568,12 @@ fn delete_next_char(text: &mut String, ccursor: CCursor) -> CCursor {
} }
fn delete_previous_word(text: &mut String, max_ccursor: CCursor) -> CCursor { fn delete_previous_word(text: &mut String, max_ccursor: CCursor) -> CCursor {
let min_ccursor = ccursor_previous_word(&text, max_ccursor); let min_ccursor = ccursor_previous_word(text, max_ccursor);
delete_selected_ccursor_range(text, [min_ccursor, max_ccursor]) delete_selected_ccursor_range(text, [min_ccursor, max_ccursor])
} }
fn delete_next_word(text: &mut String, min_ccursor: CCursor) -> CCursor { fn delete_next_word(text: &mut String, min_ccursor: CCursor) -> CCursor {
let max_ccursor = ccursor_next_word(&text, min_ccursor); let max_ccursor = ccursor_next_word(text, min_ccursor);
delete_selected_ccursor_range(text, [min_ccursor, max_ccursor]) delete_selected_ccursor_range(text, [min_ccursor, max_ccursor])
} }
@ -610,10 +623,6 @@ fn on_key_press(
key: Key, key: Key,
modifiers: &Modifiers, modifiers: &Modifiers,
) -> Option<CCursorPair> { ) -> Option<CCursorPair> {
// TODO: ctrl-U to clear paragraph before the cursor
// TODO: ctrl-W to delete previous word
// TODO: cmd-A to select all
match key { match key {
Key::Backspace => { Key::Backspace => {
let ccursor = if modifiers.mac_cmd { let ccursor = if modifiers.mac_cmd {
@ -650,6 +659,31 @@ fn on_key_press(
Some(CCursorPair::one(ccursor)) Some(CCursorPair::one(ccursor))
} }
Key::A if modifiers.command => {
// select all
*cursorp = CursorPair::two(Cursor::default(), galley.end());
None
}
Key::K if modifiers.ctrl => {
let ccursor = delete_paragraph_after_cursor(text, galley, cursorp);
Some(CCursorPair::one(ccursor))
}
Key::U if modifiers.ctrl => {
let ccursor = delete_paragraph_before_cursor(text, galley, cursorp);
Some(CCursorPair::one(ccursor))
}
Key::W if modifiers.ctrl => {
let ccursor = if let Some(cursor) = cursorp.single() {
delete_previous_word(text, cursor.ccursor)
} else {
delete_selected(text, cursorp)
};
Some(CCursorPair::one(ccursor))
}
Key::ArrowLeft | Key::ArrowRight | Key::ArrowUp | Key::ArrowDown | Key::Home | Key::End => { Key::ArrowLeft | Key::ArrowRight | Key::ArrowUp | Key::ArrowDown | Key::Home | Key::End => {
move_single_cursor(&mut cursorp.primary, galley, key, modifiers); move_single_cursor(&mut cursorp.primary, galley, key, modifiers);
if !modifiers.shift { if !modifiers.shift {
@ -658,9 +692,7 @@ fn on_key_press(
None None
} }
Key::Enter | Key::Escape => unreachable!("Handled outside this function"), _ => None,
Key::Insert | Key::PageDown | Key::PageUp | Key::Tab => None,
} }
} }

View file

@ -20,7 +20,6 @@ pub use clipboard::ClipboardContext; // TODO: remove
pub struct GliumInputState { pub struct GliumInputState {
raw: egui::RawInput, raw: egui::RawInput,
modifiers: egui::Modifiers,
} }
impl GliumInputState { impl GliumInputState {
@ -30,7 +29,6 @@ impl GliumInputState {
pixels_per_point: Some(pixels_per_point), pixels_per_point: Some(pixels_per_point),
..Default::default() ..Default::default()
}, },
modifiers: Default::default(), // cmd: false,
} }
} }
} }
@ -60,7 +58,10 @@ pub fn input_to_egui(
input_state.raw.mouse_pos = None; input_state.raw.mouse_pos = None;
} }
ReceivedCharacter(ch) => { ReceivedCharacter(ch) => {
if printable_char(ch) && !input_state.modifiers.ctrl && !input_state.modifiers.mac_cmd { if printable_char(ch)
&& !input_state.raw.modifiers.ctrl
&& !input_state.raw.modifiers.mac_cmd
{
input_state.raw.events.push(Event::Text(ch.to_string())); input_state.raw.events.push(Event::Text(ch.to_string()));
} }
} }
@ -69,27 +70,27 @@ pub fn input_to_egui(
let pressed = input.state == glutin::event::ElementState::Pressed; let pressed = input.state == glutin::event::ElementState::Pressed;
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) { if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
input_state.modifiers.alt = pressed; input_state.raw.modifiers.alt = pressed;
} }
if matches!(keycode, VirtualKeyCode::LControl | VirtualKeyCode::RControl) { if matches!(keycode, VirtualKeyCode::LControl | VirtualKeyCode::RControl) {
input_state.modifiers.ctrl = pressed; input_state.raw.modifiers.ctrl = pressed;
if !cfg!(target_os = "macos") { if !cfg!(target_os = "macos") {
input_state.modifiers.command = pressed; input_state.raw.modifiers.command = pressed;
} }
} }
if matches!(keycode, VirtualKeyCode::LShift | VirtualKeyCode::RShift) { if matches!(keycode, VirtualKeyCode::LShift | VirtualKeyCode::RShift) {
input_state.modifiers.shift = pressed; input_state.raw.modifiers.shift = pressed;
} }
if cfg!(target_os = "macos") if cfg!(target_os = "macos")
&& matches!(keycode, VirtualKeyCode::LWin | VirtualKeyCode::RWin) && matches!(keycode, VirtualKeyCode::LWin | VirtualKeyCode::RWin)
{ {
input_state.modifiers.mac_cmd = pressed; input_state.raw.modifiers.mac_cmd = pressed;
input_state.modifiers.command = pressed; input_state.raw.modifiers.command = pressed;
} }
if pressed { if pressed {
if cfg!(target_os = "macos") if cfg!(target_os = "macos")
&& input_state.modifiers.mac_cmd && input_state.raw.modifiers.mac_cmd
&& keycode == VirtualKeyCode::Q && keycode == VirtualKeyCode::Q
{ {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
@ -97,11 +98,11 @@ pub fn input_to_egui(
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy, // VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
// so we detect these things manually: // so we detect these things manually:
if input_state.modifiers.command && keycode == VirtualKeyCode::X { if input_state.raw.modifiers.command && keycode == VirtualKeyCode::X {
input_state.raw.events.push(Event::Cut); input_state.raw.events.push(Event::Cut);
} else if input_state.modifiers.command && keycode == VirtualKeyCode::C { } else if input_state.raw.modifiers.command && keycode == VirtualKeyCode::C {
input_state.raw.events.push(Event::Copy); input_state.raw.events.push(Event::Copy);
} else if input_state.modifiers.command && keycode == VirtualKeyCode::V { } else if input_state.raw.modifiers.command && keycode == VirtualKeyCode::V {
if let Some(clipboard) = clipboard { if let Some(clipboard) = clipboard {
match clipboard.get_contents() { match clipboard.get_contents() {
Ok(contents) => { Ok(contents) => {
@ -116,7 +117,7 @@ pub fn input_to_egui(
input_state.raw.events.push(Event::Key { input_state.raw.events.push(Event::Key {
key, key,
pressed, pressed,
modifiers: input_state.modifiers, modifiers: input_state.raw.modifiers,
}); });
} }
} }
@ -170,6 +171,13 @@ pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> {
Back => Key::Backspace, Back => Key::Backspace,
Return => Key::Enter, Return => Key::Enter,
Tab => Key::Tab, Tab => Key::Tab,
A => Key::A,
K => Key::K,
U => Key::U,
W => Key::W,
Z => Key::Z,
_ => { _ => {
return None; return None;
} }

View file

@ -92,16 +92,18 @@ impl egui::app::TextureAllocator for webgl::Painter {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// TODO: Just use RawInput?
/// Data gathered between frames. /// Data gathered between frames.
/// Is translated to `egui::RawInput` at the start of each frame. /// Is translated to `egui::RawInput` at the start of each frame.
#[derive(Default)] #[derive(Default)]
pub struct WebInput { pub struct WebInput {
/// In native points (not same as Egui points)
pub mouse_pos: Option<egui::Pos2>, pub mouse_pos: Option<egui::Pos2>,
pub mouse_down: bool, // TODO: which button /// Is this a touch screen? If so, we ignore mouse events.
pub is_touch: bool, pub is_touch: bool,
/// In native points (not same as Egui points)
pub scroll_delta: egui::Vec2, pub scroll_delta: egui::Vec2,
pub events: Vec<egui::Event>,
pub raw: egui::RawInput,
} }
impl WebInput { impl WebInput {
@ -111,13 +113,12 @@ impl WebInput {
let scroll_delta = std::mem::take(&mut self.scroll_delta) * scale; let scroll_delta = std::mem::take(&mut self.scroll_delta) * scale;
let mouse_pos = self.mouse_pos.map(|mp| pos2(mp.x * scale, mp.y * scale)); let mouse_pos = self.mouse_pos.map(|mp| pos2(mp.x * scale, mp.y * scale));
egui::RawInput { egui::RawInput {
mouse_down: self.mouse_down,
mouse_pos, mouse_pos,
scroll_delta, scroll_delta,
screen_size: screen_size_in_native_points().unwrap() * scale, screen_size: screen_size_in_native_points().unwrap() * scale,
pixels_per_point: Some(pixels_per_point), pixels_per_point: Some(pixels_per_point),
time: now_sec(), time: now_sec(),
events: std::mem::take(&mut self.events), ..self.raw.take()
} }
} }
} }
@ -127,7 +128,7 @@ impl WebInput {
pub struct AppRunner { pub struct AppRunner {
pixels_per_point: f32, pixels_per_point: f32,
pub web_backend: WebBackend, pub web_backend: WebBackend,
pub web_input: WebInput, pub input: WebInput,
pub app: Box<dyn App>, pub app: Box<dyn App>,
pub needs_repaint: bool, // TODO: move pub needs_repaint: bool, // TODO: move
} }
@ -138,7 +139,7 @@ impl AppRunner {
Ok(Self { Ok(Self {
pixels_per_point: native_pixels_per_point(), pixels_per_point: native_pixels_per_point(),
web_backend, web_backend,
web_input: Default::default(), input: Default::default(),
app, app,
needs_repaint: true, // TODO: move needs_repaint: true, // TODO: move
}) })
@ -151,7 +152,7 @@ impl AppRunner {
pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> { pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> {
resize_canvas_to_screen_size(self.web_backend.canvas_id()); resize_canvas_to_screen_size(self.web_backend.canvas_id());
let raw_input = self.web_input.new_frame(self.pixels_per_point); let raw_input = self.input.new_frame(self.pixels_per_point);
self.web_backend.begin_frame(raw_input); self.web_backend.begin_frame(raw_input);
let mut integration_context = egui::app::IntegrationContext { let mut integration_context = egui::app::IntegrationContext {

View file

@ -195,14 +195,30 @@ fn should_ignore_key(key: &str) -> bool {
|| matches!( || matches!(
key, key,
"Alt" "Alt"
| "ArrowDown"
| "ArrowLeft"
| "ArrowRight"
| "ArrowUp"
| "Backspace"
| "CapsLock" | "CapsLock"
| "ContextMenu" | "ContextMenu"
| "Control" | "Control"
| "Delete"
| "End"
| "Enter"
| "Esc"
| "Escape"
| "Help"
| "Home"
| "Insert"
| "Meta" | "Meta"
| "NumLock" | "NumLock"
| "PageDown"
| "PageUp"
| "Pause" | "Pause"
| "ScrollLock" | "ScrollLock"
| "Shift" | "Shift"
| "Tab"
) )
} }
@ -224,6 +240,11 @@ pub fn translate_key(key: &str) -> Option<egui::Key> {
"PageDown" => Some(egui::Key::PageDown), "PageDown" => Some(egui::Key::PageDown),
"PageUp" => Some(egui::Key::PageUp), "PageUp" => Some(egui::Key::PageUp),
"Tab" => Some(egui::Key::Tab), "Tab" => Some(egui::Key::Tab),
"a" | "A" => Some(egui::Key::A),
"k" | "K" => Some(egui::Key::K),
"u" | "U" => Some(egui::Key::U),
"w" | "W" => Some(egui::Key::W),
"z" | "Z" => Some(egui::Key::Z),
_ => None, _ => None,
} }
} }
@ -271,20 +292,24 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
// https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/ // https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
return; return;
} }
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
let modifiers = modifiers_from_event(&event);
runner_lock.input.raw.modifiers = modifiers;
let key = event.key(); let key = event.key();
if let Some(key) = translate_key(&key) { if let Some(key) = translate_key(&key) {
runner_lock.web_input.events.push(egui::Event::Key { runner_lock.input.raw.events.push(egui::Event::Key {
key, key,
pressed: true, pressed: true,
modifiers: modifiers_from_event(&event), modifiers,
}); });
runner_lock.needs_repaint = true;
} else if !should_ignore_key(&key) {
runner_lock.web_input.events.push(egui::Event::Text(key));
runner_lock.needs_repaint = true;
} }
if !modifiers.ctrl && !modifiers.command && !should_ignore_key(&key) {
runner_lock.input.raw.events.push(egui::Event::Text(key));
}
runner_lock.needs_repaint = true;
}) as Box<dyn FnMut(_)>); }) as Box<dyn FnMut(_)>);
document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?; document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?;
closure.forget(); closure.forget();
@ -295,15 +320,16 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
let key = event.key(); let modifiers = modifiers_from_event(&event);
if let Some(key) = translate_key(&key) { runner_lock.input.raw.modifiers = modifiers;
runner_lock.web_input.events.push(egui::Event::Key { if let Some(key) = translate_key(&event.key()) {
runner_lock.input.raw.events.push(egui::Event::Key {
key, key,
pressed: false, pressed: false,
modifiers: modifiers_from_event(&event), modifiers,
}); });
runner_lock.needs_repaint = true;
} }
runner_lock.needs_repaint = true;
}) as Box<dyn FnMut(_)>); }) as Box<dyn FnMut(_)>);
document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?; document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?;
closure.forget(); closure.forget();
@ -346,10 +372,10 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
if !runner_lock.web_input.is_touch { if !runner_lock.input.is_touch {
runner_lock.web_input.mouse_pos = runner_lock.input.mouse_pos =
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
runner_lock.web_input.mouse_down = true; runner_lock.input.raw.mouse_down = true;
runner_lock.logic().unwrap(); // in case we get "mouseup" the same frame. TODO: handle via events instead runner_lock.logic().unwrap(); // in case we get "mouseup" the same frame. TODO: handle via events instead
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
@ -365,8 +391,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
if !runner_lock.web_input.is_touch { if !runner_lock.input.is_touch {
runner_lock.web_input.mouse_pos = runner_lock.input.mouse_pos =
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
@ -382,10 +408,10 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
if !runner_lock.web_input.is_touch { if !runner_lock.input.is_touch {
runner_lock.web_input.mouse_pos = runner_lock.input.mouse_pos =
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
runner_lock.web_input.mouse_down = false; runner_lock.input.raw.mouse_down = false;
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
@ -400,8 +426,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
if !runner_lock.web_input.is_touch { if !runner_lock.input.is_touch {
runner_lock.web_input.mouse_pos = None; runner_lock.input.mouse_pos = None;
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
@ -416,9 +442,9 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
runner_lock.web_input.is_touch = true; runner_lock.input.is_touch = true;
runner_lock.web_input.mouse_pos = Some(pos_from_touch_event(&event)); runner_lock.input.mouse_pos = Some(pos_from_touch_event(&event));
runner_lock.web_input.mouse_down = true; runner_lock.input.raw.mouse_down = true;
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
@ -432,8 +458,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
runner_lock.web_input.is_touch = true; runner_lock.input.is_touch = true;
runner_lock.web_input.mouse_pos = Some(pos_from_touch_event(&event)); runner_lock.input.mouse_pos = Some(pos_from_touch_event(&event));
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
@ -447,10 +473,10 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
runner_lock.web_input.is_touch = true; runner_lock.input.is_touch = true;
runner_lock.web_input.mouse_down = false; // First release mouse to click... runner_lock.input.raw.mouse_down = false; // First release mouse to click...
runner_lock.logic().unwrap(); // ...do the clicking... (TODO: handle via events instead) runner_lock.logic().unwrap(); // ...do the clicking... (TODO: handle via events instead)
runner_lock.web_input.mouse_pos = None; // ...remove hover effect runner_lock.input.mouse_pos = None; // ...remove hover effect
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();
@ -464,8 +490,8 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
runner_lock.web_input.scroll_delta.x -= event.delta_x() as f32; runner_lock.input.scroll_delta.x -= event.delta_x() as f32;
runner_lock.web_input.scroll_delta.y -= event.delta_y() as f32; runner_lock.input.scroll_delta.y -= event.delta_y() as f32;
runner_lock.needs_repaint = true; runner_lock.needs_repaint = true;
event.stop_propagation(); event.stop_propagation();
event.prevent_default(); event.prevent_default();