Styles pane: the scroll bar no longer covers the a / ¶ markers (#4753) (#4882)

* #4753: Styles pane scroll bar keeps clear of the a / ¶ markers

The Styles pane list used egui's default floating scroll bar, which is drawn
over the right edge of the rows; on hover it widens to 10 pt with a translucent
track and covers the right-aligned "a" and "¶" style-type markers. The list now
uses a solid scroll bar, which takes a column of its own beside the rows (as in
the TOC options dialog and vectorcraft#2232's menus). Its handle keeps the text
colour: the solid preset fills it with the button colour, which is white on the
pane's white track and would leave the handle invisible.

The regression test draws the pane with the app theme, hovers the bar and
checks that no scroll bar rectangle meets a marker and that the handle stands
out from the track.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4753: Styles pane scroll bar test hovers the bar and checks dark too

The test now takes the hover point from the pane's own right edge instead of
the harness size (kittest insets the UI, so the old point sat just outside the
bar), asserts the pointer is on the bar, and runs in the dark appearance as
well as the light one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
This commit is contained in:
TheRenegadeDev
2026-11-08 02:55:02 +01:00
committed by GitHub
co-authored by Claude Opus 5.5 Martin Ball
parent bc0f691c13
commit b61aee61ba
2 changed files with 59 additions and 0 deletions
+3
View File
@@ -72,6 +72,9 @@ pub(super) fn styles(app: &mut WordApp, ui: &mut Ui) {
if ui.selectable_label(app.session.view.style_inspector, tl!("Style Inspector")).clicked() {
let _ = app.run("styles.inspector", json!({}));
}
// A solid scroll bar takes its own room beside the list instead of floating over the a / ¶ markers.
// Its handle keeps the text colour: the solid default fills it like a button, white on white here.
ui.spacing_mut().scroll = egui::style::ScrollStyle { foreground_color: true, ..egui::style::ScrollStyle::solid() };
ui.separator();
let mut list: Vec<(String, String, bool, bool)> = app
.session
+56
View File
@@ -468,3 +468,59 @@ fn a_long_comment_card_keeps_its_buttons_and_the_next_card_in_view() {
assert_eq!(go_to.len(), 2, "one Go to button per card");
assert!(go_to.iter().all(|r| r.max.y < 900.0), "both cards' buttons are in view: {go_to:?}");
}
/// #4753: the Styles pane's scroll bar has a column of its own, so with the pointer over it the
/// "a" and "¶" markers at the right of each row stay clear of it; its handle still shows, light
/// or dark.
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn the_styles_pane_scroll_bar_leaves_the_style_markers_clear() {
use egui_kittest::kittest::Queryable;
for appearance in [crate::theme::Appearance::Light, crate::theme::Appearance::Dark] {
let app = crate::WordApp::new(wordcraft_engine::Session::new(wordcraft_doc::Document::from_text("Hello")), crate::Services::default());
let size = egui::vec2(240.0, 400.0);
let right = std::rc::Rc::new(std::cell::Cell::new(0.0));
let edge = right.clone();
let mut fonts = false;
let mut h = egui_kittest::Harness::builder().with_size(size).build_ui_state(
move |ui, app: &mut crate::WordApp| {
if !fonts {
crate::theme::install_fonts(ui.ctx());
crate::theme::apply(ui.ctx(), appearance);
fonts = true;
return;
}
edge.set(ui.max_rect().right());
super::styles(app, ui);
},
app,
);
h.run_steps(4);
// On the bar, along the pane's right edge, where it is at its widest.
h.hover_at(egui::pos2(right.get() - 3.0, size.y - 60.0));
h.run_steps(20);
let markers: Vec<egui::Rect> = h.get_all_by_label("").chain(h.get_all_by_label("a")).map(|n| n.rect()).collect();
assert!(markers.len() > 5, "{appearance:?}: the list shows its markers: {markers:?}");
// Only a scroll bar is a narrow, tall filled rectangle along the list's right edge.
let bars: Vec<(egui::Rect, egui::Color32)> = h
.output()
.shapes
.iter()
.filter_map(|c| match &c.shape {
egui::Shape::Rect(r) if r.fill.a() > 0 && r.rect.width() < 20.0 && r.rect.height() > 3.0 * r.rect.width() => {
Some((r.rect.intersect(c.clip_rect), r.fill))
}
_ => None,
})
.collect();
assert!(bars.iter().any(|(b, _)| b.x_range().contains(right.get() - 3.0)), "{appearance:?}: the pointer is on the bar: {bars:?}");
let covered: Vec<_> = markers.iter().filter(|m| bars.iter().any(|(b, _)| b.intersects(**m))).collect();
assert!(covered.is_empty(), "{appearance:?}: the scroll bar {bars:?} covers markers {covered:?}");
// The track is the pane's field colour; the handle stands out from it.
let track = h.ctx.global_style().visuals.extreme_bg_color;
assert!(
bars.iter().any(|(_, fill)| fill.r().abs_diff(track.r()) > 60),
"{appearance:?}: the handle shows against the track {track:?}: {bars:?}"
);
}
}