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;
if Length::Shrink != self.width || state.collapsed {
let mut width_offset = 0.0;
if state.collapsed {
bounds.x += 16.0;
bounds.width -= 32.0;
bounds.x += f32::from(self.button_height);
width_offset = f32::from(self.button_height) * 2.0;
}
homogenous_width =
((num as f32).mul_add(-spacing, bounds.width) + spacing) / num as f32;
homogenous_width = ((num as f32).mul_add(-spacing, bounds.width - width_offset)
+ spacing)
/ num as f32;
}
self.model
@ -98,7 +100,7 @@ where
let mut total_width = 0.0;
let spacing = f32::from(self.spacing);
let limits = limits.width(self.width);
let size;
let mut size;
if state.known_length != num {
if state.known_length > num {
@ -131,7 +133,7 @@ where
.height(Length::Fixed(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;
for button_size in &state.internal_layout {
@ -150,12 +152,13 @@ where
// If collapsed, use the maximum width available.
visible_width = if state.collapsed {
max_size.width - 32.0
max_size.width - f32::from(self.button_height)
} else {
total_width
};
size = limits
.width(Length::Fixed(visible_width))
.height(Length::Fixed(total_height))
.resolve(Size::new(visible_width, total_height));
} else {
@ -173,6 +176,10 @@ where
state.collapsed = actual_width < minimum_width;
if state.collapsed {
size = limits
.height(Length::Fixed(height))
.resolve(Size::new(f32::MAX, height));
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)
}
/// Focus the previous item in the widget.
/// Item the previous item in the widget.
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
match state.focused_item {
Focus::Tab(entity) => {
Item::Tab(entity) => {
let mut keys = self.iterate_visible_tabs(state).rev();
while let Some(key) = keys.next() {
@ -173,7 +173,7 @@ where
continue;
}
state.focused_item = Focus::Tab(key);
state.focused_item = Item::Tab(key);
return event::Status::Captured;
}
@ -182,39 +182,39 @@ where
}
if self.prev_tab_sensitive(state) {
state.focused_item = Focus::PrevButton;
state.focused_item = Item::PrevButton;
return event::Status::Captured;
}
}
Focus::NextButton => {
Item::NextButton => {
if let Some(last) = self.last_tab(state) {
state.focused_item = Focus::Tab(last);
state.focused_item = Item::Tab(last);
return event::Status::Captured;
}
}
Focus::None => {
Item::None => {
if self.next_tab_sensitive(state) {
state.focused_item = Focus::NextButton;
state.focused_item = Item::NextButton;
return event::Status::Captured;
} 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;
}
}
Focus::PrevButton | Focus::Set => (),
Item::PrevButton | Item::Set => (),
}
state.focused_item = Focus::None;
state.focused_item = Item::None;
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 {
match state.focused_item {
Focus::Tab(entity) => {
Item::Tab(entity) => {
let mut keys = self.iterate_visible_tabs(state);
while let Some(key) = keys.next() {
if key == entity {
@ -224,7 +224,7 @@ where
continue;
}
state.focused_item = Focus::Tab(key);
state.focused_item = Item::Tab(key);
return event::Status::Captured;
}
@ -233,32 +233,32 @@ where
}
if self.next_tab_sensitive(state) {
state.focused_item = Focus::NextButton;
state.focused_item = Item::NextButton;
return event::Status::Captured;
}
}
Focus::PrevButton => {
Item::PrevButton => {
if let Some(first) = self.first_tab(state) {
state.focused_item = Focus::Tab(first);
state.focused_item = Item::Tab(first);
return event::Status::Captured;
}
}
Focus::None => {
Item::None => {
if self.prev_tab_sensitive(state) {
state.focused_item = Focus::PrevButton;
state.focused_item = Item::PrevButton;
return event::Status::Captured;
} 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;
}
}
Focus::NextButton | Focus::Set => (),
Item::NextButton | Item::Set => (),
}
state.focused_item = Focus::None;
state.focused_item = Item::None;
event::Status::Ignored
}
@ -460,31 +460,33 @@ where
if state.collapsed {
// Check if the prev tab button was clicked.
if cursor_position.is_over(Rectangle {
y: bounds.y + 8.0,
width: 16.0,
..bounds
}) {
x: bounds.x,
y: bounds.y,
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))
| Event::Touch(touch::Event::FingerLifted { .. }) = event
{
if self.prev_tab_sensitive(state) {
state.buttons_offset -= 1;
}
state.buttons_offset -= 1;
}
} else {
// Check if the next tab button was clicked.
if cursor_position.is_over(Rectangle {
x: bounds.width,
y: bounds.y + 8.0,
width: 16.0,
..bounds
}) {
x: bounds.width - f32::from(self.button_height) / 4.0 - 8.0,
y: bounds.y,
width: f32::from(self.button_height),
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))
| 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 self.model.items[key].enabled {
// 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 self.model.items[key].closable {
@ -594,7 +596,7 @@ where
}
}
} else {
state.hovered = Entity::default();
state.hovered = Item::None;
}
if state.focused {
@ -618,37 +620,37 @@ where
}) = event
{
match state.focused_item {
Focus::Tab(entity) => {
Item::Tab(entity) => {
shell.publish(on_activate(entity));
}
Focus::PrevButton => {
Item::PrevButton => {
if self.prev_tab_sensitive(state) {
state.buttons_offset -= 1;
// If the change would cause it to be insensitive, focus the first tab.
if !self.prev_tab_sensitive(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) {
state.buttons_offset += 1;
// If the change would cause it to be insensitive, focus the last tab.
if !self.next_tab_sensitive(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;
@ -671,11 +673,11 @@ where
let state = tree.state.downcast_mut::<LocalState>();
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) {
state.focused_item = Focus::PrevButton;
state.focused_item = Item::PrevButton;
} 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,
viewport: &iced::Rectangle,
) {
let cosmic_theme = theme.cosmic();
let state = tree.state.downcast_ref::<LocalState>();
let appearance = Self::variant_appearance(theme, &self.style);
let bounds = layout.bounds();
@ -738,22 +741,28 @@ where
// Draw previous and next tab buttons if there is a need to paginate tabs.
if state.collapsed {
// Previous tab button
let prev_bounds = Rectangle {
y: bounds.y + 8.0,
width: 16.0,
..bounds
let mut background_appearance = if Item::PrevButton == state.focused_item {
Some(appearance.focus)
} else if Item::PrevButton == state.hovered {
Some(appearance.hover)
} else {
None
};
if let Focus::PrevButton = state.focused_item {
if let Some(background_appearance) = background_appearance.take() {
renderer.fill_quad(
renderer::Quad {
bounds: prev_bounds,
border_radius: appearance.focus.first.border_radius,
bounds: Rectangle {
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_color: Color::TRANSPARENT,
},
appearance
.focus
background_appearance
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
@ -767,33 +776,49 @@ where
viewport,
if state.buttons_offset == 0 {
appearance.inactive.text_color
} else if let Focus::PrevButton = state.focused_item {
} else if let Item::PrevButton = state.focused_item {
appearance.focus.text_color
} else {
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(),
);
// Next tab button
let next_bounds = Rectangle {
x: bounds.width,
y: bounds.y + 8.0,
width: 16.0,
..bounds
background_appearance = if Item::NextButton == state.focused_item {
Some(appearance.focus)
} else if Item::NextButton == state.hovered {
Some(appearance.hover)
} else {
None
};
if let Focus::NextButton = state.focused_item {
if let Some(background_appearance) = background_appearance {
renderer.fill_quad(
renderer::Quad {
bounds: next_bounds,
border_radius: appearance.focus.last.border_radius,
bounds: Rectangle {
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_color: Color::TRANSPARENT,
},
appearance
.focus
background_appearance
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
@ -807,12 +832,23 @@ where
viewport,
if self.next_tab_sensitive(state) {
appearance.active.text_color
} else if let Focus::NextButton = state.focused_item {
} else if let Item::NextButton = state.focused_item {
appearance.focus.text_color
} else {
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(),
);
}
@ -820,9 +856,9 @@ where
// Draw each of the items in the widget.
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_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)
} else if key_is_active {
(appearance.active, &self.font_active)
@ -1022,9 +1058,9 @@ pub struct LocalState {
/// If the widget is focused or not.
focused: bool,
/// 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.
hovered: Entity,
hovered: Item,
/// Last known length of the model.
pub(super) known_length: usize,
/// Dimensions of internal buttons when shrinking
@ -1036,7 +1072,7 @@ pub struct LocalState {
}
#[derive(Default, PartialEq)]
enum Focus {
enum Item {
NextButton,
#[default]
None,
@ -1052,12 +1088,12 @@ impl operation::Focusable for LocalState {
fn focus(&mut self) {
self.focused = true;
self.focused_item = Focus::Set;
self.focused_item = Item::Set;
}
fn unfocus(&mut self) {
self.focused = false;
self.focused_item = Focus::None;
self.focused_item = Item::None;
}
}