1use rgpui::{
2 AnyElement, App, Axis, ClickEvent, Half, InteractiveElement as _, IntoElement, ParentElement,
3 Pixels, RenderOnce, StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, px,
4 relative,
5};
6
7use crate::{
8 ActiveTheme as _, AxisExt, Icon, Sizable, Size, StyledExt as _,
9 stepper::trigger::StepperTrigger,
10};
11
12#[derive(IntoElement)]
14pub struct StepperItem {
15 step: usize,
16 checked_step: usize,
17 style: StyleRefinement,
18 icon: Option<Icon>,
19 children: Vec<AnyElement>,
20 layout: Axis,
21 disabled: bool,
22 size: Size,
23 is_last: bool,
24 text_center: bool,
25 on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
26}
27
28impl StepperItem {
29 pub fn new() -> Self {
30 Self {
31 step: 0,
32 checked_step: 0,
33 style: StyleRefinement::default(),
34 icon: None,
35 layout: Axis::Horizontal,
36 disabled: false,
37 size: Size::default(),
38 is_last: false,
39 text_center: false,
40 children: Vec::new(),
41 on_click: Box::new(|_, _, _| {}),
42 }
43 }
44
45 pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
47 self.icon = Some(icon.into());
48 self
49 }
50
51 pub fn disabled(mut self, disabled: bool) -> Self {
57 self.disabled = disabled;
58 self
59 }
60
61 pub(super) fn text_center(mut self, center: bool) -> Self {
62 self.text_center = center;
63 self
64 }
65
66 pub(super) fn step(mut self, ix: usize) -> Self {
67 self.step = ix;
68 self
69 }
70
71 pub(super) fn checked_step(mut self, checked_step: usize) -> Self {
72 self.checked_step = checked_step;
73 self
74 }
75
76 pub(super) fn layout(mut self, layout: Axis) -> Self {
77 self.layout = layout;
78 self
79 }
80
81 pub(super) fn is_last(mut self, is_last: bool) -> Self {
82 self.is_last = is_last;
83 self
84 }
85
86 pub(super) fn on_click<F>(mut self, f: F) -> Self
87 where
88 F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
89 {
90 self.on_click = Box::new(f);
91 self
92 }
93}
94
95impl ParentElement for StepperItem {
96 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
97 self.children.extend(elements);
98 }
99}
100
101impl Sizable for StepperItem {
102 fn with_size(mut self, size: impl Into<Size>) -> Self {
103 self.size = size.into();
104 self
105 }
106}
107
108impl Styled for StepperItem {
109 fn style(&mut self) -> &mut StyleRefinement {
110 &mut self.style
111 }
112}
113
114impl RenderOnce for StepperItem {
115 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
116 let is_passed = self.step < self.checked_step;
117 let icon_size = match self.size {
118 Size::XSmall => px(8.),
119 Size::Small => px(18.),
120 Size::Large => px(32.),
121 _ => px(24.),
122 };
123
124 div()
125 .id(("stepper-item", self.step))
126 .relative()
127 .when(self.layout.is_horizontal(), |this| this.h_flex())
128 .when(self.layout.is_vertical(), |this| this.v_flex())
129 .when(!self.is_last, |this| this.flex_1())
130 .when(self.text_center, |this| this.flex_1().justify_center())
131 .items_start()
132 .refine_style(&self.style)
133 .child(
134 StepperTrigger::new()
135 .icon(self.icon)
136 .icon_size(icon_size)
137 .step(self.step)
138 .with_size(self.size)
139 .checked_step(self.checked_step)
140 .text_center(self.text_center)
141 .layout(self.layout)
142 .disabled(self.disabled)
143 .children(self.children)
144 .on_click({
145 let on_click = self.on_click;
146 move |e, window, cx| {
147 on_click(e, window, cx);
148 }
149 }),
150 )
151 .when(!self.is_last, |this| {
152 this.child(
153 StepperSeparator::new()
154 .with_size(self.size)
155 .layout(self.layout)
156 .text_center(self.text_center)
157 .icon_size(icon_size)
158 .checked(is_passed),
159 )
160 })
161 }
162}
163
164#[derive(IntoElement)]
168struct StepperSeparator {
169 size: Size,
170 checked: bool,
171 icon_size: Pixels,
172 layout: Axis,
173 style: StyleRefinement,
174 text_center: bool,
175}
176
177impl StepperSeparator {
178 fn new() -> Self {
179 Self {
180 size: Size::default(),
181 checked: false,
182 icon_size: px(24.),
183 layout: Axis::Horizontal,
184 style: StyleRefinement::default(),
185 text_center: false,
186 }
187 }
188
189 fn with_size(mut self, size: Size) -> Self {
190 self.size = size;
191 self
192 }
193
194 fn text_center(mut self, center: bool) -> Self {
195 self.text_center = center;
196 self
197 }
198
199 fn layout(mut self, layout: Axis) -> Self {
200 self.layout = layout;
201 self
202 }
203
204 fn icon_size(mut self, size: Pixels) -> Self {
205 self.icon_size = size;
206 self
207 }
208
209 fn checked(mut self, checked: bool) -> Self {
210 self.checked = checked;
211 self
212 }
213}
214
215impl Styled for StepperSeparator {
216 fn style(&mut self) -> &mut StyleRefinement {
217 &mut self.style
218 }
219}
220
221impl RenderOnce for StepperSeparator {
222 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
223 let icon_size = self.icon_size;
224 let text_center = self.text_center;
225 let separator_wide = match self.size {
226 Size::XSmall => px(1.5),
227 Size::Large => px(3.),
228 _ => px(2.),
229 };
230
231 let gap = px(4.);
232
233 div()
234 .absolute()
235 .flex_1()
236 .when(self.layout.is_horizontal(), |this| {
237 this.h(separator_wide).mt(icon_size.half()).map(|this| {
238 if !text_center {
239 this.ml(icon_size + gap).mr(gap).left_0().right_0()
240 } else {
241 this.mx(icon_size.half() + gap)
242 .left(relative(0.5))
243 .right(relative(-0.5))
244 }
245 })
246 })
247 .when(self.layout.is_vertical(), |this| {
248 this.w(separator_wide).ml(icon_size.half()).map(|this| {
249 if !text_center {
250 this.mt(icon_size + gap).mb(gap).top_0().bottom_0()
251 } else {
252 this.mx(icon_size.half() + gap)
253 .top(relative(0.5))
254 .bottom(relative(-0.5))
255 }
256 })
257 })
258 .refine_style(&self.style)
259 .bg(cx.theme().border)
260 .when(self.checked, |this| this.bg(cx.theme().primary))
261 }
262}