1use crate::alignment;
3use crate::event::{self, Event};
4use crate::layout;
5use crate::mouse;
6use crate::renderer;
7use crate::text;
8use crate::touch;
9use crate::widget::{self, Row, Text, Tree};
10use crate::{
11 Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle,
12 Shell, Widget,
13};
14
15pub use iced_style::checkbox::{Appearance, StyleSheet};
16
17#[allow(missing_debug_implementations)]
35pub struct Checkbox<'a, Message, Renderer>
36where
37 Renderer: text::Renderer,
38 Renderer::Theme: StyleSheet + widget::text::StyleSheet,
39{
40 is_checked: bool,
41 on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
42 label: String,
43 width: Length,
44 size: f32,
45 spacing: f32,
46 text_size: Option<f32>,
47 font: Renderer::Font,
48 icon: Icon<Renderer::Font>,
49 style: <Renderer::Theme as StyleSheet>::Style,
50}
51
52impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer>
53where
54 Renderer: text::Renderer,
55 Renderer::Theme: StyleSheet + widget::text::StyleSheet,
56{
57 const DEFAULT_SIZE: f32 = 20.0;
59
60 const DEFAULT_SPACING: f32 = 15.0;
62
63 pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self
72 where
73 F: 'a + Fn(bool) -> Message,
74 {
75 Checkbox {
76 is_checked,
77 on_toggle: Box::new(f),
78 label: label.into(),
79 width: Length::Shrink,
80 size: Self::DEFAULT_SIZE,
81 spacing: Self::DEFAULT_SPACING,
82 text_size: None,
83 font: Renderer::Font::default(),
84 icon: Icon {
85 font: Renderer::ICON_FONT,
86 code_point: Renderer::CHECKMARK_ICON,
87 size: None,
88 },
89 style: Default::default(),
90 }
91 }
92
93 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
95 self.size = size.into().0;
96 self
97 }
98
99 pub fn width(mut self, width: impl Into<Length>) -> Self {
101 self.width = width.into();
102 self
103 }
104
105 pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
107 self.spacing = spacing.into().0;
108 self
109 }
110
111 pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
113 self.text_size = Some(text_size.into().0);
114 self
115 }
116
117 pub fn font(mut self, font: Renderer::Font) -> Self {
121 self.font = font;
122 self
123 }
124
125 pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
127 self.icon = icon;
128 self
129 }
130
131 pub fn style(
133 mut self,
134 style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
135 ) -> Self {
136 self.style = style.into();
137 self
138 }
139}
140
141impl<'a, Message, Renderer> Widget<Message, Renderer>
142 for Checkbox<'a, Message, Renderer>
143where
144 Renderer: text::Renderer,
145 Renderer::Theme: StyleSheet + widget::text::StyleSheet,
146{
147 fn width(&self) -> Length {
148 self.width
149 }
150
151 fn height(&self) -> Length {
152 Length::Shrink
153 }
154
155 fn layout(
156 &self,
157 renderer: &Renderer,
158 limits: &layout::Limits,
159 ) -> layout::Node {
160 Row::<(), Renderer>::new()
161 .width(self.width)
162 .spacing(self.spacing)
163 .align_items(Alignment::Center)
164 .push(Row::new().width(self.size).height(self.size))
165 .push(
166 Text::new(&self.label)
167 .font(self.font.clone())
168 .width(self.width)
169 .size(
170 self.text_size
171 .unwrap_or_else(|| renderer.default_size()),
172 ),
173 )
174 .layout(renderer, limits)
175 }
176
177 fn on_event(
178 &mut self,
179 _tree: &mut Tree,
180 event: Event,
181 layout: Layout<'_>,
182 cursor_position: Point,
183 _renderer: &Renderer,
184 _clipboard: &mut dyn Clipboard,
185 shell: &mut Shell<'_, Message>,
186 ) -> event::Status {
187 match event {
188 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
189 | Event::Touch(touch::Event::FingerPressed { .. }) => {
190 let mouse_over = layout.bounds().contains(cursor_position);
191
192 if mouse_over {
193 shell.publish((self.on_toggle)(!self.is_checked));
194
195 return event::Status::Captured;
196 }
197 }
198 _ => {}
199 }
200
201 event::Status::Ignored
202 }
203
204 fn mouse_interaction(
205 &self,
206 _tree: &Tree,
207 layout: Layout<'_>,
208 cursor_position: Point,
209 _viewport: &Rectangle,
210 _renderer: &Renderer,
211 ) -> mouse::Interaction {
212 if layout.bounds().contains(cursor_position) {
213 mouse::Interaction::Pointer
214 } else {
215 mouse::Interaction::default()
216 }
217 }
218
219 fn draw(
220 &self,
221 _tree: &Tree,
222 renderer: &mut Renderer,
223 theme: &Renderer::Theme,
224 style: &renderer::Style,
225 layout: Layout<'_>,
226 cursor_position: Point,
227 _viewport: &Rectangle,
228 ) {
229 let bounds = layout.bounds();
230 let is_mouse_over = bounds.contains(cursor_position);
231
232 let mut children = layout.children();
233
234 let custom_style = if is_mouse_over {
235 theme.hovered(&self.style, self.is_checked)
236 } else {
237 theme.active(&self.style, self.is_checked)
238 };
239
240 {
241 let layout = children.next().unwrap();
242 let bounds = layout.bounds();
243
244 renderer.fill_quad(
245 renderer::Quad {
246 bounds,
247 border_radius: custom_style.border_radius.into(),
248 border_width: custom_style.border_width,
249 border_color: custom_style.border_color,
250 },
251 custom_style.background,
252 );
253
254 let Icon {
255 font,
256 code_point,
257 size,
258 } = &self.icon;
259 let size = size.map(f32::from).unwrap_or(bounds.height * 0.7);
260
261 if self.is_checked {
262 renderer.fill_text(text::Text {
263 content: &code_point.to_string(),
264 font: font.clone(),
265 size,
266 bounds: Rectangle {
267 x: bounds.center_x(),
268 y: bounds.center_y(),
269 ..bounds
270 },
271 color: custom_style.icon_color,
272 horizontal_alignment: alignment::Horizontal::Center,
273 vertical_alignment: alignment::Vertical::Center,
274 });
275 }
276 }
277
278 {
279 let label_layout = children.next().unwrap();
280
281 widget::text::draw(
282 renderer,
283 style,
284 label_layout,
285 &self.label,
286 self.text_size,
287 self.font.clone(),
288 widget::text::Appearance {
289 color: custom_style.text_color,
290 },
291 alignment::Horizontal::Left,
292 alignment::Vertical::Center,
293 );
294 }
295 }
296}
297
298impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
299 for Element<'a, Message, Renderer>
300where
301 Message: 'a,
302 Renderer: 'a + text::Renderer,
303 Renderer::Theme: StyleSheet + widget::text::StyleSheet,
304{
305 fn from(
306 checkbox: Checkbox<'a, Message, Renderer>,
307 ) -> Element<'a, Message, Renderer> {
308 Element::new(checkbox)
309 }
310}
311
312#[derive(Debug, Clone, PartialEq)]
314pub struct Icon<Font> {
315 pub font: Font,
317 pub code_point: char,
319 pub size: Option<f32>,
321}