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",
50 doc = embed_doc_image::embed_image!("checkbox", "images/gallery_checkbox.png")
51)]
52#[derive(Clone, PartialEq)]
53pub struct Checkbox {
54 pub(crate) theme: Option<CheckboxThemePartial>,
55 selected: bool,
56 key: DiffKey,
57 size: f32,
58}
59
60impl Default for Checkbox {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl Checkbox {
67 pub fn new() -> Self {
68 Self {
69 selected: false,
70 theme: None,
71 key: DiffKey::None,
72 size: 20.,
73 }
74 }
75
76 pub fn selected(mut self, selected: bool) -> Self {
77 self.selected = selected;
78 self
79 }
80
81 pub fn theme(mut self, theme: CheckboxThemePartial) -> Self {
82 self.theme = Some(theme);
83 self
84 }
85
86 pub fn key(mut self, key: impl Into<DiffKey>) -> Self {
87 self.key = key.into();
88 self
89 }
90
91 pub fn size(mut self, size: impl Into<f32>) -> Self {
92 self.size = size.into();
93 self
94 }
95}
96
97impl Render for Checkbox {
98 fn render(&self) -> impl IntoElement {
99 let focus = use_focus();
100 let focus_status = use_focus_status(focus);
101 let CheckboxTheme {
102 border_fill,
103 unselected_fill,
104 selected_fill,
105 selected_icon_fill,
106 } = get_theme!(&self.theme, checkbox);
107
108 let animation = use_animation_with_dependencies(&self.selected, move |conf, selected| {
109 conf.on_change(OnChange::Rerun);
110 conf.on_creation(OnCreation::Finish);
111
112 let scale = AnimNum::new(0.6, 1.)
113 .time(350)
114 .ease(Ease::Out)
115 .function(Function::Expo);
116 let opacity = AnimNum::new(0., 1.)
117 .time(350)
118 .ease(Ease::Out)
119 .function(Function::Expo);
120
121 if *selected {
122 (scale, opacity)
123 } else {
124 (scale.into_reversed(), opacity.into_reversed())
125 }
126 });
127
128 let (scale, opacity) = animation.read().value();
129
130 let (background, fill) = if self.selected {
131 (selected_fill, selected_fill)
132 } else {
133 (Color::TRANSPARENT, unselected_fill)
134 };
135
136 let border = Border::new()
137 .fill(fill)
138 .width(2.)
139 .alignment(BorderAlignment::Inner);
140
141 let focused_border = (focus_status() == FocusStatus::Keyboard).then(|| {
142 Border::new()
143 .fill(border_fill)
144 .width((self.size * 0.15).ceil())
145 .alignment(BorderAlignment::Outer)
146 });
147
148 rect()
149 .a11y_id(focus.a11y_id())
150 .a11y_focusable(Focusable::Enabled)
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}