GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix(text_box): measure the caret in the font the text is drawn in; F1-F12 keys
TextBox measured its glyph positions (caret, selection, click-to-column)
by shaping font_family, or monospace for a password, while the text is
drawn through Adapted's own-labels bridge in Paint::text_font -- the
configured control font string. Two faces, so each column was measured
a little off and the caret drifted with every character: two dots short
after twelve in the login greeter's password box. value_font() is now
the one answer, text_font(), and both prepare_text and paint use it.
NamedKey gains F1-F4 and F6-F12 beside F5, mapped from their keysyms in
the runner, for the greeter's F1 power off and F2 reboot. NamedKey is
matched exhaustively in cce-browser's and cce-mail's WPE key tables,
which gain the arms in the same change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/backend/window_runner.rs | 11 +++++++++++
src/widget/input/text_box.rs | 34 +++++++++++++++++++++++++---------
src/widget/mod.rs | 14 ++++++++++++++
3 files changed, 50 insertions(+), 9 deletions(-)
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 797d3ab..d6f6043 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -5260,7 +5260,18 @@ impl<A: Application> EngineState<A> {
xkeysym::Keysym::Alt_L | xkeysym::Keysym::Alt_R => Key::Named(NamedKey::Alt),
xkeysym::Keysym::Control_L | xkeysym::Keysym::Control_R => Key::Named(NamedKey::Control),
xkeysym::Keysym::Shift_L | xkeysym::Keysym::Shift_R => Key::Named(NamedKey::Shift),
+ xkeysym::Keysym::F1 => Key::Named(NamedKey::F1),
+ xkeysym::Keysym::F2 => Key::Named(NamedKey::F2),
+ xkeysym::Keysym::F3 => Key::Named(NamedKey::F3),
+ xkeysym::Keysym::F4 => Key::Named(NamedKey::F4),
xkeysym::Keysym::F5 => Key::Named(NamedKey::F5),
+ xkeysym::Keysym::F6 => Key::Named(NamedKey::F6),
+ xkeysym::Keysym::F7 => Key::Named(NamedKey::F7),
+ xkeysym::Keysym::F8 => Key::Named(NamedKey::F8),
+ xkeysym::Keysym::F9 => Key::Named(NamedKey::F9),
+ xkeysym::Keysym::F10 => Key::Named(NamedKey::F10),
+ xkeysym::Keysym::F11 => Key::Named(NamedKey::F11),
+ xkeysym::Keysym::F12 => Key::Named(NamedKey::F12),
_ => {
// With Ctrl held, xkb's utf8 goes through the legacy control-character
// transformation (ctrl+j = "\n", ctrl+a = 0x01, ...); the keysym is
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 72146ed..a18e8d9 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -221,6 +221,23 @@ impl TextBox {
closest
}
+ /// The font the value text is drawn in — AND measured in: `prepare_text`
+ /// (whose per-glyph positions place the caret, the selection and
+ /// click-to-column) shapes with exactly this, and `paint` emits it. It is
+ /// [`Paint::text_font`], because that is what actually draws the text:
+ /// `Adapted::paint_self` drops the Text prims `paint` emits and redraws
+ /// the value through its own-labels bridge in `text_font()` — the box's
+ /// family when customized (cce-text-editor's monospace editor), else the
+ /// configured control font string.
+ ///
+ /// Until 2026-09-25 the measurement shaped `font_family` instead (or
+ /// `monospace` for a password), a different face from the drawn one, so
+ /// the caret drifted off the text a little more with every character —
+ /// two dots short after twelve in the login greeter's password box.
+ pub fn value_font(&self) -> Option<String> {
+ <Self as Paint>::text_font(self)
+ }
+
pub fn char_width(&self) -> f32 {
if self.shaped_char_advance > 0.0 {
self.shaped_char_advance
@@ -1385,11 +1402,9 @@ impl Paint for TextBox {
text_src.as_str()
};
- let font_fam = if self.is_password {
- "monospace"
- } else {
- self.font_family.as_str()
- };
+ // Measured in the family the value text is DRAWN in — see `value_font`.
+ let font_fam = self.value_font();
+ let font_fam = font_fam.as_deref();
let render_text = if self.is_password {
"•".repeat(display_text.chars().count())
@@ -1397,7 +1412,7 @@ impl Paint for TextBox {
display_text.to_string()
};
- let buffer = crate::widget::display::text_label::make_widget_text_buffer(fs, &render_text, self.font_size, font_fam);
+ let buffer = crate::backend::window_runner::get_text_buffer(fs, &render_text, self.font_size, font_fam);
let char_count = render_text.chars().count();
let mut x_offsets = vec![0.0; char_count + 1];
@@ -1406,7 +1421,7 @@ impl Paint for TextBox {
// One column's advance, from the same shaping path as the labels (buffer-cached,
// so this is a lookup after the first frame per family/size).
- let probe = crate::widget::display::text_label::make_widget_text_buffer(fs, "MMMMMMMM", self.font_size, font_fam);
+ let probe = crate::backend::window_runner::get_text_buffer(fs, "MMMMMMMM", self.font_size, font_fam);
self.shaped_char_advance = probe
.layout_runs()
.next()
@@ -1455,7 +1470,7 @@ impl Paint for TextBox {
};
let (lines, _) = self.wrap_text(max_chars);
for line in &lines {
- let line_buffer = crate::widget::display::text_label::make_widget_text_buffer(fs, line, self.font_size, font_fam);
+ let line_buffer = crate::backend::window_runner::get_text_buffer(fs, line, self.font_size, font_fam);
let n = line.chars().count();
let mut offs = vec![0.0f32; n + 1];
let mut line_total: f32 = 0.0;
@@ -1591,8 +1606,9 @@ impl Paint for TextBox {
self.rect.x + self.rect.width,
self.rect.y + self.rect.height,
]);
+ let font = self.value_font();
for tl in self.value_labels() {
- ctx.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, None, well);
+ ctx.text_with(tl.text, tl.x, tl.y, tl.font_size, tl.color, font.clone(), well);
}
}
}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index eda59f8..09655bb 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -71,7 +71,21 @@ pub enum NamedKey {
Shift,
Alt,
Super,
+ // The function keys. F5 came first (the login greeter's restart); the
+ // rest arrived together on 2026-09-25 for the greeter's F1 power off and
+ // F2 reboot.
+ F1,
+ F2,
+ F3,
+ F4,
F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
}
#[derive(Debug, Clone, PartialEq, Eq)]