Input selection constraining

Closes #113
This commit is contained in:
Jonathan Johnson 2023-12-24 08:25:48 -08:00
parent 8d3a4a42cf
commit 470267ccfe
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
2 changed files with 58 additions and 61 deletions

View file

@ -27,14 +27,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Using a `Resize` widget in the root hierarchy allows setting minimum width and Using a `Resize` widget in the root hierarchy allows setting minimum width and
heights for the content. heights for the content.
[92]: https://github.com/khonsulabs/gooey/issues/92
### Fixed ### Fixed
- A memory leak has been fixed that prevented the underlying widget tree of each - A memory leak has been fixed that prevented the underlying widget tree of each
window from being dropped. This was caused by a reference counting cycle, and window from being dropped. This was caused by a reference counting cycle, and
has been fixed by switching `MountedWidget` to use a weak reference internally has been fixed by switching `MountedWidget` to use a weak reference internally
and having the window hold the strong reference to the tree. and having the window hold the strong reference to the tree.
- [#113][113]: `Input` now constraints its internal selection to the value's
length automatically. This fixes an issue where the backspace key no longer
would work after clearing the text field by setting the `Dynamic`.
### Added ### Added
@ -59,6 +60,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Window::on_close` sets a callback to be invoked when the window has closed. - `Window::on_close` sets a callback to be invoked when the window has closed.
[91]: https://github.com/khonsulabs/gooey/issues/91 [91]: https://github.com/khonsulabs/gooey/issues/91
[92]: https://github.com/khonsulabs/gooey/issues/92
[113]: https://github.com/khonsulabs/gooey/issues/113
## v0.1.3 (2023-12-19) ## v0.1.3 (2023-12-19)

View file

@ -50,35 +50,23 @@ pub struct Input<Storage> {
window_focused: bool, window_focused: bool,
} }
struct CachedLayout { #[derive(Eq, PartialEq, Clone, Copy)]
bytes: usize, struct CacheKey {
color: Color,
generation: Generation, generation: Generation,
mask_generation: Option<Generation>, mask_generation: Option<Generation>,
placeholder_generation: Option<Generation>, placeholder_generation: Option<Generation>,
mask_bytes: usize,
width: Option<Px>, width: Option<Px>,
measured: MeasuredText<Px>, color: Color,
placeholder: MeasuredText<Px>, mask_bytes: usize,
cursor: Cursor,
selection: Option<Cursor>,
} }
impl CachedLayout { struct CachedLayout {
pub fn is_current( bytes: usize,
&self, measured: MeasuredText<Px>,
generation: Generation, placeholder: MeasuredText<Px>,
mask_generation: Option<Generation>, key: CacheKey,
placeholder_generation: Option<Generation>,
width: Option<Px>,
color: Color,
mask_bytes: usize,
) -> bool {
self.generation == generation
&& self.mask_generation == mask_generation
&& self.placeholder_generation == placeholder_generation
&& self.width == width
&& self.color == color
&& self.mask_bytes == mask_bytes
}
} }
/// The current selection of an [`Input`]. /// The current selection of an [`Input`].
@ -223,7 +211,8 @@ where
} else if cursor.offset > 0 { } else if cursor.offset > 0 {
let mut value = self.value.lock(); let mut value = self.value.lock();
let length = value.as_str().len(); let length = value.as_str().len();
if length == 0 || cursor.offset == 0 || cursor.offset > length {
if length == 0 || cursor.offset == 0 {
return; return;
} }
@ -379,7 +368,16 @@ where
self.selection.cursor = self.cursor_from_point(position, context); self.selection.cursor = self.cursor_from_point(position, context);
} }
fn constrain_selection(&mut self) {
let length = self.value.map_ref(|s| s.as_str().len());
self.selection.cursor.offset = self.selection.cursor.offset.min(length);
if let Some(start) = &mut self.selection.start {
start.offset = start.offset.min(length);
}
}
fn selected_range(&mut self) -> (Cursor, Option<Cursor>) { fn selected_range(&mut self) -> (Cursor, Option<Cursor>) {
self.constrain_selection();
match self.selection.start { match self.selection.start {
Some(start) => match start.offset.cmp(&self.selection.cursor.offset) { Some(start) => match start.offset.cmp(&self.selection.cursor.offset) {
Ordering::Less => (start, Some(self.selection.cursor)), Ordering::Less => (start, Some(self.selection.cursor)),
@ -556,25 +554,25 @@ where
width: Option<Px>, width: Option<Px>,
context: &mut GraphicsContext<'_, '_, '_, '_, '_>, context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
) -> CacheInfo<'_> { ) -> CacheInfo<'_> {
let (mut cursor, mut selection) = self.selected_range();
let generation = self.value.generation();
let mask_generation = self.mask_symbol.generation();
let placeholder_generation = self.placeholder.generation();
let mut mask_bytes = self
.mask_symbol
.map(|sym| sym.graphemes(true).next().map_or(0, str::len));
let color = context.get(&TextColor);
context.invalidate_when_changed(&self.value); context.invalidate_when_changed(&self.value);
let mut key = {
let (cursor, selection) = self.selected_range();
CacheKey {
generation: self.value.generation(),
mask_generation: self.mask_symbol.generation(),
placeholder_generation: self.placeholder.generation(),
width,
color: context.get(&TextColor),
mask_bytes: self
.mask_symbol
.map(|sym| sym.graphemes(true).next().map_or(0, str::len)),
cursor,
selection,
}
};
match &mut self.cache { match &mut self.cache {
Some(cache) Some(cache) if cache.key == key => {}
if cache.is_current(
generation,
mask_generation,
placeholder_generation,
width,
color,
mask_bytes,
) => {}
_ => { _ => {
let (bytes, measured, placeholder, ) = self.value.map_ref(|storage| { let (bytes, measured, placeholder, ) = self.value.map_ref(|storage| {
let mut text = storage.as_str(); let mut text = storage.as_str();
@ -591,9 +589,9 @@ where
// Technically something more optimal than asking the // Technically something more optimal than asking the
// layout system to lay out a repeated string should be // layout system to lay out a repeated string should be
// doable, but it seems like a lot of code. // doable, but it seems like a lot of code.
mask_bytes = first_grapheme.len(); key.mask_bytes = first_grapheme.len();
let char_count = text.graphemes(true).count(); let char_count = text.graphemes(true).count();
bytes = mask_bytes * char_count; bytes = key.mask_bytes * char_count;
self.mask.truncate(bytes); self.mask.truncate(bytes);
while self.mask.len() < bytes { while self.mask.len() < bytes {
@ -601,12 +599,12 @@ where
} }
text = &self.mask; text = &self.mask;
} else { } else {
mask_bytes = 0; key.mask_bytes = 0;
} }
}); });
context.apply_current_font_settings(); context.apply_current_font_settings();
let mut text = Text::new(text, color); let mut text = Text::new(text, key.color);
if let Some(width) = width { if let Some(width) = width {
text = text.wrap_at(width); text = text.wrap_at(width);
} }
@ -617,29 +615,25 @@ where
}); });
self.cache = Some(CachedLayout { self.cache = Some(CachedLayout {
bytes, bytes,
color,
generation,
mask_generation,
placeholder_generation,
mask_bytes,
width,
measured, measured,
placeholder, placeholder,
key,
}); });
} }
} }
// Adjust the selection cursors to accommodate the difference in unicode // Adjust the selection cursors to accommodate the difference in unicode
// widths of characters in the source string and the mask_char. // widths of characters in the source string and the mask_char.
if mask_bytes > 0 { if key.mask_bytes > 0 {
self.value.map_ref(|value| { self.value.map_ref(|value| {
let value = value.as_str(); let value = value.as_str();
assert!(cursor.offset <= value.len()); assert!(key.cursor.offset <= value.len());
cursor.offset = value[..cursor.offset].graphemes(true).count() * mask_bytes; key.cursor.offset =
if let Some(selection) = &mut selection { value[..key.cursor.offset].graphemes(true).count() * key.mask_bytes;
if let Some(selection) = &mut key.selection {
assert!(selection.offset <= value.len()); assert!(selection.offset <= value.len());
selection.offset = selection.offset =
value[..selection.offset].graphemes(true).count() * mask_bytes; value[..selection.offset].graphemes(true).count() * key.mask_bytes;
} }
}); });
} }
@ -649,9 +643,9 @@ where
measured: &cache.measured, measured: &cache.measured,
placeholder: &cache.placeholder, placeholder: &cache.placeholder,
bytes: cache.bytes, bytes: cache.bytes,
masked: mask_bytes > 0, masked: key.mask_bytes > 0,
cursor, cursor: key.cursor,
selection, selection: key.selection,
} }
} }