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