LR-LIB-RATING: first arrow after Tab steps the rating

egui picks the neighbour to focus from the arrow key in begin_pass,
before a widget can set a focus lock for the frame it just gained
focus (set_focus_lock_filter needs a frame of focus first). A focused
star that consumes an arrow now cancels that pending focus move. The
favourite toggle is one interact-height line square again, so Manage
Effect Presets rows keep their height.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Nathaniel Ledford
2026-10-22 02:03:17 -04:00
co-authored by Claude
parent 0e04000f60
commit e161744d70
2 changed files with 24 additions and 7 deletions
+21 -4
View File
@@ -13,7 +13,6 @@
use egui::{Color32, Key, Modifiers, Painter, Pos2, Rect, Sense, Ui, pos2, vec2};
use crate::button::Button;
use crate::icons::{Icon, paint};
use crate::theme::Tokens;
use crate::widgets::{announce_selected, register};
@@ -151,6 +150,11 @@ impl Rating {
}
if focused && out.is_none() {
let (left, right) = ui.input_mut(|i| (i.consume_key(Modifiers::NONE, Key::ArrowLeft), i.consume_key(Modifiers::NONE, Key::ArrowRight)));
if left || right {
// egui picked the neighbour to focus from this same key before the lock above could apply (the
// first arrow after Tab): cancel that move
ui.memory_mut(|m| m.move_focus(egui::FocusDirection::None));
}
out = nudged_rating(self.rating, left, right);
}
// The hovered star also gets the icon-button hover square, so hovering the current rating (where
@@ -167,10 +171,23 @@ impl Rating {
}
}
/// A frameless star toggle for favourites: filled when `favorite`, named `name` for screen readers,
/// reachable as `widget` (a full automation id).
/// A frameless star toggle for favourites: filled when `favorite`, named `name` for screen readers and as
/// its tooltip, reachable as `widget` (a full automation id). A square one line tall.
pub fn favorite_button(ui: &mut Ui, widget: String, favorite: bool, name: &str) -> egui::Response {
Button::icon_only(if favorite { Icon::StarFilled } else { Icon::Star }, name).ghost().small().widget(widget).show(ui)
let t = Tokens::get(ui.ctx());
let side = ui.spacing().interact_size.y;
let (r, resp) = ui.allocate_exact_size(vec2(side, side), Sense::click());
announce_selected(&resp, name, favorite);
register(ui.ctx(), widget, r);
if resp.hovered() {
ui.painter().rect_filled(r, 4.0, t.hover.gamma_multiply(0.7));
}
if resp.has_focus() {
ui.painter().rect_stroke(r.expand(1.0), 6.0, egui::Stroke::new(2.0, t.accent), egui::StrokeKind::Outside);
}
let icon = RatingSize::Medium.icon();
paint_star(ui.painter(), Rect::from_center_size(r.center(), vec2(icon, icon)), favorite, if favorite { t.star } else { t.icon });
resp.on_hover_text(name)
}
#[cfg(test)]
+3 -3
View File
@@ -103,10 +103,10 @@ fn step_with_neighbours(start: u8, keys: &[egui::Key]) -> Vec<u8> {
crate::theme::install_fonts(&ctx);
let rating = std::cell::Cell::new(start);
let mut seen = Vec::new();
// a key every other frame, as a person presses them: the focus lock is set during the frame after a focus change
for frame in 0..keys.len() * 2 + 3 {
// a key every frame, Tab then an arrow straight away
for frame in 0..keys.len() + 3 {
let mut events = Vec::new();
if let Some(k) = frame.checked_sub(1).filter(|f| f % 2 == 0).and_then(|f| keys.get(f / 2)) {
if let Some(k) = frame.checked_sub(1).and_then(|f| keys.get(f)) {
events.push(egui::Event::Key { key: *k, physical_key: Some(*k), pressed: true, repeat: false, modifiers: Default::default() });
}
let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(600.0, 200.0))), events, ..Default::default() };