repose_material/material3/
segmented_button.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use repose_core::*;
7use repose_ui::{
8 Box, Row, Text, TextStyle,
9 ViewExt,
10 anim::animate_color,
11};
12
13use super::*;
14
15#[derive(Clone)]
17pub struct SegmentConfig {
18 pub label: String,
19 pub icon: Option<View>,
20 pub on_click: Rc<dyn Fn()>,
21 pub enabled: bool,
22 pub interaction_source: Option<MutableInteractionSource>,
23}
24
25impl Default for SegmentConfig {
26 fn default() -> Self {
27 Self {
28 label: String::new(),
29 icon: None,
30 on_click: Rc::new(|| {}),
31 enabled: true,
32 interaction_source: None,
33 }
34 }
35}
36
37#[derive(Clone, Debug)]
39pub struct SegmentedButtonConfig {
40 pub modifier: Modifier,
41 pub border_color: Color,
42 pub selected_container_color: Color,
43 pub selected_content_color: Color,
44 pub unselected_content_color: Color,
45 pub state_colors: StateColors,
46 pub height: f32,
47 pub shape_radius: f32,
48 pub content_padding: PaddingValues,
49}
50
51impl Default for SegmentedButtonConfig {
52 fn default() -> Self {
53 Self {
54 modifier: Modifier::new(),
55 border_color: SegmentedButtonDefaults::border_color(),
56 selected_container_color: SegmentedButtonDefaults::selected_container_color(),
57 selected_content_color: SegmentedButtonDefaults::selected_content_color(),
58 unselected_content_color: SegmentedButtonDefaults::unselected_content_color(),
59 state_colors: SegmentedButtonDefaults::state_colors_default(),
60 height: SegmentedButtonDefaults::HEIGHT,
61 shape_radius: SegmentedButtonDefaults::SHAPE_RADIUS,
62 content_padding: SegmentedButtonDefaults::CONTENT_PADDING,
63 }
64 }
65}
66
67static SEGBUTTON_COUNTER: AtomicU64 = AtomicU64::new(0);
68
69pub fn SegmentedButton(
74 selected: &[usize],
75 segments: Vec<SegmentConfig>,
76 config: SegmentedButtonConfig,
77) -> View {
78 let th = theme();
79 let count = segments.len();
80 let id = remember(|| SEGBUTTON_COUNTER.fetch_add(1, Ordering::Relaxed));
81 let spec = th.motion.color;
82 let shape_r = config.shape_radius;
83
84 let segment_radii = |i: usize| -> [f32; 4] {
86 if count == 1 {
87 [shape_r, shape_r, shape_r, shape_r]
88 } else if i == 0 {
89 [shape_r, 0.0, 0.0, shape_r]
90 } else if i == count - 1 {
91 [0.0, shape_r, shape_r, 0.0]
92 } else {
93 [0.0, 0.0, 0.0, 0.0]
94 }
95 };
96
97 Row(Modifier::new()
99 .height(config.height)
100 .border(1.0, config.border_color, shape_r)
101 .then(config.modifier))
102 .child(
103 segments
104 .into_iter()
105 .enumerate()
106 .map(|(i, seg)| {
107 let is_selected = selected.contains(&i);
108
109 let bg = animate_color(
110 format!("sb_bg_{}_{}", id, i),
111 if is_selected {
112 config.selected_container_color
113 } else {
114 Color::TRANSPARENT
115 },
116 spec,
117 );
118 let fg = animate_color(
119 format!("sb_fg_{}_{}", id, i),
120 if is_selected {
121 config.selected_content_color
122 } else {
123 config.unselected_content_color
124 },
125 spec,
126 );
127
128 let cb = seg.on_click.clone();
129 let radii = segment_radii(i);
130 let is_enabled = seg.enabled;
131 let seg_source: Rc<MutableInteractionSource> = seg
132 .interaction_source
133 .clone()
134 .map(Rc::new)
135 .unwrap_or_else(|| remember(MutableInteractionSource::new));
136
137 let state_colors = config.state_colors;
138 let content_modifier = Modifier::new()
139 .flex_grow(1.0)
140 .fill_max_height()
141 .clip_rounded_radii(radii)
142 .background(bg)
143 .state_colors(state_colors)
144 .interaction_source(&*seg_source)
145 .align_items(AlignItems::CENTER)
146 .justify_content(JustifyContent::CENTER)
147 .padding_values(config.content_padding);
148
149 let content_modifier = if is_enabled {
150 content_modifier.clickable().on_click(move || cb())
151 } else {
152 content_modifier
153 };
154
155 Row(Modifier::new().flex_grow(1.0).fill_max_height()).child((
156 Row(content_modifier).child((
157 seg.icon.unwrap_or(Box(Modifier::new())),
158 Text(seg.label)
159 .color(fg)
160 .size(th.typography.label_large)
161 .single_line(),
162 )),
163 if i < count - 1 {
164 Box(Modifier::new()
165 .width(1.0)
166 .fill_max_height()
167 .background(th.outline))
168 } else {
169 Box(Modifier::new())
170 },
171 ))
172 })
173 .collect::<Vec<_>>(),
174 )
175}