fix(segmented_button): hoverable tab pagination buttons; fixed padding

This commit is contained in:
Michael Aaron Murphy 2024-01-26 20:30:23 +01:00 • committed by Michael Murphy
parent d5b2a2e87c
commit 135770a16d
2 changed files with 128 additions and 85 deletions

View file

@ -55,13 +55,15 @@ where
let mut homogenous_width = 0.0; let mut homogenous_width = 0.0;
if Length::Shrink != self.width || state.collapsed { if Length::Shrink != self.width || state.collapsed {
let mut width_offset = 0.0;
if state.collapsed { if state.collapsed {
bounds.x += 16.0; bounds.x += f32::from(self.button_height);
bounds.width -= 32.0; width_offset = f32::from(self.button_height) * 2.0;
} }
homogenous_width = homogenous_width = ((num as f32).mul_add(-spacing, bounds.width - width_offset)
((num as f32).mul_add(-spacing, bounds.width) + spacing) / num as f32; + spacing)
/ num as f32;
} }
self.model self.model
@ -98,7 +100,7 @@ where
let mut total_width = 0.0; let mut total_width = 0.0;
let spacing = f32::from(self.spacing); let spacing = f32::from(self.spacing);
let limits = limits.width(self.width); let limits = limits.width(self.width);
let size; let mut size;
if state.known_length != num { if state.known_length != num {
if state.known_length > num { if state.known_length > num {
@ -131,7 +133,7 @@ where
.height(Length::Fixed(total_height)) .height(Length::Fixed(total_height))
.resolve(Size::new(f32::MAX, total_height)); .resolve(Size::new(f32::MAX, total_height));
let mut visible_width = 32.0; let mut visible_width = f32::from(self.button_height) * 2.0;
state.buttons_visible = 0; state.buttons_visible = 0;
for button_size in &state.internal_layout { for button_size in &state.internal_layout {
@ -150,12 +152,13 @@ where
// If collapsed, use the maximum width available. // If collapsed, use the maximum width available.
visible_width = if state.collapsed { visible_width = if state.collapsed {
max_size.width - 32.0 max_size.width - f32::from(self.button_height)
} else { } else {
total_width total_width
}; };
size = limits size = limits
.width(Length::Fixed(visible_width))
.height(Length::Fixed(total_height)) .height(Length::Fixed(total_height))
.resolve(Size::new(visible_width, total_height)); .resolve(Size::new(visible_width, total_height));
} else { } else {
@ -173,6 +176,10 @@ where
state.collapsed = actual_width < minimum_width; state.collapsed = actual_width < minimum_width;
if state.collapsed { if state.collapsed {
size = limits
.height(Length::Fixed(height))
.resolve(Size::new(f32::MAX, height));
state.buttons_visible = state.buttons_visible =
(actual_width / self.minimum_button_width as usize).min(state.buttons_visible); (actual_width / self.minimum_button_width as usize).min(state.buttons_visible);
} }

View file

@ -159,10 +159,10 @@ where
self.model.items.get(key).map_or(false, |item| item.enabled) self.model.items.get(key).map_or(false, |item| item.enabled)
} }
/// Focus the previous item in the widget. /// Item the previous item in the widget.
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status { fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
match state.focused_item { match state.focused_item {
Focus::Tab(entity) => { Item::Tab(entity) => {
let mut keys = self.iterate_visible_tabs(state).rev(); let mut keys = self.iterate_visible_tabs(state).rev();
while let Some(key) = keys.next() { while let Some(key) = keys.next() {
@ -173,7 +173,7 @@ where
continue; continue;
} }
state.focused_item = Focus::Tab(key); state.focused_item = Item::Tab(key);
return event::Status::Captured; return event::Status::Captured;
} }
@ -182,39 +182,39 @@ where
} }
if self.prev_tab_sensitive(state) { if self.prev_tab_sensitive(state) {
state.focused_item = Focus::PrevButton; state.focused_item = Item::PrevButton;
return event::Status::Captured; return event::Status::Captured;
} }
} }
Focus::NextButton => { Item::NextButton => {
if let Some(last) = self.last_tab(state) { if let Some(last) = self.last_tab(state) {
state.focused_item = Focus::Tab(last); state.focused_item = Item::Tab(last);
return event::Status::Captured; return event::Status::Captured;
} }
} }
Focus::None => { Item::None => {
if self.next_tab_sensitive(state) { if self.next_tab_sensitive(state) {
state.focused_item = Focus::NextButton; state.focused_item = Item::NextButton;
return event::Status::Captured; return event::Status::Captured;
} else if let Some(last) = self.last_tab(state) { } else if let Some(last) = self.last_tab(state) {
state.focused_item = Focus::Tab(last); state.focused_item = Item::Tab(last);
return event::Status::Captured; return event::Status::Captured;
} }
} }
Focus::PrevButton | Focus::Set => (), Item::PrevButton | Item::Set => (),
} }
state.focused_item = Focus::None; state.focused_item = Item::None;
event::Status::Ignored event::Status::Ignored
} }
/// Focus the next item in the widget. /// Item the next item in the widget.
fn focus_next(&mut self, state: &mut LocalState) -> event::Status { fn focus_next(&mut self, state: &mut LocalState) -> event::Status {
match state.focused_item { match state.focused_item {
Focus::Tab(entity) => { Item::Tab(entity) => {
let mut keys = self.iterate_visible_tabs(state); let mut keys = self.iterate_visible_tabs(state);
while let Some(key) = keys.next() { while let Some(key) = keys.next() {
if key == entity { if key == entity {
@ -224,7 +224,7 @@ where
continue; continue;
} }
state.focused_item = Focus::Tab(key); state.focused_item = Item::Tab(key);
return event::Status::Captured; return event::Status::Captured;
} }
@ -233,32 +233,32 @@ where
} }
if self.next_tab_sensitive(state) { if self.next_tab_sensitive(state) {
state.focused_item = Focus::NextButton; state.focused_item = Item::NextButton;
return event::Status::Captured; return event::Status::Captured;
} }
} }
Focus::PrevButton => { Item::PrevButton => {
if let Some(first) = self.first_tab(state) { if let Some(first) = self.first_tab(state) {
state.focused_item = Focus::Tab(first); state.focused_item = Item::Tab(first);
return event::Status::Captured; return event::Status::Captured;
} }
} }
Focus::None => { Item::None => {
if self.prev_tab_sensitive(state) { if self.prev_tab_sensitive(state) {
state.focused_item = Focus::PrevButton; state.focused_item = Item::PrevButton;
return event::Status::Captured; return event::Status::Captured;
} else if let Some(first) = self.first_tab(state) { } else if let Some(first) = self.first_tab(state) {
state.focused_item = Focus::Tab(first); state.focused_item = Item::Tab(first);
return event::Status::Captured; return event::Status::Captured;
} }
} }
Focus::NextButton | Focus::Set => (), Item::NextButton | Item::Set => (),
} }
state.focused_item = Focus::None; state.focused_item = Item::None;
event::Status::Ignored event::Status::Ignored
} }
@ -460,31 +460,33 @@ where
if state.collapsed { if state.collapsed {
// Check if the prev tab button was clicked. // Check if the prev tab button was clicked.
if cursor_position.is_over(Rectangle { if cursor_position.is_over(Rectangle {
y: bounds.y + 8.0, x: bounds.x,
width: 16.0, y: bounds.y,
..bounds width: f32::from(self.button_height),
}) { height: f32::from(self.button_height),
}) && self.prev_tab_sensitive(state)
{
state.hovered = Item::PrevButton;
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) = event | Event::Touch(touch::Event::FingerLifted { .. }) = event
{ {
if self.prev_tab_sensitive(state) { state.buttons_offset -= 1;
state.buttons_offset -= 1;
}
} }
} else { } else {
// Check if the next tab button was clicked. // Check if the next tab button was clicked.
if cursor_position.is_over(Rectangle { if cursor_position.is_over(Rectangle {
x: bounds.width, x: bounds.width - f32::from(self.button_height) / 4.0 - 8.0,
y: bounds.y + 8.0, y: bounds.y,
width: 16.0, width: f32::from(self.button_height),
..bounds height: f32::from(self.button_height),
}) { }) && self.next_tab_sensitive(state)
{
state.hovered = Item::NextButton;
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) = event | Event::Touch(touch::Event::FingerLifted { .. }) = event
{ {
if self.next_tab_sensitive(state) { state.buttons_offset += 1;
state.buttons_offset += 1;
}
} }
} }
} }
@ -497,7 +499,7 @@ where
if cursor_position.is_over(bounds) { if cursor_position.is_over(bounds) {
if self.model.items[key].enabled { if self.model.items[key].enabled {
// Record that the mouse is hovering over this button. // Record that the mouse is hovering over this button.
state.hovered = key; state.hovered = Item::Tab(key);
// If marked as closable, show a close icon. // If marked as closable, show a close icon.
if self.model.items[key].closable { if self.model.items[key].closable {
@ -594,7 +596,7 @@ where
} }
} }
} else { } else {
state.hovered = Entity::default(); state.hovered = Item::None;
} }
if state.focused { if state.focused {
@ -618,37 +620,37 @@ where
}) = event }) = event
{ {
match state.focused_item { match state.focused_item {
Focus::Tab(entity) => { Item::Tab(entity) => {
shell.publish(on_activate(entity)); shell.publish(on_activate(entity));
} }
Focus::PrevButton => { Item::PrevButton => {
if self.prev_tab_sensitive(state) { if self.prev_tab_sensitive(state) {
state.buttons_offset -= 1; state.buttons_offset -= 1;
// If the change would cause it to be insensitive, focus the first tab. // If the change would cause it to be insensitive, focus the first tab.
if !self.prev_tab_sensitive(state) { if !self.prev_tab_sensitive(state) {
if let Some(first) = self.first_tab(state) { if let Some(first) = self.first_tab(state) {
state.focused_item = Focus::Tab(first); state.focused_item = Item::Tab(first);
} }
} }
} }
} }
Focus::NextButton => { Item::NextButton => {
if self.next_tab_sensitive(state) { if self.next_tab_sensitive(state) {
state.buttons_offset += 1; state.buttons_offset += 1;
// If the change would cause it to be insensitive, focus the last tab. // If the change would cause it to be insensitive, focus the last tab.
if !self.next_tab_sensitive(state) { if !self.next_tab_sensitive(state) {
if let Some(last) = self.last_tab(state) { if let Some(last) = self.last_tab(state) {
state.focused_item = Focus::Tab(last); state.focused_item = Item::Tab(last);
} }
} }
} }
} }
Focus::None | Focus::Set => (), Item::None | Item::Set => (),
} }
return event::Status::Captured; return event::Status::Captured;
@ -671,11 +673,11 @@ where
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
operation.focusable(state, self.id.as_ref().map(|id| &id.0)); operation.focusable(state, self.id.as_ref().map(|id| &id.0));
if let Focus::Set = state.focused_item { if let Item::Set = state.focused_item {
if self.prev_tab_sensitive(state) { if self.prev_tab_sensitive(state) {
state.focused_item = Focus::PrevButton; state.focused_item = Item::PrevButton;
} else if let Some(first) = self.first_tab(state) { } else if let Some(first) = self.first_tab(state) {
state.focused_item = Focus::Tab(first); state.focused_item = Item::Tab(first);
} }
} }
} }
@ -717,6 +719,7 @@ where
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &iced::Rectangle, viewport: &iced::Rectangle,
) { ) {
let cosmic_theme = theme.cosmic();
let state = tree.state.downcast_ref::<LocalState>(); let state = tree.state.downcast_ref::<LocalState>();
let appearance = Self::variant_appearance(theme, &self.style); let appearance = Self::variant_appearance(theme, &self.style);
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -738,22 +741,28 @@ where
// Draw previous and next tab buttons if there is a need to paginate tabs. // Draw previous and next tab buttons if there is a need to paginate tabs.
if state.collapsed { if state.collapsed {
// Previous tab button // Previous tab button
let prev_bounds = Rectangle { let mut background_appearance = if Item::PrevButton == state.focused_item {
y: bounds.y + 8.0, Some(appearance.focus)
width: 16.0, } else if Item::PrevButton == state.hovered {
..bounds Some(appearance.hover)
} else {
None
}; };
if let Focus::PrevButton = state.focused_item { if let Some(background_appearance) = background_appearance.take() {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: prev_bounds, bounds: Rectangle {
border_radius: appearance.focus.first.border_radius, x: bounds.x,
y: bounds.y,
width: f32::from(self.button_height),
height: bounds.height,
},
border_radius: cosmic_theme.radius_s().into(),
border_width: 0.0, border_width: 0.0,
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,
}, },
appearance background_appearance
.focus
.background .background
.unwrap_or(Background::Color(Color::TRANSPARENT)), .unwrap_or(Background::Color(Color::TRANSPARENT)),
); );
@ -767,33 +776,49 @@ where
viewport, viewport,
if state.buttons_offset == 0 { if state.buttons_offset == 0 {
appearance.inactive.text_color appearance.inactive.text_color
} else if let Focus::PrevButton = state.focused_item { } else if let Item::PrevButton = state.focused_item {
appearance.focus.text_color appearance.focus.text_color
} else { } else {
appearance.active.text_color appearance.active.text_color
}, },
prev_bounds, Rectangle {
x: bounds.x + f32::from(self.button_height) / 4.0,
y: bounds.y + f32::from(self.button_height) / 4.0,
width: 16.0,
height: 16.0,
},
icon::from_name("go-previous-symbolic").size(16).icon(), icon::from_name("go-previous-symbolic").size(16).icon(),
); );
// Next tab button // Next tab button
let next_bounds = Rectangle { background_appearance = if Item::NextButton == state.focused_item {
x: bounds.width, Some(appearance.focus)
y: bounds.y + 8.0, } else if Item::NextButton == state.hovered {
width: 16.0, Some(appearance.hover)
..bounds } else {
None
}; };
if let Focus::NextButton = state.focused_item { if let Some(background_appearance) = background_appearance {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: next_bounds, bounds: Rectangle {
border_radius: appearance.focus.last.border_radius, x: bounds.width
- f32::from(self.button_height) / 2.0
- if let Length::Shrink = self.width {
0.0
} else {
8.0
},
y: bounds.y,
width: f32::from(self.button_height),
height: bounds.height,
},
border_radius: cosmic_theme.radius_s().into(),
border_width: 0.0, border_width: 0.0,
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,
}, },
appearance background_appearance
.focus
.background .background
.unwrap_or(Background::Color(Color::TRANSPARENT)), .unwrap_or(Background::Color(Color::TRANSPARENT)),
); );
@ -807,12 +832,23 @@ where
viewport, viewport,
if self.next_tab_sensitive(state) { if self.next_tab_sensitive(state) {
appearance.active.text_color appearance.active.text_color
} else if let Focus::NextButton = state.focused_item { } else if let Item::NextButton = state.focused_item {
appearance.focus.text_color appearance.focus.text_color
} else { } else {
appearance.inactive.text_color appearance.inactive.text_color
}, },
next_bounds, Rectangle {
x: bounds.width
- f32::from(self.button_height) / 4.0
- if let Length::Shrink = self.width {
0.0
} else {
8.0
},
y: bounds.y + f32::from(self.button_height) / 4.0,
width: 16.0,
height: 16.0,
},
icon::from_name("go-next-symbolic").size(16).icon(), icon::from_name("go-next-symbolic").size(16).icon(),
); );
} }
@ -820,9 +856,9 @@ where
// Draw each of the items in the widget. // Draw each of the items in the widget.
for (nth, (key, mut bounds)) in self.variant_button_bounds(state, bounds).enumerate() { for (nth, (key, mut bounds)) in self.variant_button_bounds(state, bounds).enumerate() {
let key_is_active = self.model.is_active(key); let key_is_active = self.model.is_active(key);
let key_is_hovered = state.hovered == key; let key_is_hovered = state.hovered == Item::Tab(key);
let (status_appearance, font) = if Focus::Tab(key) == state.focused_item { let (status_appearance, font) = if Item::Tab(key) == state.focused_item {
(appearance.focus, &self.font_active) (appearance.focus, &self.font_active)
} else if key_is_active { } else if key_is_active {
(appearance.active, &self.font_active) (appearance.active, &self.font_active)
@ -1022,9 +1058,9 @@ pub struct LocalState {
/// If the widget is focused or not. /// If the widget is focused or not.
focused: bool, focused: bool,
/// The key inside the widget that is currently focused. /// The key inside the widget that is currently focused.
focused_item: Focus, focused_item: Item,
/// The ID of the button that is being hovered. Defaults to null. /// The ID of the button that is being hovered. Defaults to null.
hovered: Entity, hovered: Item,
/// Last known length of the model. /// Last known length of the model.
pub(super) known_length: usize, pub(super) known_length: usize,
/// Dimensions of internal buttons when shrinking /// Dimensions of internal buttons when shrinking
@ -1036,7 +1072,7 @@ pub struct LocalState {
} }
#[derive(Default, PartialEq)] #[derive(Default, PartialEq)]
enum Focus { enum Item {
NextButton, NextButton,
#[default] #[default]
None, None,
@ -1052,12 +1088,12 @@ impl operation::Focusable for LocalState {
fn focus(&mut self) { fn focus(&mut self) {
self.focused = true; self.focused = true;
self.focused_item = Focus::Set; self.focused_item = Item::Set;
} }
fn unfocus(&mut self) { fn unfocus(&mut self) {
self.focused = false; self.focused = false;
self.focused_item = Focus::None; self.focused_item = Item::None;
} }
} }