From 01fc7a5e752ef686ae42a12b6ba5410aa3b270d4 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:01:03 +0200 Subject: [PATCH 1/2] UI: star rating previews the hovered rating, Info tab and bottom bar (#647) --- crates/ui-egui/src/widgets.rs | 49 +++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 4281c28ec..54fc662d4 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -488,27 +488,32 @@ pub fn icon_button(ui: &mut Ui, id: &str, icon: Icon, size: egui::Vec2, active: pub fn stars(ui: &mut Ui, id: &str, rating: u8, size: f32) -> Option { let t = Tokens::get(ui.ctx()); let mut out = None; - let hover_n = { - let mut h = None; - for i in 0..5u8 { - let (r, resp) = ui.allocate_exact_size(vec2(size, size), Sense::click()); - register(ui.ctx(), format!("star:{id}:{}", i + 1), r); - if resp.hovered() { - h = Some(i + 1); - } - if resp.clicked() { - out = Some(if rating == i + 1 { 0 } else { i + 1 }); - } - ui.painter().text(r.center(), Align2::CENTER_CENTER, "", t.font(1.0), t.star); - let filled = i < rating; - paint(ui.painter(), r.shrink(size * 0.12), if filled { Icon::StarFilled } else { Icon::Star }, if filled { t.star } else { t.icon }); + let mut hover_n = None; + let mut rects = [Rect::NOTHING; 5]; + for (i, slot) in (0..5u8).zip(rects.iter_mut()) { + let (r, resp) = ui.allocate_exact_size(vec2(size, size), Sense::click()); + register(ui.ctx(), format!("star:{id}:{}", i + 1), r); + if resp.hovered() { + hover_n = Some(i + 1); } - h - }; - let _ = hover_n; + if resp.clicked() { + out = Some(if rating == i + 1 { 0 } else { i + 1 }); + } + *slot = r; + } + // Hover previews the rating a click would set: stars 1..=hovered light up. + for (i, r) in (0..5u8).zip(rects) { + let filled = star_lit(i, rating, hover_n); + paint(ui.painter(), r.shrink(size * 0.12), if filled { Icon::StarFilled } else { Icon::Star }, if filled { t.star } else { t.icon }); + } out } +/// Whether the star at zero-based index `i` is drawn filled: the hovered star's rating previews over the current one. +fn star_lit(i: u8, rating: u8, hover: Option) -> bool { + i < hover.unwrap_or(rating) +} + /// A borderless dropdown button: `text` followed by a painted chevron (no font glyph needed). pub fn dropdown(ui: &mut Ui, id: &str, text: &str, font: egui::FontId, color: Color32) -> Response { let t = Tokens::get(ui.ctx()); @@ -527,6 +532,16 @@ pub fn dropdown(ui: &mut Ui, id: &str, text: &str, font: egui::FontId, color: Co mod tests { use super::*; + /// Issue #647: hovering a star previews the rating a click would set. + #[test] + fn hovered_star_previews_its_rating() { + let lit = |rating, hover| (0..5u8).filter(|&i| star_lit(i, rating, hover)).count(); + assert_eq!(lit(2, None), 2, "no hover shows the rating"); + assert_eq!(lit(2, Some(4)), 4); + assert_eq!(lit(5, Some(1)), 1); + assert_eq!(lit(0, Some(3)), 3); + } + #[test] fn nudges_are_a_sensible_step() { let spec = |id, min, max, step| ControlSpec { From 4cce2d169e74afbc3677298add3978b5234c1cd3 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sat, 10 Oct 2026 11:26:36 +0200 Subject: [PATCH 2/2] UI: hovered star gets the icon-button hover square (#647) --- crates/ui-egui/src/widgets.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 54fc662d4..5a8d24791 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -501,8 +501,12 @@ pub fn stars(ui: &mut Ui, id: &str, rating: u8, size: f32) -> Option { } *slot = r; } - // Hover previews the rating a click would set: stars 1..=hovered light up. + // Hover previews the rating a click would set: stars 1..=hovered light up. The hovered star also gets the + // icon_button hover square, so hovering the current rating (where the preview looks the same) still shows feedback. for (i, r) in (0..5u8).zip(rects) { + if hover_n == Some(i + 1) { + ui.painter().rect_filled(r, 4.0, t.hover.gamma_multiply(0.7)); + } let filled = star_lit(i, rating, hover_n); paint(ui.painter(), r.shrink(size * 0.12), if filled { Icon::StarFilled } else { Icon::Star }, if filled { t.star } else { t.icon }); }