diff --git a/Cargo.lock b/Cargo.lock index e6eb69a527706ae4d1ff815c3a2fb86e2921f005..e70356486b16ce06a2f46cda493253c2637b18fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,6 +416,7 @@ dependencies = [ "insta", "js-sys", "sys-locale", + "unicode-segmentation", "util", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/Cargo.toml b/Cargo.toml index a8e2e6ecc21a18279dc3677cf6befb4293767841..03eb4da9bf8c7478fa87a7c7bc8d2b5dc91bd327 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ exclude = ["assets/*"] [features] multicam = [] placeholder = [] -password = [] +password = ["unicode-segmentation"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -37,6 +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 } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] arboard = "3.2.0" diff --git a/src/plugins/password/mod.rs b/src/plugins/password/mod.rs index 3c1c6dfb37f28bcfe10603664a79602c46681361..d890369a2d7bc6dcde163017629b34772c96599a 100644 --- a/src/plugins/password/mod.rs +++ b/src/plugins/password/mod.rs @@ -1,5 +1,6 @@ use bevy::prelude::*; use cosmic_text::{Buffer, Edit, Shaping}; +use unicode_segmentation::UnicodeSegmentation; use crate::{ input::{input_mouse, kb_input_text, kb_move_cursor}, @@ -51,6 +52,22 @@ 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, @@ -62,44 +79,31 @@ 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(); + editor.with_buffer_mut(|buffer| { - fn get_text(buffer: &mut Buffer) -> String { - let mut text = String::new(); - let line_count = buffer.lines.len(); + let text = get_text(buffer); - for (i, line) in buffer.lines.iter().enumerate() { - text.push_str(line.text()); + let (pre, _post) = text.split_at(cursor.index); - if i < line_count - 1 { - text.push('\n'); - } - } + let graphemes = pre.graphemes(true).count(); - text - } + cursor.index = graphemes * password.glyph.len_utf8(); - let text = get_text(buffer); buffer.set_text( &mut font_system, - password.glyph.to_string().repeat(text.len()).as_str(), + password + .glyph + .to_string() + .repeat(text.graphemes(true).count()) + .as_str(), 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; @@ -127,17 +131,39 @@ 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; + 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; + } + g_idx += 1; + } + + // TODO: save/restore with selection bounds + + if cursor.index > 0 && index == 0 { + index = password.real_text.len(); + } + buffer.set_text( &mut font_system, password.real_text.as_str(), attrs.as_attrs(), Shaping::Advanced, - ) + ); }); - let mut cursor = editor.cursor(); - cursor.index /= password.glyph.len_utf8(); // HACK: restore cursor position assuming no widechars are input + cursor.index = index; + editor.set_cursor(cursor); continue;