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