feat(segmented_button): add FileNav style with related widget improvements

This commit is contained in:
Michael Aaron Murphy 2025-08-19 11:13:28 +02:00
parent 8412dd5939
commit c10695600b
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
3 changed files with 100 additions and 57 deletions

View file

@ -18,6 +18,8 @@ pub enum SegmentedButton {
Control, Control,
/// Navigation bar style /// Navigation bar style
NavBar, NavBar,
/// File browser
FileNav,
/// Or implement any custom theme of your liking. /// Or implement any custom theme of your liking.
Custom(Box<dyn Fn(&Theme) -> Appearance>), Custom(Box<dyn Fn(&Theme) -> Appearance>),
} }
@ -69,7 +71,7 @@ impl StyleSheet for Theme {
} }
} }
SegmentedButton::NavBar => Appearance { SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance {
active_width: 0.0, active_width: 0.0,
..horizontal::tab_bar(cosmic, container) ..horizontal::tab_bar(cosmic, container)
}, },
@ -124,7 +126,7 @@ impl StyleSheet for Theme {
} }
} }
SegmentedButton::NavBar => Appearance { SegmentedButton::NavBar | SegmentedButton::FileNav => Appearance {
active_width: 0.0, active_width: 0.0,
..vertical::tab_bar(cosmic, container) ..vertical::tab_bar(cosmic, container)
}, },

View file

@ -67,7 +67,7 @@ where
/ num as f32; / num as f32;
} }
let segmetned_control = matches!(self.style, crate::theme::SegmentedButton::Control); let is_control = matches!(self.style, crate::theme::SegmentedButton::Control);
Box::new( Box::new(
self.model self.model
@ -93,7 +93,7 @@ where
let button_bounds = ItemBounds::Button(key, layout_bounds); let button_bounds = ItemBounds::Button(key, layout_bounds);
let mut divider = None; let mut divider = None;
if self.dividers && segmetned_control && nth + 1 < num { if self.dividers && is_control && nth + 1 < num {
divider = Some(ItemBounds::Divider( divider = Some(ItemBounds::Divider(
Rectangle { Rectangle {
width: 1.0, width: 1.0,
@ -143,7 +143,7 @@ where
let max_size = limits.height(Length::Fixed(max_height)).resolve( let max_size = limits.height(Length::Fixed(max_height)).resolve(
Length::Fill, Length::Fill,
max_height, max_height,
Size::new(f32::MAX, max_height), Size::new(limits.max().width, max_height),
); );
let mut visible_width = 0.0; let mut visible_width = 0.0;
@ -152,7 +152,7 @@ where
for (button_size, _actual_size) in &state.internal_layout { for (button_size, _actual_size) in &state.internal_layout {
visible_width += button_size.width; visible_width += button_size.width;
if max_size.width >= visible_width { if max_size.width - spacing >= visible_width {
state.buttons_visible += 1; state.buttons_visible += 1;
} else { } else {
visible_width = max_size.width - max_height; visible_width = max_size.width - max_height;

View file

@ -619,20 +619,19 @@ where
for key in self.model.order.iter().copied() { for key in self.model.order.iter().copied() {
if let Some(text) = self.model.text.get(key) { if let Some(text) = self.model.text.get(key) {
let (font, button_state) = if self.button_is_focused(state, key) { let font = if self.button_is_focused(state, key) {
(self.font_active, 0) self.font_active
} else if state.show_context.is_some() || self.button_is_hovered(state, key) { } else if state.show_context.is_some() || self.button_is_hovered(state, key) {
(self.font_hovered, 1) self.font_hovered
} else if self.model.is_active(key) { } else if self.model.is_active(key) {
(self.font_active, 2) self.font_active
} else { } else {
(self.font_inactive, 3) self.font_inactive
}; };
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
text.hash(&mut hasher); text.hash(&mut hasher);
font.hash(&mut hasher); font.hash(&mut hasher);
button_state.hash(&mut hasher);
let text_hash = hasher.finish(); let text_hash = hasher.finish();
if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) { if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) {
@ -1293,6 +1292,15 @@ where
); );
} }
let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
let divider_background = Background::Color(
crate::theme::active()
.cosmic()
.primary_component_divider()
.into(),
);
// Draw each of the items in the widget. // Draw each of the items in the widget.
let mut nth = 0; let mut nth = 0;
self.variant_bounds(state, bounds).for_each(move |item| { self.variant_bounds(state, bounds).for_each(move |item| {
@ -1337,7 +1345,7 @@ where
let key_is_active = self.model.is_active(key); let key_is_active = self.model.is_active(key);
let key_is_focused = state.focused_visible && self.button_is_focused(state, key); let key_is_focused = state.focused_visible && self.button_is_focused(state, key);
let key_is_hovered = self.button_is_hovered(state, key); let key_is_hovered = self.button_is_hovered(state, key);
let status_appearance = if self.button_is_pressed(state, key) && key_is_hovered { let status_appearance = if self.button_is_pressed(state, key) {
appearance.pressed appearance.pressed
} else if key_is_hovered || menu_open() { } else if key_is_hovered || menu_open() {
appearance.hover appearance.hover
@ -1355,11 +1363,87 @@ where
status_appearance.middle status_appearance.middle
}; };
// Draw the active hint on tabs
if appearance.active_width > 0.0 {
let active_width = if key_is_active {
appearance.active_width
} else {
1.0
};
renderer.fill_quad(
renderer::Quad {
bounds: if Self::VERTICAL {
Rectangle {
x: bounds.x + bounds.width - active_width,
width: active_width,
..bounds
}
} else {
Rectangle {
y: bounds.y + bounds.height - active_width,
height: active_width,
..bounds
}
},
border: Border {
radius: rad_0.into(),
..Default::default()
},
shadow: Shadow::default(),
},
appearance.active.text_color,
);
}
let original_bounds = bounds;
bounds.x += f32::from(self.button_padding[0]);
bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
let mut indent_padding = 0.0;
// Adjust bounds by indent
if let Some(indent) = self.model.indent(key) {
if indent > 0 {
let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
bounds.x += adjustment;
bounds.width -= adjustment;
// Draw indent line
if let crate::theme::SegmentedButton::FileNav = self.style {
if indent > 1 {
indent_padding = 7.0;
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x - self.indent_spacing as f32 + indent_padding,
width: 1.0,
..bounds
},
border: Border {
radius: rad_0.into(),
..Default::default()
},
shadow: Shadow::default(),
},
divider_background,
);
indent_padding += 4.0;
}
}
}
}
// Render the background of the button. // Render the background of the button.
if key_is_focused || status_appearance.background.is_some() { if key_is_focused || status_appearance.background.is_some() {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds, bounds: Rectangle {
x: bounds.x - f32::from(self.button_padding[0]) + indent_padding,
width: bounds.width + f32::from(self.button_padding[0])
- f32::from(self.button_padding[2])
- indent_padding,
..bounds
},
border: if key_is_focused { border: if key_is_focused {
Border { Border {
width: 1.0, width: 1.0,
@ -1377,49 +1461,6 @@ where
); );
} }
// Draw the active hint on tabs
if appearance.active_width > 0.0 {
let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
let active_width = if key_is_active {
appearance.active_width
} else {
1.0
};
let mut bounds = bounds;
if Self::VERTICAL {
bounds.x += bounds.height - active_width;
bounds.width = active_width;
} else {
bounds.y += bounds.height - active_width;
bounds.height = active_width;
}
renderer.fill_quad(
renderer::Quad {
bounds,
border: Border {
radius: rad_0.into(),
..Default::default()
},
shadow: Shadow::default(),
},
appearance.active.text_color,
);
}
let original_bounds = bounds;
bounds.x += f32::from(self.button_padding[0]);
bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
// Adjust bounds by indent
if let Some(indent) = self.model.indent(key) {
let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
bounds.x += adjustment;
bounds.width -= adjustment;
}
// Align contents of the button to the requested `button_alignment`. // Align contents of the button to the requested `button_alignment`.
{ {
let actual_width = state.internal_layout[nth].1.width; let actual_width = state.internal_layout[nth].1.width;