diff --git a/Cargo.toml b/Cargo.toml index 03eb4da9bf8c7478fa87a7c7bc8d2b5dc91bd327..b4dc8d0b68c040697f6c8d6d3a5a35cb1e4ccca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ cosmic-text = { version = "0.11.2" } crossbeam-channel = "0.5.8" image = "0.24.6" sys-locale = "0.3.0" -unicode-segmentation = { optional = true } +unicode-segmentation = { version = "1.11.0", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] arboard = "3.2.0" diff --git a/src/buffer.rs b/src/buffer.rs index 1d32295ba20b873ad0403f7aa34f931e0f1056f6..1244e35b633c7ac39d5acee4ab042c7f576e2d05 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,6 +1,36 @@ use crate::*; use bevy::{prelude::*, window::PrimaryWindow}; +pub trait BufferExtras { + fn get_text(&self) -> String; +} + +impl BufferExtras for Buffer { + /// Retrieves the text content from a buffer. + /// + /// # Arguments + /// + /// * none, takes the rust magic ref to self + /// + /// # Returns + /// + /// A `String` containing the cosmic text content. + fn get_text(&self) -> String { + let mut text = String::new(); + let line_count = self.lines.len(); + + for (i, line) in self.lines.iter().enumerate() { + text.push_str(line.text()); + + if i < line_count - 1 { + text.push('\n'); + } + } + + text + } +} + #[derive(Component, Deref, DerefMut)] pub struct CosmicBuffer(pub Buffer); @@ -66,29 +96,6 @@ impl<'s, 'r> CosmicBuffer { self } - /// Retrieves the cosmic text content from a buffer. - /// - /// # Arguments - /// - /// * none, takes the rust magic ref to self - /// - /// # Returns - /// - /// A `String` containing the cosmic text content. - pub fn get_text(&self) -> String { - let mut text = String::new(); - let line_count = self.lines.len(); - - for (i, line) in self.lines.iter().enumerate() { - text.push_str(line.text()); - - if i < line_count - 1 { - text.push('\n'); - } - } - - text - } /// Returns texts from a MultiStyle buffer pub fn get_text_spans(&self, default_attrs: AttrsOwned) -> Vec<Vec<(String, AttrsOwned)>> { // TODO: untested! diff --git a/src/input.rs b/src/input.rs index c6862448503391938f07268b71eb4d5b3e62d052..aed352fee836d97ec6d0bc3722311f38f7930b78 100644 --- a/src/input.rs +++ b/src/input.rs @@ -20,7 +20,7 @@ use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use crate::{ - buffer::{get_x_offset_center, get_y_offset_center}, + buffer::{get_x_offset_center, get_y_offset_center, BufferExtras}, get_node_cursor_pos, CosmicBuffer, CosmicEditor, CosmicFontSystem, CosmicMaxChars, CosmicMaxLines, CosmicSource, CosmicTextChanged, CosmicTextPosition, FocusedWidget, ReadOnly, XOffset, @@ -370,7 +370,6 @@ pub fn kb_move_cursor( if !shift { editor.set_selection(Selection::None); } - return; } } } diff --git a/src/plugins/password/mod.rs b/src/plugins/password/mod.rs index d890369a2d7bc6dcde163017629b34772c96599a..7a7d045e14aff518bf59e8a3fc3b38f3795ae6cf 100644 --- a/src/plugins/password/mod.rs +++ b/src/plugins/password/mod.rs @@ -1,5 +1,6 @@ +use crate::buffer::BufferExtras; use bevy::prelude::*; -use cosmic_text::{Buffer, Edit, Shaping}; +use cosmic_text::{Cursor, Edit, Selection, Shaping}; use unicode_segmentation::UnicodeSegmentation; use crate::{ @@ -11,29 +12,20 @@ pub struct PasswordPlugin; impl Plugin for PasswordPlugin { fn build(&self, app: &mut App) { - app.add_systems( - PreUpdate, - ( - hide_password_text.before(input_mouse), - restore_password_text.after(input_mouse), - ), - ) - .add_systems( - Update, - ( - hide_password_text.before(kb_move_cursor), - restore_password_text + app.add_systems(PreUpdate, (hide_password_text.before(input_mouse),)) + .add_systems( + Update, + (restore_password_text .before(kb_input_text) - .after(kb_move_cursor), - ), - ) - .add_systems( - PostUpdate, - ( - hide_password_text.before(Render), - restore_password_text.after(Render), - ), - ); + .after(kb_move_cursor),), + ) + .add_systems( + PostUpdate, + ( + hide_password_text.before(Render), + restore_password_text.after(Render), + ), + ); } } @@ -52,22 +44,6 @@ impl Default for Password { } } -// TODO: impl this on buffer -fn get_text(buffer: &mut Buffer) -> String { - let mut text = String::new(); - let line_count = buffer.lines.len(); - - for (i, line) in buffer.lines.iter().enumerate() { - text.push_str(line.text()); - - if i < line_count - 1 { - text.push('\n'); - } - } - - text -} - fn hide_password_text( mut q: Query<( &mut Password, @@ -80,16 +56,35 @@ fn hide_password_text( for (mut password, mut buffer, attrs, editor_opt) in q.iter_mut() { if let Some(mut editor) = editor_opt { let mut cursor = editor.cursor(); + let mut selection = editor.selection(); editor.with_buffer_mut(|buffer| { - let text = get_text(buffer); - - let (pre, _post) = text.split_at(cursor.index); - - let graphemes = pre.graphemes(true).count(); - - cursor.index = graphemes * password.glyph.len_utf8(); + let text = buffer.get_text(); + + // Translate cursor to correct position for blocker glyphs + let translate_cursor = |c: &mut Cursor| { + let (pre, _post) = text.split_at(c.index); + let graphemes = pre.graphemes(true).count(); + c.index = graphemes * password.glyph.len_utf8(); + }; + + translate_cursor(&mut cursor); + + // Translate selection cursor + match selection { + Selection::None => {} + Selection::Line(ref mut c) => { + translate_cursor(c); + } + Selection::Word(ref mut c) => { + translate_cursor(c); + } + Selection::Normal(ref mut c) => { + translate_cursor(c); + } + } + // Update text to blockers buffer.set_text( &mut font_system, password @@ -105,6 +100,7 @@ fn hide_password_text( }); editor.set_cursor(cursor); + editor.set_selection(selection); continue; } @@ -132,26 +128,38 @@ fn restore_password_text( for (password, mut buffer, attrs, editor_opt) in q.iter_mut() { if let Some(mut editor) = editor_opt { let mut cursor = editor.cursor(); - let mut index = 0; + let mut selection = editor.selection(); editor.with_buffer_mut(|buffer| { - let text = get_text(buffer); - let (pre, _post) = text.split_at(cursor.index); - - let grapheme_count = pre.graphemes(true).count(); - - let mut g_idx = 0; - for (i, _c) in password.real_text.grapheme_indices(true) { - if g_idx == grapheme_count { - index = i; + let text = buffer.get_text(); + + // Find cursor position and translate back to correct position in real text + let restore_cursor = |c: &mut Cursor| { + let (pre, _post) = text.split_at(c.index); + let graphemes = pre.graphemes(true).count(); + let mut n_i = 0; + if let Some((i, _)) = password.real_text.grapheme_indices(true).nth(graphemes) { + n_i = i; + } else if c.index > 0 { + n_i = password.real_text.len(); } - g_idx += 1; - } + c.index = n_i; + }; - // TODO: save/restore with selection bounds + restore_cursor(&mut cursor); - if cursor.index > 0 && index == 0 { - index = password.real_text.len(); + // Translate selection cursor + match selection { + Selection::None => {} + Selection::Line(ref mut c) => { + restore_cursor(c); + } + Selection::Word(ref mut c) => { + restore_cursor(c); + } + Selection::Normal(ref mut c) => { + restore_cursor(c); + } } buffer.set_text( @@ -162,9 +170,8 @@ fn restore_password_text( ); }); - cursor.index = index; - editor.set_cursor(cursor); + editor.set_selection(selection); continue; } diff --git a/src/plugins/placeholder/mod.rs b/src/plugins/placeholder/mod.rs index 65d7b704b31b092676722bda9eb64a5c0c4d6678..7fa3d94bfa8650929993a641d32c8f46e1fedbf6 100644 --- a/src/plugins/placeholder/mod.rs +++ b/src/plugins/placeholder/mod.rs @@ -1,3 +1,4 @@ +use crate::buffer::BufferExtras; use bevy::prelude::*; use cosmic_text::{Attrs, Edit};