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