Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

segmented_button: support variable-width buttons when horizontal width is Shrink #282

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 110 additions & 38 deletions src/widget/segmented_button/horizontal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

//! Implementation details for the horizontal layout of a segmented button.

use super::model::{Model, Selectable};
use super::model::{Entity, Model, Selectable};
use super::style::StyleSheet;
use super::widget::{LocalState, SegmentedButton, SegmentedVariant};

use iced::{Length, Rectangle, Size};
use iced_core::layout;
use iced_core::text::Renderer;

/// Horizontal [`SegmentedButton`].
pub type HorizontalSegmentedButton<'a, SelectionMode, Message> =
Expand Down Expand Up @@ -48,34 +49,40 @@ where
&self,
state: &LocalState,
mut bounds: Rectangle,
nth: usize,
) -> Option<Rectangle> {
) -> impl Iterator<Item = (Entity, Rectangle)> {
let num = state.buttons_visible;
let spacing = f32::from(self.spacing);
let mut homogenous_width = 0.0;

// Do not display tabs that are currently hidden due to width constraints.
if state.collapsed && nth < state.buttons_offset {
return None;
}

if num != 0 {
let offset_width;
(bounds.x, offset_width) = if state.collapsed {
(bounds.x + 16.0, 32.0)
} else {
(bounds.x, 0.0)
};

let spacing = f32::from(self.spacing);
bounds.width = ((num as f32).mul_add(-spacing, bounds.width - offset_width) + spacing)
/ num as f32;

if nth != state.buttons_offset {
let pos = (nth - state.buttons_offset) as f32;
bounds.x += pos.mul_add(bounds.width, pos * spacing);
if Length::Shrink != self.width || state.collapsed {
if state.collapsed {
bounds.x += 16.0;
bounds.width -= 32.0;
}

homogenous_width =
((num as f32).mul_add(-spacing, bounds.width) + spacing) / num as f32;
}

Some(bounds)
self.model
.order
.iter()
.copied()
.enumerate()
.skip(state.buttons_offset)
.take(state.buttons_visible)
.map(move |(nth, key)| {
let mut this_bounds = bounds;

if !state.collapsed && Length::Shrink == self.width {
this_bounds.width = state.internal_layout[nth].width;
} else {
this_bounds.width = homogenous_width;
}

bounds.x += this_bounds.width + spacing;
(key, this_bounds)
})
}

#[allow(clippy::cast_precision_loss)]
Expand All @@ -87,27 +94,92 @@ where
renderer: &crate::Renderer,
limits: &layout::Limits,
) -> layout::Node {
let num = self.model.order.len();
let mut total_width = 0.0;
let spacing = f32::from(self.spacing);
let limits = limits.width(self.width);
let (mut width, height) = self.max_button_dimensions(state, renderer, limits.max());
let size;

let num = self.model.items.len();
let spacing = f32::from(self.spacing);
if state.known_length != num {
if state.known_length > num {
state.buttons_offset -= state.buttons_offset.min(state.known_length - num);
} else {
state.buttons_offset += num - state.known_length;
}

if num != 0 {
width = (num as f32).mul_add(width, num as f32 * spacing) - spacing;
state.known_length = num;
}

let size = limits
.height(Length::Fixed(height))
.resolve(Size::new(width, height));
if let Length::Shrink = self.width {
// Buttons will be rendered at their smallest widths possible.
state.internal_layout.clear();

let font = renderer.default_font();
let mut total_height = 0.0f32;

for &button in &self.model.order {
let (mut width, height) = self.button_dimensions(state, font, button);
width = f32::from(self.minimum_button_width).max(width);
total_width += width + spacing;
total_height = total_height.max(height);

let actual_width = size.width as usize;
let minimum_width = self.minimum_button_width as usize * self.model.items.len();
state.internal_layout.push(Size::new(width, height));
}

// Get the max available width for placing buttons into.
let max_size = limits
.height(Length::Fixed(total_height))
.resolve(Size::new(f32::MAX, total_height));

let mut visible_width = 32.0;
state.buttons_visible = 0;

for button_size in &state.internal_layout {
visible_width += button_size.width;

if max_size.width >= visible_width {
state.buttons_visible += 1;
} else {
break;
}

visible_width += spacing;
}

state.collapsed = num > 1 && state.buttons_visible != num;

// If collapsed, use the maximum width available.
visible_width = if state.collapsed {
max_size.width - 32.0
} else {
total_width
};

size = limits
.height(Length::Fixed(total_height))
.resolve(Size::new(visible_width, total_height));
} else {
// Buttons will be rendered with equal widths.
state.buttons_visible = self.model.items.len();
let (width, height) = self.max_button_dimensions(state, renderer, limits.max());
let total_width = (state.buttons_visible as f32) * (width + spacing);

size = limits
.height(Length::Fixed(height))
.resolve(Size::new(total_width, height));

let actual_width = size.width as usize;
let minimum_width = state.buttons_visible * self.minimum_button_width as usize;
state.collapsed = actual_width < minimum_width;

if state.collapsed {
state.buttons_visible =
(actual_width / self.minimum_button_width as usize).min(state.buttons_visible);
}
}

state.buttons_visible = num;
state.collapsed = actual_width < minimum_width;
if state.collapsed {
state.buttons_visible = (actual_width / self.minimum_button_width as usize).min(num);
if !state.collapsed {
state.buttons_offset = 0;
}

layout::Node::new(size)
Expand Down
31 changes: 17 additions & 14 deletions src/widget/segmented_button/vertical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

//! Implementation details for the vertical layout of a segmented button.

use super::model::{Model, Selectable};
use super::model::{Entity, Model, Selectable};
use super::style::StyleSheet;
use super::widget::{LocalState, SegmentedButton, SegmentedVariant};

Expand Down Expand Up @@ -47,21 +47,22 @@ where
#[allow(clippy::cast_precision_loss)]
fn variant_button_bounds(
&self,
_state: &LocalState,
state: &LocalState,
mut bounds: Rectangle,
nth: usize,
) -> Option<Rectangle> {
let num = self.model.items.len();
if num != 0 {
let spacing = f32::from(self.spacing);
bounds.height = (bounds.height - (num as f32 * spacing) + spacing) / num as f32;

if nth != 0 {
bounds.y += (nth as f32 * bounds.height) + (nth as f32 * spacing);
}
}
) -> impl Iterator<Item = (Entity, Rectangle)> {
let spacing = f32::from(self.spacing);

Some(bounds)
self.model
.order
.iter()
.copied()
.enumerate()
.map(move |(_nth, key)| {
let mut this_bounds = bounds;
this_bounds.height = state.internal_layout[0].height;
bounds.y += this_bounds.height + spacing;
(key, this_bounds)
})
}

#[allow(clippy::cast_precision_loss)]
Expand All @@ -73,8 +74,10 @@ where
renderer: &crate::Renderer,
limits: &layout::Limits,
) -> layout::Node {
state.internal_layout.clear();
let limits = limits.width(self.width);
let (width, mut height) = self.max_button_dimensions(state, renderer, limits.max());
state.internal_layout.push(Size::new(width, height));

let num = self.model.items.len();
let spacing = f32::from(self.spacing);
Expand Down
Loading
Loading