1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4use std::sync::atomic::Ordering;
5
6use repose_core::*;
7use repose_ui::{
8 Box, Row,
9 ViewExt,
10 anim::animate_color,
11};
12
13use super::*;
14use super::util::FILTERCHIP_COUNTER;
15
16#[derive(Clone, Copy, Debug)]
18pub struct ChipColors {
19 pub container_color: Color,
20 pub label_color: Color,
21 pub leading_icon_color: Color,
22 pub trailing_icon_color: Color,
23 pub disabled_container_color: Color,
24 pub disabled_label_color: Color,
25 pub disabled_leading_icon_color: Color,
26 pub disabled_trailing_icon_color: Color,
27 pub selected_container_color: Color,
28 pub selected_label_color: Color,
29 pub selected_leading_icon_color: Color,
30 pub selected_trailing_icon_color: Color,
31 pub disabled_selected_container_color: Color,
32}
33
34impl ChipColors {
35 pub fn container(&self, enabled: bool, selected: bool) -> Color {
36 match (enabled, selected) {
37 (true, true) => self.selected_container_color,
38 (true, false) => self.container_color,
39 (false, true) => self.disabled_selected_container_color,
40 (false, false) => self.disabled_container_color,
41 }
42 }
43 pub fn label(&self, enabled: bool, selected: bool) -> Color {
44 if !enabled {
45 self.disabled_label_color
46 } else if selected {
47 self.selected_label_color
48 } else {
49 self.label_color
50 }
51 }
52 pub fn leading_icon(&self, enabled: bool, selected: bool) -> Color {
53 if !enabled {
54 self.disabled_leading_icon_color
55 } else if selected {
56 self.selected_leading_icon_color
57 } else {
58 self.leading_icon_color
59 }
60 }
61 pub fn trailing_icon(&self, enabled: bool, selected: bool) -> Color {
62 if !enabled {
63 self.disabled_trailing_icon_color
64 } else if selected {
65 self.selected_trailing_icon_color
66 } else {
67 self.trailing_icon_color
68 }
69 }
70}
71
72#[derive(Clone, Copy, Debug)]
74pub struct ChipElevation {
75 pub default: f32,
76 pub hovered: f32,
77 pub focused: f32,
78 pub pressed: f32,
79 pub dragged: f32,
80 pub disabled: f32,
81}
82
83impl ChipElevation {
84 pub fn to_state_elevation(&self) -> StateElevation {
85 StateElevation {
86 default: self.default,
87 hovered: self.hovered,
88 pressed: self.pressed,
89 disabled: self.disabled,
90 }
91 }
92}
93
94impl Default for ChipElevation {
95 fn default() -> Self {
96 Self {
97 default: ChipDefaults::elevation_default(),
98 hovered: ChipDefaults::elevation_hovered(),
99 focused: ChipDefaults::elevation_focused(),
100 pressed: ChipDefaults::elevation_pressed(),
101 dragged: ChipDefaults::elevation_dragged(),
102 disabled: ChipDefaults::elevation_disabled(),
103 }
104 }
105}
106
107#[derive(Clone, Debug)]
109pub struct ChipConfig {
110 pub modifier: Modifier,
111 pub enabled: bool,
112 pub colors: ChipColors,
113 pub elevation: ChipElevation,
114 pub border_width: f32,
115 pub border_color: Color,
116 pub selected_border_color: Color,
117 pub disabled_border_color: Color,
118 pub disabled_selected_border_color: Color,
119 pub shape_radius: f32,
120 pub horizontal_padding: f32,
121 pub interaction_source: Option<MutableInteractionSource>,
122}
123
124impl Default for ChipConfig {
125 fn default() -> Self {
126 Self {
127 modifier: Modifier::new(),
128 enabled: true,
129 colors: ChipColors {
130 container_color: ChipDefaults::container_color(),
131 label_color: ChipDefaults::label_color(),
132 leading_icon_color: ChipDefaults::leading_icon_color(),
133 trailing_icon_color: ChipDefaults::trailing_icon_color(),
134 disabled_container_color: ChipDefaults::disabled_container_color(),
135 disabled_label_color: ChipDefaults::disabled_label_color(),
136 disabled_leading_icon_color: ChipDefaults::disabled_leading_icon_color(),
137 disabled_trailing_icon_color: ChipDefaults::disabled_trailing_icon_color(),
138 selected_container_color: ChipDefaults::selected_container_color(),
139 selected_label_color: ChipDefaults::selected_label_color(),
140 selected_leading_icon_color: ChipDefaults::selected_leading_icon_color(),
141 selected_trailing_icon_color: ChipDefaults::selected_trailing_icon_color(),
142 disabled_selected_container_color: ChipDefaults::disabled_selected_container_color(
143 ),
144 },
145 elevation: ChipElevation::default(),
146 border_width: ChipDefaults::BORDER_WIDTH,
147 border_color: ChipDefaults::border_color(),
148 selected_border_color: ChipDefaults::selected_border_color(),
149 disabled_border_color: ChipDefaults::disabled_border_color(),
150 disabled_selected_border_color: ChipDefaults::disabled_selected_border_color(),
151 shape_radius: ChipDefaults::SHAPE_RADIUS,
152 horizontal_padding: ChipDefaults::HORIZONTAL_PADDING,
153 interaction_source: None,
154 }
155 }
156}
157
158pub fn AssistChip(
160 on_click: impl Fn() + 'static,
161 label: View,
162 leading_icon: Option<View>,
163 trailing_icon: Option<View>,
164 config: ChipConfig,
165) -> View {
166 let th = theme();
167 let is_enabled = config.enabled;
168 let colors = &config.colors;
169 let bg = colors.container(is_enabled, false);
170 let label_color = colors.label(is_enabled, false);
171 let leading_color = colors.leading_icon(is_enabled, false);
172 let trailing_color = colors.trailing_icon(is_enabled, false);
173 let border = if is_enabled {
174 config.border_color
175 } else {
176 config.disabled_border_color
177 };
178 let shape = config.shape_radius;
179 let ch_source: Rc<MutableInteractionSource> = config
180 .interaction_source
181 .clone()
182 .map(Rc::new)
183 .unwrap_or_else(|| remember(MutableInteractionSource::new));
184
185 let mut m = Modifier::new()
186 .state_colors(StateColors {
187 default: Color::TRANSPARENT,
188 hovered: th.on_surface.with_alpha_f32(0.08),
189 pressed: th.on_surface.with_alpha_f32(0.12),
190 disabled: Color::TRANSPARENT,
191 })
192 .padding_values(PaddingValues {
193 left: config.horizontal_padding,
194 right: config.horizontal_padding,
195 top: 8.0,
196 bottom: 8.0,
197 })
198 .background(bg)
199 .clip_rounded(shape)
200 .interaction_source(&*ch_source)
201 .then(config.modifier);
202
203 if config.border_width > 0.0 && border != Color::TRANSPARENT {
204 m = m.border(config.border_width, border, shape);
205 }
206
207 if is_enabled {
208 m = m.clickable().on_click(move || on_click());
209 }
210
211 Box(m).child(
212 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
213 leading_icon
214 .map(|v| {
215 Box(Modifier::new().padding_values(PaddingValues {
216 left: 0.0,
217 right: 8.0,
218 top: 0.0,
219 bottom: 0.0,
220 }))
221 .child(with_content_color(leading_color, move || v))
222 })
223 .unwrap_or(Box(Modifier::new())),
224 with_content_color(label_color, move || label),
225 trailing_icon
226 .map(|v| {
227 Box(Modifier::new().padding_values(PaddingValues {
228 left: 8.0,
229 right: 0.0,
230 top: 0.0,
231 bottom: 0.0,
232 }))
233 .child(with_content_color(trailing_color, move || v))
234 })
235 .unwrap_or(Box(Modifier::new())),
236 )),
237 )
238}
239
240pub fn ElevatedAssistChip(
242 on_click: impl Fn() + 'static,
243 label: View,
244 leading_icon: Option<View>,
245 trailing_icon: Option<View>,
246 config: ChipConfig,
247) -> View {
248 let th = theme();
249 let is_enabled = config.enabled;
250 let colors = &config.colors;
251 let bg = colors.container(is_enabled, false);
252 let label_color = colors.label(is_enabled, false);
253 let leading_color = colors.leading_icon(is_enabled, false);
254 let trailing_color = colors.trailing_icon(is_enabled, false);
255 let shape = config.shape_radius;
256 let ch_source: Rc<MutableInteractionSource> = config
257 .interaction_source
258 .clone()
259 .map(Rc::new)
260 .unwrap_or_else(|| remember(MutableInteractionSource::new));
261
262 let mut m = Modifier::new()
263 .state_colors(StateColors {
264 default: Color::TRANSPARENT,
265 hovered: th.on_surface.with_alpha_f32(0.08),
266 pressed: th.on_surface.with_alpha_f32(0.12),
267 disabled: Color::TRANSPARENT,
268 })
269 .state_elevation(config.elevation.to_state_elevation())
270 .padding_values(PaddingValues {
271 left: config.horizontal_padding,
272 right: config.horizontal_padding,
273 top: 8.0,
274 bottom: 8.0,
275 })
276 .background(bg)
277 .clip_rounded(shape)
278 .interaction_source(&*ch_source)
279 .then(config.modifier);
280
281 if is_enabled {
282 m = m.clickable().on_click(move || on_click());
283 }
284
285 Box(m).child(
286 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
287 leading_icon
288 .map(|v| {
289 Box(Modifier::new().padding_values(PaddingValues {
290 left: 0.0,
291 right: 8.0,
292 top: 0.0,
293 bottom: 0.0,
294 }))
295 .child(with_content_color(leading_color, move || v))
296 })
297 .unwrap_or(Box(Modifier::new())),
298 with_content_color(label_color, move || label),
299 trailing_icon
300 .map(|v| {
301 Box(Modifier::new().padding_values(PaddingValues {
302 left: 8.0,
303 right: 0.0,
304 top: 0.0,
305 bottom: 0.0,
306 }))
307 .child(with_content_color(trailing_color, move || v))
308 })
309 .unwrap_or(Box(Modifier::new())),
310 )),
311 )
312}
313
314pub fn FilterChip(
315 selected: bool,
316 on_click: impl Fn() + 'static,
317 label: View,
318 leading_icon: Option<View>,
319 trailing_icon: Option<View>,
320 config: ChipConfig,
321) -> View {
322 let th = theme();
323 let id = remember(|| FILTERCHIP_COUNTER.fetch_add(1, Ordering::Relaxed));
324 let spec = th.motion.color;
325 let is_enabled = config.enabled;
326 let colors = &config.colors;
327
328 let bg = animate_color(
329 format!("fc_bg_{}", id),
330 colors.container(is_enabled, selected),
331 spec,
332 );
333 let label_color = animate_color(
334 format!("fc_lc_{}", id),
335 colors.label(is_enabled, selected),
336 spec,
337 );
338 let leading_color = animate_color(
339 format!("fc_lic_{}", id),
340 colors.leading_icon(is_enabled, selected),
341 spec,
342 );
343 let trailing_color = animate_color(
344 format!("fc_tic_{}", id),
345 colors.trailing_icon(is_enabled, selected),
346 spec,
347 );
348 let border = if !is_enabled {
349 if selected {
350 config.disabled_selected_border_color
351 } else {
352 config.disabled_border_color
353 }
354 } else {
355 if selected {
356 config.selected_border_color
357 } else {
358 config.border_color
359 }
360 };
361 let shape = config.shape_radius;
362
363 let ch_source: Rc<MutableInteractionSource> = config
364 .interaction_source
365 .clone()
366 .map(Rc::new)
367 .unwrap_or_else(|| remember(MutableInteractionSource::new));
368
369 let mut m = Modifier::new()
370 .state_colors(StateColors {
371 default: Color::TRANSPARENT,
372 hovered: th.on_surface.with_alpha_f32(0.08),
373 pressed: th.on_surface.with_alpha_f32(0.12),
374 disabled: Color::TRANSPARENT,
375 })
376 .padding_values(PaddingValues {
377 left: config.horizontal_padding,
378 right: config.horizontal_padding,
379 top: 8.0,
380 bottom: 8.0,
381 })
382 .background(bg)
383 .clip_rounded(shape)
384 .interaction_source(&*ch_source)
385 .then(config.modifier);
386
387 if config.border_width > 0.0 && border != Color::TRANSPARENT {
388 m = m.border(config.border_width, border, shape);
389 }
390 if is_enabled {
391 m = m.clickable().on_click(move || on_click());
392 }
393
394 Box(m).child(
395 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
396 leading_icon
397 .map(|v| {
398 Box(Modifier::new().padding_values(PaddingValues {
399 left: 0.0,
400 right: 8.0,
401 top: 0.0,
402 bottom: 0.0,
403 }))
404 .child(with_content_color(leading_color, move || v))
405 })
406 .unwrap_or(Box(Modifier::new())),
407 with_content_color(label_color, move || label),
408 trailing_icon
409 .map(|v| {
410 Box(Modifier::new().padding_values(PaddingValues {
411 left: 8.0,
412 right: 0.0,
413 top: 0.0,
414 bottom: 0.0,
415 }))
416 .child(with_content_color(trailing_color, move || v))
417 })
418 .unwrap_or(Box(Modifier::new())),
419 )),
420 )
421}
422
423pub fn ElevatedFilterChip(
425 selected: bool,
426 on_click: impl Fn() + 'static,
427 label: View,
428 leading_icon: Option<View>,
429 trailing_icon: Option<View>,
430 config: ChipConfig,
431) -> View {
432 let th = theme();
433 let id = remember(|| FILTERCHIP_COUNTER.fetch_add(1, Ordering::Relaxed));
434 let spec = th.motion.color;
435 let is_enabled = config.enabled;
436 let colors = &config.colors;
437
438 let bg = animate_color(
439 format!("efc_bg_{}", id),
440 colors.container(is_enabled, selected),
441 spec,
442 );
443 let label_color = animate_color(
444 format!("efc_lc_{}", id),
445 colors.label(is_enabled, selected),
446 spec,
447 );
448 let leading_color = animate_color(
449 format!("efc_lic_{}", id),
450 colors.leading_icon(is_enabled, selected),
451 spec,
452 );
453 let trailing_color = animate_color(
454 format!("efc_tic_{}", id),
455 colors.trailing_icon(is_enabled, selected),
456 spec,
457 );
458 let shape = config.shape_radius;
459
460 let ch_source: Rc<MutableInteractionSource> = config
461 .interaction_source
462 .clone()
463 .map(Rc::new)
464 .unwrap_or_else(|| remember(MutableInteractionSource::new));
465
466 let mut m = Modifier::new()
467 .state_colors(StateColors {
468 default: Color::TRANSPARENT,
469 hovered: th.on_surface.with_alpha_f32(0.08),
470 pressed: th.on_surface.with_alpha_f32(0.12),
471 disabled: Color::TRANSPARENT,
472 })
473 .state_elevation(config.elevation.to_state_elevation())
474 .padding_values(PaddingValues {
475 left: config.horizontal_padding,
476 right: config.horizontal_padding,
477 top: 8.0,
478 bottom: 8.0,
479 })
480 .background(bg)
481 .clip_rounded(shape)
482 .interaction_source(&*ch_source)
483 .then(config.modifier);
484
485 if is_enabled {
486 m = m.clickable().on_click(move || on_click());
487 }
488
489 Box(m).child(
490 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
491 leading_icon
492 .map(|v| {
493 Box(Modifier::new().padding_values(PaddingValues {
494 left: 0.0,
495 right: 8.0,
496 top: 0.0,
497 bottom: 0.0,
498 }))
499 .child(with_content_color(leading_color, move || v))
500 })
501 .unwrap_or(Box(Modifier::new())),
502 with_content_color(label_color, move || label),
503 trailing_icon
504 .map(|v| {
505 Box(Modifier::new().padding_values(PaddingValues {
506 left: 8.0,
507 right: 0.0,
508 top: 0.0,
509 bottom: 0.0,
510 }))
511 .child(with_content_color(trailing_color, move || v))
512 })
513 .unwrap_or(Box(Modifier::new())),
514 )),
515 )
516}
517
518pub fn SuggestionChip(
519 on_click: impl Fn() + 'static,
520 label: View,
521 icon: Option<View>,
522 config: ChipConfig,
523) -> View {
524 let th = theme();
525 let is_enabled = config.enabled;
526 let colors = &config.colors;
527 let bg = colors.container(is_enabled, false);
528 let label_color = colors.label(is_enabled, false);
529 let leading_color = colors.leading_icon(is_enabled, false);
530 let border = if is_enabled {
531 config.border_color
532 } else {
533 config.disabled_border_color
534 };
535 let shape = config.shape_radius;
536
537 let ch_source: Rc<MutableInteractionSource> = config
538 .interaction_source
539 .clone()
540 .map(Rc::new)
541 .unwrap_or_else(|| remember(MutableInteractionSource::new));
542
543 let mut m = Modifier::new()
544 .state_colors(StateColors {
545 default: Color::TRANSPARENT,
546 hovered: th.on_surface.with_alpha_f32(0.08),
547 pressed: th.on_surface.with_alpha_f32(0.12),
548 disabled: Color::TRANSPARENT,
549 })
550 .padding_values(PaddingValues {
551 left: config.horizontal_padding,
552 right: config.horizontal_padding,
553 top: 8.0,
554 bottom: 8.0,
555 })
556 .background(bg)
557 .clip_rounded(shape)
558 .interaction_source(&*ch_source)
559 .then(config.modifier);
560
561 if config.border_width > 0.0 && border != Color::TRANSPARENT {
562 m = m.border(config.border_width, border, shape);
563 }
564 if is_enabled {
565 m = m.clickable().on_click(move || on_click());
566 }
567
568 Box(m).child(
569 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
570 icon.map(|v| {
571 Box(Modifier::new().padding_values(PaddingValues {
572 left: 0.0,
573 right: 8.0,
574 top: 0.0,
575 bottom: 0.0,
576 }))
577 .child(with_content_color(leading_color, move || v))
578 })
579 .unwrap_or(Box(Modifier::new())),
580 with_content_color(label_color, move || label),
581 )),
582 )
583}
584
585pub fn ElevatedSuggestionChip(
587 on_click: impl Fn() + 'static,
588 label: View,
589 icon: Option<View>,
590 config: ChipConfig,
591) -> View {
592 let th = theme();
593 let is_enabled = config.enabled;
594 let colors = &config.colors;
595 let bg = colors.container(is_enabled, false);
596 let label_color = colors.label(is_enabled, false);
597 let leading_color = colors.leading_icon(is_enabled, false);
598 let shape = config.shape_radius;
599
600 let ch_source: Rc<MutableInteractionSource> = config
601 .interaction_source
602 .clone()
603 .map(Rc::new)
604 .unwrap_or_else(|| remember(MutableInteractionSource::new));
605
606 let mut m = Modifier::new()
607 .state_colors(StateColors {
608 default: Color::TRANSPARENT,
609 hovered: th.on_surface.with_alpha_f32(0.08),
610 pressed: th.on_surface.with_alpha_f32(0.12),
611 disabled: Color::TRANSPARENT,
612 })
613 .state_elevation(config.elevation.to_state_elevation())
614 .padding_values(PaddingValues {
615 left: config.horizontal_padding,
616 right: config.horizontal_padding,
617 top: 8.0,
618 bottom: 8.0,
619 })
620 .background(bg)
621 .clip_rounded(shape)
622 .interaction_source(&*ch_source)
623 .then(config.modifier);
624
625 if is_enabled {
626 m = m.clickable().on_click(move || on_click());
627 }
628
629 Box(m).child(
630 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
631 icon.map(|v| {
632 Box(Modifier::new().padding_values(PaddingValues {
633 left: 0.0,
634 right: 8.0,
635 top: 0.0,
636 bottom: 0.0,
637 }))
638 .child(with_content_color(leading_color, move || v))
639 })
640 .unwrap_or(Box(Modifier::new())),
641 with_content_color(label_color, move || label),
642 )),
643 )
644}
645
646pub fn InputChip(
647 selected: bool,
648 on_click: impl Fn() + 'static,
649 label: View,
650 leading_icon: Option<View>,
651 avatar: Option<View>,
652 trailing_icon: Option<View>,
653 config: ChipConfig,
654) -> View {
655 let th = theme();
656 let id = remember(|| FILTERCHIP_COUNTER.fetch_add(1, Ordering::Relaxed));
657 let spec = th.motion.color;
658 let is_enabled = config.enabled;
659 let colors = &config.colors;
660
661 let bg = animate_color(
662 format!("ic_bg_{}", id),
663 colors.container(is_enabled, selected),
664 spec,
665 );
666 let label_color = animate_color(
667 format!("ic_lc_{}", id),
668 colors.label(is_enabled, selected),
669 spec,
670 );
671 let leading_color = animate_color(
672 format!("ic_lic_{}", id),
673 colors.leading_icon(is_enabled, selected),
674 spec,
675 );
676 let trailing_color = animate_color(
677 format!("ic_tic_{}", id),
678 colors.trailing_icon(is_enabled, selected),
679 spec,
680 );
681 let border = if !is_enabled {
682 if selected {
683 config.disabled_selected_border_color
684 } else {
685 config.disabled_border_color
686 }
687 } else {
688 if selected {
689 config.selected_border_color
690 } else {
691 config.border_color
692 }
693 };
694 let shape = config.shape_radius;
695
696 let ch_source: Rc<MutableInteractionSource> = config
697 .interaction_source
698 .clone()
699 .map(Rc::new)
700 .unwrap_or_else(|| remember(MutableInteractionSource::new));
701
702 let mut m = Modifier::new()
703 .state_colors(StateColors {
704 default: Color::TRANSPARENT,
705 hovered: th.on_surface.with_alpha_f32(0.08),
706 pressed: th.on_surface.with_alpha_f32(0.12),
707 disabled: Color::TRANSPARENT,
708 })
709 .padding_values(PaddingValues {
710 left: config.horizontal_padding,
711 right: config.horizontal_padding,
712 top: 8.0,
713 bottom: 8.0,
714 })
715 .background(bg)
716 .clip_rounded(shape)
717 .interaction_source(&*ch_source)
718 .then(config.modifier);
719
720 if config.border_width > 0.0 && border != Color::TRANSPARENT {
721 m = m.border(config.border_width, border, shape);
722 }
723 if is_enabled {
724 m = m.clickable().on_click(move || on_click());
725 }
726
727 Box(m).child(
728 Row(Modifier::new().align_items(AlignItems::CENTER)).child((
729 avatar
730 .or(leading_icon)
731 .map(|v| {
732 Box(Modifier::new().padding_values(PaddingValues {
733 left: 0.0,
734 right: 8.0,
735 top: 0.0,
736 bottom: 0.0,
737 }))
738 .child(with_content_color(leading_color, move || v))
739 })
740 .unwrap_or(Box(Modifier::new())),
741 with_content_color(label_color, move || label),
742 trailing_icon
743 .map(|v| {
744 Box(Modifier::new().padding_values(PaddingValues {
745 left: 8.0,
746 right: 0.0,
747 top: 0.0,
748 bottom: 0.0,
749 }))
750 .child(with_content_color(trailing_color, move || v))
751 })
752 .unwrap_or(Box(Modifier::new())),
753 )),
754 )
755}