mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 23:00:04 -04:00
Add modifier keys and implement moving cursors one word at a time
This commit is contained in:
@@ -151,7 +151,7 @@ impl Default for MouseInput {
|
||||
}
|
||||
|
||||
/// An input event. Only covers events used by Egui.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Event {
|
||||
Copy,
|
||||
Cut,
|
||||
@@ -161,33 +161,45 @@ pub enum Event {
|
||||
Key {
|
||||
key: Key,
|
||||
pressed: bool,
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
}
|
||||
|
||||
/// Keyboard key name. Only covers keys used by Egui.
|
||||
/// State of the modifier keys. These must be fed to Egui.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Modifiers {
|
||||
/// Either of the alt keys are down (option ⌥ on Mac)
|
||||
pub alt: bool,
|
||||
/// Either of the control keys are down
|
||||
pub ctrl: bool,
|
||||
/// Either of the shift keys are down
|
||||
pub shift: bool,
|
||||
/// The Mac ⌘ Command key. Should always be set to `false` on other platforms.
|
||||
pub mac_cmd: bool,
|
||||
/// On Mac, this should be set whenever one of the ⌘ Command keys are down (same as `mac_cmd`).
|
||||
/// On Windows and Linux, set this to the same value as `ctrl`.
|
||||
/// This is so that Egui can, for instance, select all text by checking for `command + A`
|
||||
/// and it will work on both Mac and Windows.
|
||||
pub command: bool,
|
||||
}
|
||||
|
||||
/// Keyboard key name. Only covers keys used by Egui (mostly for text editing).
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Key {
|
||||
Alt,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
Backspace,
|
||||
Control,
|
||||
Delete,
|
||||
Down,
|
||||
End,
|
||||
Enter,
|
||||
Escape,
|
||||
Home,
|
||||
Insert,
|
||||
Left,
|
||||
/// Windows key or Mac Command key
|
||||
Logo,
|
||||
PageDown,
|
||||
PageUp,
|
||||
/// Enter/Return key
|
||||
Enter,
|
||||
Right,
|
||||
Shift,
|
||||
// Space,
|
||||
Tab,
|
||||
Up,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
@@ -227,7 +239,8 @@ impl InputState {
|
||||
event,
|
||||
Event::Key {
|
||||
key,
|
||||
pressed: true
|
||||
pressed: true,
|
||||
..
|
||||
} if *key == desired_key
|
||||
)
|
||||
})
|
||||
@@ -240,7 +253,8 @@ impl InputState {
|
||||
event,
|
||||
Event::Key {
|
||||
key,
|
||||
pressed: false
|
||||
pressed: false,
|
||||
..
|
||||
} if *key == desired_key
|
||||
)
|
||||
})
|
||||
|
||||
@@ -621,6 +621,39 @@ impl Galley {
|
||||
column: self.rows[cursor.rcursor.row].char_count_excluding_newline(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_next_word(&self, cursor: &Cursor) -> Cursor {
|
||||
self.from_ccursor(CCursor {
|
||||
index: next_word(self.text.chars(), cursor.ccursor.index),
|
||||
prefer_next_row: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor_previous_word(&self, cursor: &Cursor) -> Cursor {
|
||||
let num_chars = self.text.chars().count();
|
||||
self.from_ccursor(CCursor {
|
||||
index: num_chars - next_word(self.text.chars().rev(), num_chars - cursor.ccursor.index),
|
||||
prefer_next_row: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn next_word(it: impl Iterator<Item = char>, mut index: usize) -> usize {
|
||||
let mut it = it.skip(index);
|
||||
if let Some(_first) = it.next() {
|
||||
index += 1;
|
||||
|
||||
if let Some(second) = it.next() {
|
||||
index += 1;
|
||||
for next in it {
|
||||
if next.is_alphabetic() != second.is_alphabetic() {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -176,7 +176,7 @@ impl<'t> Widget for TextEdit<'t> {
|
||||
} else {
|
||||
Sense::nothing()
|
||||
};
|
||||
let response = ui.interact(rect, id, sense); // TODO: implement drag-select
|
||||
let response = ui.interact(rect, id, sense);
|
||||
|
||||
if response.clicked && enabled {
|
||||
ui.memory().request_kb_focus(id);
|
||||
@@ -228,6 +228,7 @@ impl<'t> Widget for TextEdit<'t> {
|
||||
Event::Key {
|
||||
key: Key::Enter,
|
||||
pressed: true,
|
||||
..
|
||||
} => {
|
||||
if multiline {
|
||||
let mut ccursor = cursor.ccursor;
|
||||
@@ -242,13 +243,16 @@ impl<'t> Widget for TextEdit<'t> {
|
||||
Event::Key {
|
||||
key: Key::Escape,
|
||||
pressed: true,
|
||||
..
|
||||
} => {
|
||||
ui.memory().surrender_kb_focus(id);
|
||||
break;
|
||||
}
|
||||
Event::Key { key, pressed: true } => {
|
||||
on_key_press(&mut cursor, text, &galley, *key)
|
||||
}
|
||||
Event::Key {
|
||||
key,
|
||||
pressed: true,
|
||||
modifiers,
|
||||
} => on_key_press(&mut cursor, text, &galley, *key, modifiers),
|
||||
Event::Key { .. } => None,
|
||||
};
|
||||
|
||||
@@ -334,18 +338,35 @@ fn on_key_press(
|
||||
text: &mut String,
|
||||
galley: &Galley,
|
||||
key: Key,
|
||||
modifiers: &Modifiers,
|
||||
) -> Option<CCursor> {
|
||||
// TODO: cursor position preview on mouse hover
|
||||
// TODO: drag-select
|
||||
// TODO: double-click to select whole word
|
||||
// TODO: triple-click to select whole paragraph
|
||||
// TODO: drag selected text to either move or clone (ctrl on windows, alt on mac)
|
||||
// TODO: ctrl-U to clear paragraph before the cursor
|
||||
// TODO: ctrl-W to delete previous word
|
||||
// TODO: alt/ctrl + backspace to delete previous word (alt on mac, ctrl on windows)
|
||||
// TODO: alt/ctrl + delete to delete next word (alt on mac, ctrl on windows)
|
||||
// TODO: cmd-A to select all
|
||||
// TODO: shift modifier to only move half of the cursor to select things
|
||||
|
||||
match key {
|
||||
Key::Backspace if cursor.ccursor.index > 0 => {
|
||||
*cursor = galley.from_ccursor(cursor.ccursor - 1);
|
||||
let mut char_it = text.chars();
|
||||
let mut new_text = String::with_capacity(text.capacity());
|
||||
for _ in 0..cursor.ccursor.index {
|
||||
new_text.push(char_it.next().unwrap())
|
||||
Key::Backspace => {
|
||||
if cursor.ccursor.index > 0 {
|
||||
*cursor = galley.from_ccursor(cursor.ccursor - 1);
|
||||
let mut char_it = text.chars();
|
||||
let mut new_text = String::with_capacity(text.capacity());
|
||||
for _ in 0..cursor.ccursor.index {
|
||||
new_text.push(char_it.next().unwrap())
|
||||
}
|
||||
new_text.extend(char_it.skip(1));
|
||||
*text = new_text;
|
||||
Some(cursor.ccursor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
new_text.extend(char_it.skip(1));
|
||||
*text = new_text;
|
||||
Some(cursor.ccursor)
|
||||
}
|
||||
Key::Delete => {
|
||||
let mut char_it = text.chars();
|
||||
@@ -357,32 +378,69 @@ fn on_key_press(
|
||||
*text = new_text;
|
||||
Some(cursor.ccursor)
|
||||
}
|
||||
Key::Enter => unreachable!("Should have been handled earlier"),
|
||||
|
||||
Key::ArrowLeft => {
|
||||
if modifiers.alt || modifiers.ctrl {
|
||||
// alt on mac, ctrl on windows
|
||||
*cursor = galley.cursor_previous_word(cursor);
|
||||
} else if modifiers.mac_cmd {
|
||||
*cursor = galley.cursor_begin_of_row(cursor);
|
||||
} else {
|
||||
*cursor = galley.cursor_left_one_character(cursor);
|
||||
}
|
||||
None
|
||||
}
|
||||
Key::ArrowRight => {
|
||||
if modifiers.alt || modifiers.ctrl {
|
||||
// alt on mac, ctrl on windows
|
||||
*cursor = galley.cursor_next_word(cursor);
|
||||
} else if modifiers.mac_cmd {
|
||||
*cursor = galley.cursor_end_of_row(cursor);
|
||||
} else {
|
||||
*cursor = galley.cursor_right_one_character(cursor);
|
||||
}
|
||||
None
|
||||
}
|
||||
Key::ArrowUp => {
|
||||
if modifiers.command {
|
||||
// mac and windows behavior
|
||||
*cursor = Cursor::default();
|
||||
} else {
|
||||
*cursor = galley.cursor_up_one_row(cursor);
|
||||
}
|
||||
None
|
||||
}
|
||||
Key::ArrowDown => {
|
||||
if modifiers.command {
|
||||
// mac and windows behavior
|
||||
*cursor = galley.end();
|
||||
} else {
|
||||
*cursor = galley.cursor_down_one_row(cursor);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Key::Home => {
|
||||
*cursor = galley.cursor_begin_of_row(cursor);
|
||||
if modifiers.ctrl {
|
||||
// windows behavior
|
||||
*cursor = Cursor::default();
|
||||
} else {
|
||||
*cursor = galley.cursor_begin_of_row(cursor);
|
||||
}
|
||||
None
|
||||
}
|
||||
Key::End => {
|
||||
*cursor = galley.cursor_end_of_row(cursor);
|
||||
if modifiers.ctrl {
|
||||
// windows behavior
|
||||
*cursor = galley.end();
|
||||
} else {
|
||||
*cursor = galley.cursor_end_of_row(cursor);
|
||||
}
|
||||
None
|
||||
}
|
||||
Key::Left => {
|
||||
*cursor = galley.cursor_left_one_character(cursor);
|
||||
None
|
||||
}
|
||||
Key::Right => {
|
||||
*cursor = galley.cursor_right_one_character(cursor);
|
||||
None
|
||||
}
|
||||
Key::Up => {
|
||||
*cursor = galley.cursor_up_one_row(cursor);
|
||||
None
|
||||
}
|
||||
Key::Down => {
|
||||
*cursor = galley.cursor_down_one_row(cursor);
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
|
||||
Key::Enter | Key::Escape => unreachable!("Handled outside this function"),
|
||||
|
||||
Key::Insert | Key::PageDown | Key::PageUp | Key::Tab => None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user