freya_components/
checkbox.rs1use freya_animation::prelude::*;
2use freya_core::prelude::*;
3use torin::prelude::*;
4
5use crate::{
6 get_theme,
7 icons::tick::TickIcon,
8 theming::component_themes::{
9 CheckboxTheme,
10 CheckboxThemePartial,
11 },
12};
13
14#[cfg_attr(feature = "docs",
49 doc = embed_doc_image::embed_image!("checkbox", "images/gallery_checkbox.png")
50)]
51#[derive(Clone, PartialEq)]
52pub struct Checkbox {
53 pub(crate) theme: Option<CheckboxThemePartial>,
54 selected: bool,
55 key: DiffKey,
56 size: f32,
57}
58
59impl Default for Checkbox {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl Checkbox {
66 pub fn new() -> Self {
67 Self {
68 selected: false,
69 theme: None,
70 key: DiffKey::None,
71 size: 20.,
72 }
73 }
74
75 pub fn selected(mut self, selected: bool) -> Self {
76 self.selected = selected;
77 self
78 }
79
80 pub fn theme(mut self, theme: CheckboxThemePartial) -> Self {
81 self.theme = Some(theme);
82 self
83 }
84
85 pub fn key(mut self, key: impl Into<DiffKey>) -> Self {
86 self.key = key.into();
87 self
88 }
89
90 pub fn size(mut self, size: impl Into<f32>) -> Self {
91 self.size = size.into();
92 self
93 }
94}
95
96impl Component for Checkbox {
97 fn render(&self) -> impl IntoElement {
98 let focus = use_focus();
99 let focus_status = use_focus_status(focus);
100 let CheckboxTheme {
101 border_fill,
102 unselected_fill,
103 selected_fill,
104 selected_icon_fill,
105 } = get_theme!(&self.theme, checkbox);
106
107 let animation = use_animation_with_dependencies(&self.selected, move |conf, selected| {
108 conf.on_change(OnChange::Rerun);
109 conf.on_creation(OnCreation::Finish);
110
111 let scale = AnimNum::new(0.6, 1.)
112 .time(350)
113 .ease(Ease::Out)
114 .function(Function::Expo);
115 let opacity = AnimNum::new(0., 1.)
116 .time(350)
117 .ease(Ease::Out)
118 .function(Function::Expo);
119
120 if *selected {
121 (scale, opacity)
122 } else {
123 (scale.into_reversed(), opacity.into_reversed())
124 }
125 });
126
127 let (scale, opacity) = animation.read().value();
128
129 let (background, fill) = if self.selected {
130 (selected_fill, selected_fill)
131 } else {
132 (Color::TRANSPARENT, unselected_fill)
133 };
134
135 let border = Border::new()
136 .fill(fill)
137 .width(2.)
138 .alignment(BorderAlignment::Inner);
139
140 let focused_border = (focus_status() == FocusStatus::Keyboard).then(|| {
141 Border::new()
142 .fill(border_fill)
143 .width((self.size * 0.15).ceil())
144 .alignment(BorderAlignment::Outer)
145 });
146
147 rect()
148 .a11y_id(focus.a11y_id())
149 .a11y_focusable(Focusable::Enabled)
150 .a11y_role(AccessibilityRole::CheckBox)
151 .width(Size::px(self.size))
152 .height(Size::px(self.size))
153 .padding(Gaps::new_all(4.0))
154 .main_align(Alignment::center())
155 .cross_align(Alignment::center())
156 .corner_radius(CornerRadius::new_all(self.size * 0.24))
157 .border(border)
158 .border(focused_border)
159 .background(background)
160 .on_key_down({
161 move |e: Event<KeyboardEventData>| {
162 if !Focus::is_pressed(&e) {
163 e.stop_propagation();
164 }
165 }
166 })
167 .maybe_child((self.selected || opacity > 0.).then(|| {
168 rect().opacity(opacity).scale(scale).child(
169 TickIcon::new()
170 .width(Size::px(self.size * 0.7))
171 .height(Size::px(self.size * 0.7))
172 .fill(selected_icon_fill),
173 )
174 }))
175 }
176
177 fn render_key(&self) -> DiffKey {
178 self.key.clone().or(self.default_key())
179 }
180}