Support Cmd+A ^W ^U ^K and shift-click
This commit is contained in:
parent
b920822b6b
commit
fe0d159324
8 changed files with 174 additions and 77 deletions
|
@ -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
15
TODO.md
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, *};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue