From 800db964ab2cd521b23ef47a6a7d30494b414a9a Mon Sep 17 00:00:00 2001 From: sam edelsten <samedelsten1@gmail.com> Date: Fri, 26 Apr 2024 12:59:20 +0100 Subject: [PATCH] refactor kb input, fix widechar password blockers --- examples/password.rs | 2 +- src/input.rs | 224 ++++++++++++++++++++++-------------- src/lib.rs | 9 +- src/plugins/password/mod.rs | 34 +++++- 4 files changed, 177 insertions(+), 92 deletions(-) diff --git a/examples/password.rs b/examples/password.rs index 28f92dc..5dc4d50 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -39,7 +39,7 @@ fn main() { ( change_active_editor_sprite, deselect_editor_on_esc, - print_editor_text, + print_editor_text.after(KbInput), ), ) .run(); diff --git a/src/input.rs b/src/input.rs index e0cc543..c686244 100644 --- a/src/input.rs +++ b/src/input.rs @@ -227,36 +227,20 @@ pub(crate) fn input_mouse( } } -// TODO: split copy/paste into own fn, separate fn for wasm -pub(crate) fn input_kb( +pub fn kb_move_cursor( active_editor: Res<FocusedWidget>, keys: Res<ButtonInput<KeyCode>>, - mut char_evr: EventReader<ReceivedCharacter>, - mut cosmic_edit_query: Query<( - &mut CosmicEditor, - &mut CosmicBuffer, - &CosmicMaxLines, - &CosmicMaxChars, - Entity, - Option<&ReadOnly>, - )>, - mut evw_changed: EventWriter<CosmicTextChanged>, + mut cosmic_edit_query: Query<(&mut CosmicEditor,)>, mut font_system: ResMut<CosmicFontSystem>, - mut is_deleting: Local<bool>, - _channel: Option<Res<WasmPasteAsyncChannel>>, ) { let Some(active_editor_entity) = active_editor.0 else { return; }; - - if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) = - cosmic_edit_query.get_mut(active_editor_entity) - { + if let Ok((mut editor,)) = cosmic_edit_query.get_mut(active_editor_entity) { if keys.get_just_pressed().len() != 0 { editor.cursor_visible = true; editor.cursor_timer.reset(); } - let readonly = readonly_opt.is_some(); let command = keypress_command(&keys); @@ -273,11 +257,11 @@ pub(crate) fn input_kb( command }; - let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]); - #[cfg(target_os = "macos")] let option = keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]); + let shift = keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]); + // if shift key is pressed let already_has_selection = editor.selection() != Selection::None; if shift && !already_has_selection { @@ -347,6 +331,80 @@ pub(crate) fn input_kb( } return; } + if keys.just_pressed(KeyCode::Escape) { + editor.action(&mut font_system.0, Action::Escape); + } + if command && keys.just_pressed(KeyCode::KeyA) { + editor.action(&mut font_system.0, Action::Motion(Motion::BufferEnd)); + let current_cursor = editor.cursor(); + editor.set_selection(Selection::Normal(Cursor { + line: 0, + index: 0, + affinity: current_cursor.affinity, + })); + return; + } + if keys.just_pressed(KeyCode::Home) { + editor.action(&mut font_system.0, Action::Motion(Motion::Home)); + if !shift { + editor.set_selection(Selection::None); + } + return; + } + if keys.just_pressed(KeyCode::End) { + editor.action(&mut font_system.0, Action::Motion(Motion::End)); + if !shift { + editor.set_selection(Selection::None); + } + return; + } + if keys.just_pressed(KeyCode::PageUp) { + editor.action(&mut font_system.0, Action::Motion(Motion::PageUp)); + if !shift { + editor.set_selection(Selection::None); + } + return; + } + if keys.just_pressed(KeyCode::PageDown) { + editor.action(&mut font_system.0, Action::Motion(Motion::PageDown)); + if !shift { + editor.set_selection(Selection::None); + } + return; + } + } +} + +pub(crate) fn kb_input_text( + active_editor: Res<FocusedWidget>, + keys: Res<ButtonInput<KeyCode>>, + mut char_evr: EventReader<ReceivedCharacter>, + mut cosmic_edit_query: Query<( + &mut CosmicEditor, + &mut CosmicBuffer, + &CosmicMaxLines, + &CosmicMaxChars, + Entity, + Option<&ReadOnly>, + )>, + mut evw_changed: EventWriter<CosmicTextChanged>, + mut font_system: ResMut<CosmicFontSystem>, + mut is_deleting: Local<bool>, + _channel: Option<Res<WasmPasteAsyncChannel>>, +) { + let Some(active_editor_entity) = active_editor.0 else { + return; + }; + + if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) = + cosmic_edit_query.get_mut(active_editor_entity) + { + let command = keypress_command(&keys); + if keys.get_just_pressed().len() != 0 { + editor.cursor_visible = true; + editor.cursor_timer.reset(); + } + let readonly = readonly_opt.is_some(); if keys.just_pressed(KeyCode::Backspace) & !readonly { // fix for issue #8 @@ -385,48 +443,72 @@ pub(crate) fn input_kb( editor.action(&mut font_system.0, Action::Delete); editor.with_buffer_mut(|b| b.set_redraw(true)); } - if keys.just_pressed(KeyCode::Escape) { - editor.action(&mut font_system.0, Action::Escape); - } - if command && keys.just_pressed(KeyCode::KeyA) { - editor.action(&mut font_system.0, Action::Motion(Motion::BufferEnd)); - let current_cursor = editor.cursor(); - editor.set_selection(Selection::Normal(Cursor { - line: 0, - index: 0, - affinity: current_cursor.affinity, - })); - return; - } - if keys.just_pressed(KeyCode::Home) { - editor.action(&mut font_system.0, Action::Motion(Motion::Home)); - if !shift { - editor.set_selection(Selection::None); - } + + if readonly { return; } - if keys.just_pressed(KeyCode::End) { - editor.action(&mut font_system.0, Action::Motion(Motion::End)); - if !shift { - editor.set_selection(Selection::None); + + let mut is_edit = false; + let mut is_return = false; + if keys.just_pressed(KeyCode::Enter) { + is_return = true; + if (max_lines.0 == 0 || buffer.lines.len() < max_lines.0) + && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0) + { + // to have new line on wasm rather than E + is_edit = true; + editor.action(&mut font_system.0, Action::Insert('\n')); } - return; } - if keys.just_pressed(KeyCode::PageUp) { - editor.action(&mut font_system.0, Action::Motion(Motion::PageUp)); - if !shift { - editor.set_selection(Selection::None); + + if !is_return { + for char_ev in char_evr.read() { + is_edit = true; + if *is_deleting { + editor.action(&mut font_system.0, Action::Backspace); + } else if !command && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0) { + let b = char_ev.char.as_bytes(); + for c in b { + let c: char = (*c).into(); + editor.action(&mut font_system.0, Action::Insert(c)); + } + } } - return; } - if keys.just_pressed(KeyCode::PageDown) { - editor.action(&mut font_system.0, Action::Motion(Motion::PageDown)); - if !shift { - editor.set_selection(Selection::None); - } + + if !is_edit { return; } + evw_changed.send(CosmicTextChanged((entity, buffer.get_text()))); + } +} + +pub fn kb_clipboard( + active_editor: Res<FocusedWidget>, + keys: Res<ButtonInput<KeyCode>>, + mut evw_changed: EventWriter<CosmicTextChanged>, + mut font_system: ResMut<CosmicFontSystem>, + mut cosmic_edit_query: Query<( + &mut CosmicEditor, + &mut CosmicBuffer, + &CosmicMaxLines, + &CosmicMaxChars, + Entity, + Option<&ReadOnly>, + )>, +) { + let Some(active_editor_entity) = active_editor.0 else { + return; + }; + + if let Ok((mut editor, buffer, max_lines, max_chars, entity, readonly_opt)) = + cosmic_edit_query.get_mut(active_editor_entity) + { + let command = keypress_command(&keys); + + let readonly = readonly_opt.is_some(); + let mut is_clipboard = false; #[cfg(not(target_arch = "wasm32"))] { @@ -497,39 +579,7 @@ pub(crate) fn input_kb( } } - if readonly { - return; - } - - let mut is_edit = is_clipboard; - let mut is_return = false; - if keys.just_pressed(KeyCode::Enter) { - is_return = true; - if (max_lines.0 == 0 || buffer.lines.len() < max_lines.0) - && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0) - { - // to have new line on wasm rather than E - is_edit = true; - editor.action(&mut font_system.0, Action::Insert('\n')); - } - } - - if !(is_clipboard || is_return) { - for char_ev in char_evr.read() { - is_edit = true; - if *is_deleting { - editor.action(&mut font_system.0, Action::Backspace); - } else if !command && (max_chars.0 == 0 || buffer.get_text().len() < max_chars.0) { - let b = char_ev.char.as_bytes(); - for c in b { - let c: char = (*c).into(); - editor.action(&mut font_system.0, Action::Insert(c)); - } - } - } - } - - if !is_edit { + if !is_clipboard { return; } diff --git a/src/lib.rs b/src/lib.rs index 3028487..308de2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ use cosmic_text::{Buffer, Editor, FontSystem, SwashCache}; use cursor::{change_cursor, hover_sprites, hover_ui}; pub use cursor::{TextHoverIn, TextHoverOut}; pub use focus::*; -use input::{input_kb, input_mouse, ClickTimer}; +use input::{input_mouse, kb_clipboard, kb_input_text, kb_move_cursor, ClickTimer}; #[cfg(target_arch = "wasm32")] use input::{poll_wasm_paste, WasmPaste, WasmPasteAsyncChannel}; use layout::{ @@ -240,7 +240,12 @@ impl Plugin for CosmicEditPlugin { .add_systems(PreUpdate, (input_mouse,).chain()) .add_systems( Update, - (input_kb, reshape, blink_cursor).chain().in_set(KbInput), + ( + (kb_move_cursor, kb_input_text, kb_clipboard, reshape) + .chain() + .in_set(KbInput), + blink_cursor, + ), ) .add_systems( PostUpdate, diff --git a/src/plugins/password/mod.rs b/src/plugins/password/mod.rs index 7ee6749..3c1c6df 100644 --- a/src/plugins/password/mod.rs +++ b/src/plugins/password/mod.rs @@ -2,7 +2,8 @@ use bevy::prelude::*; use cosmic_text::{Buffer, Edit, Shaping}; use crate::{ - input::input_mouse, CosmicBuffer, CosmicEditor, CosmicFontSystem, DefaultAttrs, Render, + input::{input_mouse, kb_input_text, kb_move_cursor}, + CosmicBuffer, CosmicEditor, CosmicFontSystem, DefaultAttrs, Render, }; pub struct PasswordPlugin; @@ -16,6 +17,15 @@ impl Plugin for PasswordPlugin { restore_password_text.after(input_mouse), ), ) + .add_systems( + Update, + ( + hide_password_text.before(kb_move_cursor), + restore_password_text + .before(kb_input_text) + .after(kb_move_cursor), + ), + ) .add_systems( PostUpdate, ( @@ -36,7 +46,7 @@ impl Default for Password { fn default() -> Self { Self { real_text: Default::default(), - glyph: '*', + glyph: 'â—', } } } @@ -75,13 +85,28 @@ fn hide_password_text( attrs.as_attrs(), Shaping::Advanced, ); + + for (i, c) in text.char_indices() { + if !text.is_char_boundary(i) || c.len_utf8() > 1 { + panic!("Widechars (like {c}) are not yet supported in password fields.") + } + } + password.real_text = text; }); + let mut cursor = editor.cursor(); + cursor.index *= password.glyph.len_utf8(); // HACK: multiply cursor position assuming no widechars are input + // TODO: Count characters until cursor and set new position accordingly, + // noting the previous location for restoring + // TODO: Look into unicode graphemes + editor.set_cursor(cursor); + continue; } let text = buffer.get_text(); + buffer.set_text( &mut font_system, password.glyph.to_string().repeat(text.len()).as_str(), @@ -110,6 +135,11 @@ fn restore_password_text( Shaping::Advanced, ) }); + + let mut cursor = editor.cursor(); + cursor.index /= password.glyph.len_utf8(); // HACK: restore cursor position assuming no widechars are input + editor.set_cursor(cursor); + continue; } -- GitLab