mirror of
https://github.com/danbulant/cushy
synced 2026-07-08 12:40:36 +00:00
parent
470267ccfe
commit
443c5b40d1
2 changed files with 90 additions and 54 deletions
|
|
@ -33,6 +33,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
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.
|
||||||
|
- [#112][112]: Click-selection is handled correctly across graphemes now.
|
||||||
|
Previously, code that was handling selecting between "ff" where cosmic_text
|
||||||
|
had merged the two ASCII characters into a single glpyh was not honoring
|
||||||
|
graphemes, allowing dragging selections inbetween multi-character glyphs.
|
||||||
- [#113][113]: `Input` now constraints its internal selection to the value's
|
- [#113][113]: `Input` now constraints its internal selection to the value's
|
||||||
length automatically. This fixes an issue where the backspace key no longer
|
length automatically. This fixes an issue where the backspace key no longer
|
||||||
would work after clearing the text field by setting the `Dynamic`.
|
would work after clearing the text field by setting the `Dynamic`.
|
||||||
|
|
@ -61,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
[91]: https://github.com/khonsulabs/gooey/issues/91
|
[91]: https://github.com/khonsulabs/gooey/issues/91
|
||||||
[92]: https://github.com/khonsulabs/gooey/issues/92
|
[92]: https://github.com/khonsulabs/gooey/issues/92
|
||||||
|
[112]: https://github.com/khonsulabs/gooey/issues/112
|
||||||
[113]: https://github.com/khonsulabs/gooey/issues/113
|
[113]: https://github.com/khonsulabs/gooey/issues/113
|
||||||
|
|
||||||
## v0.1.3 (2023-12-19)
|
## v0.1.3 (2023-12-19)
|
||||||
|
|
|
||||||
|
|
@ -324,8 +324,7 @@ where
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut position, _) =
|
let (mut position, _) = self.point_from_cursor(cache, self.selection.cursor, cache.bytes);
|
||||||
Self::point_from_cursor(&cache.measured, self.selection.cursor, cache.bytes);
|
|
||||||
position.y += context
|
position.y += context
|
||||||
.get(&IntrinsicPadding)
|
.get(&IntrinsicPadding)
|
||||||
.into_px(context.kludgine.scale())
|
.into_px(context.kludgine.scale())
|
||||||
|
|
@ -345,8 +344,7 @@ where
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut position, _) =
|
let (mut position, _) = self.point_from_cursor(&cache, self.selection.cursor, cache.bytes);
|
||||||
Self::point_from_cursor(&cache.measured, self.selection.cursor, cache.bytes);
|
|
||||||
position += Point::squared(
|
position += Point::squared(
|
||||||
context
|
context
|
||||||
.get(&IntrinsicPadding)
|
.get(&IntrinsicPadding)
|
||||||
|
|
@ -553,7 +551,7 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
width: Option<Px>,
|
width: Option<Px>,
|
||||||
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
context: &mut GraphicsContext<'_, '_, '_, '_, '_>,
|
||||||
) -> CacheInfo<'_> {
|
) {
|
||||||
context.invalidate_when_changed(&self.value);
|
context.invalidate_when_changed(&self.value);
|
||||||
|
|
||||||
let mut key = {
|
let mut key = {
|
||||||
|
|
@ -621,41 +619,51 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_info(&self) -> CacheInfo<'_> {
|
||||||
|
let cache = self
|
||||||
|
.cache
|
||||||
|
.as_ref()
|
||||||
|
.expect("always called after layout_text");
|
||||||
|
|
||||||
// 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 key.mask_bytes > 0 {
|
|
||||||
|
let masked = cache.key.mask_bytes > 0;
|
||||||
|
let mut cursor = cache.key.cursor;
|
||||||
|
let mut selection = cache.key.selection;
|
||||||
|
if masked {
|
||||||
self.value.map_ref(|value| {
|
self.value.map_ref(|value| {
|
||||||
let value = value.as_str();
|
let value = value.as_str();
|
||||||
assert!(key.cursor.offset <= value.len());
|
assert!(cache.key.cursor.offset <= value.len());
|
||||||
key.cursor.offset =
|
cursor.offset =
|
||||||
value[..key.cursor.offset].graphemes(true).count() * key.mask_bytes;
|
value[..cache.key.cursor.offset].graphemes(true).count() * cache.key.mask_bytes;
|
||||||
if let Some(selection) = &mut key.selection {
|
if let Some(selection) = &mut selection {
|
||||||
assert!(selection.offset <= value.len());
|
assert!(selection.offset <= value.len());
|
||||||
selection.offset =
|
selection.offset =
|
||||||
value[..selection.offset].graphemes(true).count() * key.mask_bytes;
|
value[..selection.offset].graphemes(true).count() * cache.key.mask_bytes;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache = self.cache.as_ref().expect("always initialized");
|
|
||||||
CacheInfo {
|
CacheInfo {
|
||||||
measured: &cache.measured,
|
cache,
|
||||||
placeholder: &cache.placeholder,
|
masked,
|
||||||
bytes: cache.bytes,
|
cursor,
|
||||||
masked: key.mask_bytes > 0,
|
selection,
|
||||||
cursor: key.cursor,
|
|
||||||
selection: key.selection,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)] // it's text layout, c'mon
|
#[allow(clippy::too_many_lines)] // it's text layout, c'mon
|
||||||
fn point_from_cursor(
|
fn point_from_cursor(
|
||||||
measured: &MeasuredText<Px>,
|
&self,
|
||||||
|
cache: &CachedLayout,
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
total_bytes: usize,
|
total_bytes: usize,
|
||||||
) -> (Point<Px>, Px) {
|
) -> (Point<Px>, Px) {
|
||||||
if measured.glyphs.is_empty() || (cursor.offset == 0 && cursor.affinity == Affinity::Before)
|
if cache.measured.glyphs.is_empty()
|
||||||
|
|| (cursor.offset == 0 && cursor.affinity == Affinity::Before)
|
||||||
{
|
{
|
||||||
return (Point::default(), Px::ZERO);
|
return (Point::default(), Px::ZERO);
|
||||||
}
|
}
|
||||||
|
|
@ -669,7 +677,7 @@ where
|
||||||
let mut bottom_right_line = 0;
|
let mut bottom_right_line = 0;
|
||||||
let mut bottom_right_rect = Rect::default();
|
let mut bottom_right_rect = Rect::default();
|
||||||
let mut unrendered_offset = 0;
|
let mut unrendered_offset = 0;
|
||||||
for (index, glyph) in measured.glyphs.iter().enumerate() {
|
for (index, glyph) in cache.measured.glyphs.iter().enumerate() {
|
||||||
unrendered_offset = unrendered_offset.max(glyph.info.end);
|
unrendered_offset = unrendered_offset.max(glyph.info.end);
|
||||||
let rect = glyph.rect();
|
let rect = glyph.rect();
|
||||||
if bottom_right_rect.size.width == 0
|
if bottom_right_rect.size.width == 0
|
||||||
|
|
@ -692,17 +700,30 @@ where
|
||||||
if glyph.info.start < cursor.offset {
|
if glyph.info.start < cursor.offset {
|
||||||
let clustered_bytes = glyph.info.end - glyph.info.start;
|
let clustered_bytes = glyph.info.end - glyph.info.start;
|
||||||
if clustered_bytes > 1 {
|
if clustered_bytes > 1 {
|
||||||
let cursor_offset = cursor.offset - glyph.info.start;
|
let clustered_graphemes = if cache.key.mask_bytes > 0 {
|
||||||
|
self.mask[glyph.info.start..glyph.info.end]
|
||||||
|
.graphemes(true)
|
||||||
|
.count()
|
||||||
|
} else {
|
||||||
|
self.value.map_ref(|value| {
|
||||||
|
value.as_str()[glyph.info.start..glyph.info.end]
|
||||||
|
.graphemes(true)
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if clustered_graphemes > 1 {
|
||||||
|
let cursor_offset = cursor.offset - glyph.info.start;
|
||||||
|
|
||||||
grapheme_offset = rect.size.width * cursor_offset.cast::<f32>()
|
grapheme_offset = rect.size.width * cursor_offset.cast::<f32>()
|
||||||
/ clustered_bytes.cast::<f32>();
|
/ clustered_graphemes.cast::<f32>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Point::new(
|
Point::new(
|
||||||
rect.origin.x + grapheme_offset,
|
rect.origin.x + grapheme_offset,
|
||||||
measured.line_height.saturating_mul(Px::new(
|
cache.measured.line_height.saturating_mul(Px::new(
|
||||||
i32::try_from(glyph.info.line).unwrap_or(i32::MAX),
|
i32::try_from(glyph.info.line).unwrap_or(i32::MAX),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
|
@ -720,8 +741,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if closest_after_index == usize::MAX {
|
if closest_after_index == usize::MAX {
|
||||||
let bottom_right = &measured.glyphs[bottom_right_index];
|
let bottom_right = &cache.measured.glyphs[bottom_right_index];
|
||||||
let bottom_y = measured.line_height.saturating_mul(Px::new(
|
let bottom_y = cache.measured.line_height.saturating_mul(Px::new(
|
||||||
i32::try_from(bottom_right.info.line).unwrap_or(i32::MAX),
|
i32::try_from(bottom_right.info.line).unwrap_or(i32::MAX),
|
||||||
));
|
));
|
||||||
// No glyph could be found that started/contained the cursors offset.
|
// No glyph could be found that started/contained the cursors offset.
|
||||||
|
|
@ -746,11 +767,12 @@ where
|
||||||
// The cursor should be placed after the bottom_right glyph
|
// The cursor should be placed after the bottom_right glyph
|
||||||
(bottom_right_cursor, Px::ZERO)
|
(bottom_right_cursor, Px::ZERO)
|
||||||
} else {
|
} else {
|
||||||
let before = &measured.glyphs[closest_before_index];
|
let before = &cache.measured.glyphs[closest_before_index];
|
||||||
let after = &measured.glyphs[closest_after_index];
|
let after = &cache.measured.glyphs[closest_after_index];
|
||||||
let before_rect = before.rect();
|
let before_rect = before.rect();
|
||||||
let after_rect = after.rect();
|
let after_rect = after.rect();
|
||||||
let before_y = measured
|
let before_y = cache
|
||||||
|
.measured
|
||||||
.line_height
|
.line_height
|
||||||
.saturating_mul(Px::new(i32::try_from(before.info.line).unwrap_or(i32::MAX)));
|
.saturating_mul(Px::new(i32::try_from(before.info.line).unwrap_or(i32::MAX)));
|
||||||
|
|
||||||
|
|
@ -775,7 +797,7 @@ where
|
||||||
(origin, before_y)
|
(origin, before_y)
|
||||||
}
|
}
|
||||||
Affinity::After => (
|
Affinity::After => (
|
||||||
Point::new(Px::ZERO, before_y + measured.line_height),
|
Point::new(Px::ZERO, before_y + cache.measured.line_height),
|
||||||
Px::ZERO,
|
Px::ZERO,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -916,9 +938,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CacheInfo<'a> {
|
struct CacheInfo<'a> {
|
||||||
measured: &'a MeasuredText<Px>,
|
cache: &'a CachedLayout,
|
||||||
placeholder: &'a MeasuredText<Px>,
|
|
||||||
bytes: usize,
|
|
||||||
masked: bool,
|
masked: bool,
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
selection: Option<Cursor>,
|
selection: Option<Cursor>,
|
||||||
|
|
@ -1031,7 +1051,8 @@ where
|
||||||
.round();
|
.round();
|
||||||
let padding = Point::squared(padding);
|
let padding = Point::squared(padding);
|
||||||
|
|
||||||
let cache = self.layout_text(Some(size.width.into_signed()), context);
|
self.layout_text(Some(size.width.into_signed()), context);
|
||||||
|
let info = self.cache_info();
|
||||||
|
|
||||||
let highlight = if context.focused(false) && window_focused {
|
let highlight = if context.focused(false) && window_focused {
|
||||||
context.draw_focus_ring();
|
context.draw_focus_ring();
|
||||||
|
|
@ -1044,30 +1065,33 @@ where
|
||||||
|
|
||||||
if context.focused(false) {
|
if context.focused(false) {
|
||||||
context.set_ime_allowed(true);
|
context.set_ime_allowed(true);
|
||||||
context.set_ime_purpose(if cache.masked {
|
context.set_ime_purpose(if info.masked {
|
||||||
ImePurpose::Password
|
ImePurpose::Password
|
||||||
} else {
|
} else {
|
||||||
ImePurpose::Normal
|
ImePurpose::Normal
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(selection) = cache.selection {
|
if let Some(selection) = info.selection {
|
||||||
let (start, end) = if selection < cache.cursor {
|
let (start, end) = if selection < info.cursor {
|
||||||
(selection, cache.cursor)
|
(selection, info.cursor)
|
||||||
} else {
|
} else {
|
||||||
(cache.cursor, selection)
|
(info.cursor, selection)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (start_position, _) =
|
let (start_position, _) =
|
||||||
Self::point_from_cursor(cache.measured, start, cache.bytes);
|
self.point_from_cursor(info.cache, start, info.cache.bytes);
|
||||||
let (end_position, end_width) =
|
let (end_position, end_width) =
|
||||||
Self::point_from_cursor(cache.measured, end, cache.bytes);
|
self.point_from_cursor(info.cache, end, info.cache.bytes);
|
||||||
|
|
||||||
if start_position.y == end_position.y {
|
if start_position.y == end_position.y {
|
||||||
// Single line selection
|
// Single line selection
|
||||||
let width = end_position.x - start_position.x;
|
let width = end_position.x - start_position.x;
|
||||||
context.gfx.draw_shape(
|
context.gfx.draw_shape(
|
||||||
Shape::filled_rect(
|
Shape::filled_rect(
|
||||||
Rect::new(start_position, Size::new(width, cache.measured.line_height)),
|
Rect::new(
|
||||||
|
start_position,
|
||||||
|
Size::new(width, info.cache.measured.line_height),
|
||||||
|
),
|
||||||
highlight,
|
highlight,
|
||||||
)
|
)
|
||||||
.translate_by(padding),
|
.translate_by(padding),
|
||||||
|
|
@ -1077,13 +1101,16 @@ where
|
||||||
let width = size.width.into_signed() - start_position.x;
|
let width = size.width.into_signed() - start_position.x;
|
||||||
context.gfx.draw_shape(
|
context.gfx.draw_shape(
|
||||||
Shape::filled_rect(
|
Shape::filled_rect(
|
||||||
Rect::new(start_position, Size::new(width, cache.measured.line_height)),
|
Rect::new(
|
||||||
|
start_position,
|
||||||
|
Size::new(width, info.cache.measured.line_height),
|
||||||
|
),
|
||||||
highlight,
|
highlight,
|
||||||
)
|
)
|
||||||
.translate_by(padding),
|
.translate_by(padding),
|
||||||
);
|
);
|
||||||
// Fill region between
|
// Fill region between
|
||||||
let bottom_of_first_line = start_position.y + cache.measured.line_height;
|
let bottom_of_first_line = start_position.y + info.cache.measured.line_height;
|
||||||
let distance_between = end_position.y - bottom_of_first_line;
|
let distance_between = end_position.y - bottom_of_first_line;
|
||||||
if distance_between > 0 {
|
if distance_between > 0 {
|
||||||
context.gfx.draw_shape(
|
context.gfx.draw_shape(
|
||||||
|
|
@ -1102,7 +1129,10 @@ where
|
||||||
Shape::filled_rect(
|
Shape::filled_rect(
|
||||||
Rect::new(
|
Rect::new(
|
||||||
Point::new(Px::ZERO, end_position.y),
|
Point::new(Px::ZERO, end_position.y),
|
||||||
Size::new(end_position.x + end_width, cache.measured.line_height),
|
Size::new(
|
||||||
|
end_position.x + end_width,
|
||||||
|
info.cache.measured.line_height,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
highlight,
|
highlight,
|
||||||
)
|
)
|
||||||
|
|
@ -1111,14 +1141,14 @@ where
|
||||||
}
|
}
|
||||||
} else if window_focused && context.enabled() {
|
} else if window_focused && context.enabled() {
|
||||||
let (location, _) =
|
let (location, _) =
|
||||||
Self::point_from_cursor(cache.measured, cache.cursor, cache.bytes);
|
self.point_from_cursor(info.cache, info.cursor, info.cache.bytes);
|
||||||
if cursor_state.visible {
|
if cursor_state.visible {
|
||||||
let cursor_width = Lp::points(2).into_px(context.gfx.scale());
|
let cursor_width = Lp::points(2).into_px(context.gfx.scale());
|
||||||
context.gfx.draw_shape(
|
context.gfx.draw_shape(
|
||||||
Shape::filled_rect(
|
Shape::filled_rect(
|
||||||
Rect::new(
|
Rect::new(
|
||||||
Point::new(location.x - cursor_width / 2, location.y),
|
Point::new(location.x - cursor_width / 2, location.y),
|
||||||
Size::new(cursor_width, cache.measured.line_height),
|
Size::new(cursor_width, info.cache.measured.line_height),
|
||||||
),
|
),
|
||||||
highlight,
|
highlight,
|
||||||
)
|
)
|
||||||
|
|
@ -1131,10 +1161,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = if cache.bytes > 0 {
|
let text = if info.cache.bytes > 0 {
|
||||||
cache.measured
|
&info.cache.measured
|
||||||
} else {
|
} else {
|
||||||
cache.placeholder
|
&info.cache.placeholder
|
||||||
};
|
};
|
||||||
context
|
context
|
||||||
.gfx
|
.gfx
|
||||||
|
|
@ -1153,12 +1183,13 @@ where
|
||||||
|
|
||||||
let width = available_space.width.max().saturating_sub(padding * 2);
|
let width = available_space.width.max().saturating_sub(padding * 2);
|
||||||
|
|
||||||
let cache = self.layout_text(Some(width.into_signed()), &mut context.graphics);
|
self.layout_text(Some(width.into_signed()), &mut context.graphics);
|
||||||
|
let info = self.cache_info();
|
||||||
|
|
||||||
cache
|
info.cache
|
||||||
.measured
|
.measured
|
||||||
.size
|
.size
|
||||||
.max(cache.placeholder.size)
|
.max(info.cache.placeholder.size)
|
||||||
.into_unsigned()
|
.into_unsigned()
|
||||||
+ Size::squared(padding * 2)
|
+ Size::squared(padding * 2)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue