feat(ui): refine Graphite visuals and center navigation content

This commit is contained in:
aria committed 2026-10-28 04:09:10 +09:00
1 parent cc3b6f903f
commit 1e34ec47ad
14 files changed
+646 -121

No files matched your search

+172 -2
View File
@@ -1021,10 +1021,10 @@ fn sidebar_collapses_at_the_breakpoint_and_compact_navigation_dismisses() {
let compact = width < 960.;
assert_eq!(bounds.sidebar.is_none(), compact);
assert_eq!(bounds.toggle.is_some(), compact);
assert_eq!(bounds.page.unwrap().x, if compact { 0. } else { 204. });
assert_eq!(bounds.page.unwrap().x, if compact { 0. } else { 208. });
assert_eq!(
bounds.page.unwrap().width,
if compact { width } else { width - 204. }
if compact { width } else { width - 208. }
);
}
let _ = app.update(Message::Sidebar(true));
@@ -1084,3 +1084,173 @@ fn sidebar_collapses_at_the_breakpoint_and_compact_navigation_dismisses() {
);
assert!(app.pending.is_empty() && !app.saving);
}
#[test]
fn appearance_cards_reflow_without_overlap_and_keep_every_destination() {
#[derive(Default)]
struct Cards {
bounds: std::collections::HashMap<usize, Rectangle>,
}
impl Operation for Cards {
fn traverse(&mut self, children: &mut dyn FnMut(&mut dyn Operation)) {
children(self);
}
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
for page in Page::ALL {
if id == Some(&widget::Id::new(format!("settings-hub-{}", page as usize))) {
self.bounds.insert(page as usize, bounds);
}
}
}
}
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let renderer = runtime
.block_on(<cosmic::Renderer as Headless>::new(
Font::default(),
Pixels(14.),
Some("tiny-skia"),
))
.unwrap();
let app = App::init(
Core::default(),
(
"/unused/overview.kdl".into(),
Snapshot::parse(String::new()),
None,
),
)
.0;
for width in [480., 720., 960., 1280., 1800.] {
let mut view = app.page_view();
let mut tree = Tree::new(&view);
let node = view.as_widget_mut().layout(
&mut tree,
&renderer,
&layout::Limits::new(Size::ZERO, Size::new(width, 760.)),
);
let mut cards = Cards::default();
view.as_widget_mut()
.operate(&mut tree, Layout::new(&node), &renderer, &mut cards);
assert_eq!(
cards.bounds.len(),
14,
"{width}: all settings destinations must be reachable"
);
for bounds in cards.bounds.values() {
assert!(
bounds.x >= 0. && bounds.x + bounds.width <= width + 0.5,
"{width}: {bounds:?}"
);
assert!(bounds.width > 200. && bounds.height > 40.);
}
let general = cards.bounds[&(Page::General as usize)];
let complete = cards.bounds[&(Page::Configuration as usize)];
if width <= 720. {
assert!((general.x - complete.x).abs() < 0.5);
assert!(complete.y >= general.y + general.height);
} else {
assert!((general.y - complete.y).abs() < 0.5);
assert!(complete.x >= general.x + general.width + 8.);
}
let rectangles = cards.bounds.values().collect::<Vec<_>>();
for (i, a) in rectangles.iter().enumerate() {
for b in &rectangles[i + 1..] {
assert!(
a.intersection(*b).is_none(),
"{width}: cards overlap: {a:?} {b:?}"
);
}
}
}
}
#[test]
fn sidebar_rows_center_inside_the_full_button_hit_area() {
#[derive(Default)]
struct Centers {
buttons: std::collections::HashMap<usize, Rectangle>,
parts: Vec<(usize, Rectangle)>,
}
impl Operation for Centers {
fn traverse(&mut self, children: &mut dyn FnMut(&mut dyn Operation)) {
children(self);
}
fn focusable(
&mut self,
id: Option<&widget::Id>,
bounds: Rectangle,
_: &mut dyn cosmic::iced::advanced::widget::operation::Focusable,
) {
for page in Page::ALL {
if id
== Some(&widget::Id::new(format!(
"settings-nav-button-{}",
page as usize
)))
{
self.buttons.insert(page as usize, bounds);
}
}
}
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
for page in Page::ALL {
for part in ["content", "icon", "label", "marker"] {
if id
== Some(&widget::Id::new(format!(
"settings-nav-{part}-{}",
page as usize
)))
{
self.parts.push((page as usize, bounds));
}
}
}
}
}
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let renderer = runtime
.block_on(<cosmic::Renderer as Headless>::new(
Font::default(),
Pixels(14.),
Some("tiny-skia"),
))
.unwrap();
let mut app = App::init(
Core::default(),
(
"/unused/navigation-centers.kdl".into(),
Snapshot::parse(String::new()),
None,
),
)
.0;
for selected in [Page::Windows, Page::Keyboard, Page::Accessibility] {
app.page = selected;
let mut view = app.page_view();
let mut tree = Tree::new(&view);
let node = view.as_widget_mut().layout(
&mut tree,
&renderer,
&layout::Limits::new(Size::ZERO, Size::new(1280., 760.)),
);
let mut centers = Centers::default();
view.as_widget_mut()
.operate(&mut tree, Layout::new(&node), &renderer, &mut centers);
assert_eq!(centers.buttons.len(), 15);
assert_eq!(centers.parts.len(), 60);
for (page, part) in &centers.parts {
let button = centers.buttons[page];
assert_eq!(button.height, 36.);
assert!(
((part.y + part.height / 2.) - (button.y + button.height / 2.)).abs() < 0.51,
"page {page}: content {part:?} is not centered in {button:?}"
);
}
}
}