1use iced_native::color;
5use iced_native::event::{self, Event};
6use iced_native::layout;
7use iced_native::mouse;
8use iced_native::overlay;
9use iced_native::renderer;
10use iced_native::touch;
11use iced_native::widget::tree::{self, Tree};
12use iced_native::widget::Operation;
13use iced_native::{
14 Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell,
15 Vector, Widget,
16};
17
18use crate::widget::StyleType;
19
20pub use iced_style::button::{Appearance, StyleSheet};
21
22#[allow(missing_debug_implementations)]
57pub struct Button<'a, Message, Renderer>
58where
59 Renderer: iced_native::Renderer,
60 Renderer::Theme: StyleSheet,
61{
62 content: Element<'a, Message, Renderer>,
63 on_press: Option<Message>,
64 width: Length,
65 height: Length,
66 padding: Padding,
67 style: StyleType<<Renderer::Theme as StyleSheet>::Style>,
68}
69
70impl<'a, Message, Renderer> Button<'a, Message, Renderer>
71where
72 Renderer: iced_native::Renderer,
73 Renderer::Theme: StyleSheet,
74{
75 pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
77 Button {
78 content: content.into(),
79 on_press: None,
80 width: Length::Shrink,
81 height: Length::Shrink,
82 padding: Padding::new(5.0),
83 style: StyleType::Static(<Renderer::Theme as StyleSheet>::Style::default()),
84 }
85 }
86
87 pub fn width(mut self, width: impl Into<Length>) -> Self {
89 self.width = width.into();
90 self
91 }
92
93 pub fn height(mut self, height: impl Into<Length>) -> Self {
95 self.height = height.into();
96 self
97 }
98
99 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
101 self.padding = padding.into();
102 self
103 }
104
105 pub fn on_press(mut self, msg: Message) -> Self {
109 self.on_press = Some(msg);
110 self
111 }
112
113 pub fn style(mut self, style: <Renderer::Theme as StyleSheet>::Style) -> Self {
115 self.style = StyleType::Static(style);
116 self
117 }
118
119 pub fn blend_style(
121 mut self,
122 style1: <Renderer::Theme as StyleSheet>::Style,
123 style2: <Renderer::Theme as StyleSheet>::Style,
124 percent: f32,
125 ) -> Self {
126 self.style = StyleType::Blend(style1, style2, percent);
127 self
128 }
129}
130
131impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message, Renderer>
132where
133 Message: 'a + Clone,
134 Renderer: 'a + iced_native::Renderer,
135 Renderer::Theme: StyleSheet,
136{
137 fn tag(&self) -> tree::Tag {
138 tree::Tag::of::<State>()
139 }
140
141 fn state(&self) -> tree::State {
142 tree::State::new(State::new())
143 }
144
145 fn children(&self) -> Vec<Tree> {
146 vec![Tree::new(&self.content)]
147 }
148
149 fn diff(&self, tree: &mut Tree) {
150 tree.diff_children(std::slice::from_ref(&self.content))
151 }
152
153 fn width(&self) -> Length {
154 self.width
155 }
156
157 fn height(&self) -> Length {
158 self.height
159 }
160
161 fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
162 layout(
163 renderer,
164 limits,
165 self.width,
166 self.height,
167 self.padding,
168 |renderer, limits| self.content.as_widget().layout(renderer, limits),
169 )
170 }
171
172 fn operate(
173 &self,
174 tree: &mut Tree,
175 layout: Layout<'_>,
176 renderer: &Renderer,
177 operation: &mut dyn Operation<Message>,
178 ) {
179 operation.container(None, &mut |operation| {
180 self.content.as_widget().operate(
181 &mut tree.children[0],
182 layout.children().next().unwrap(),
183 renderer,
184 operation,
185 );
186 });
187 }
188
189 fn on_event(
190 &mut self,
191 tree: &mut Tree,
192 event: Event,
193 layout: Layout<'_>,
194 cursor_position: Point,
195 renderer: &Renderer,
196 clipboard: &mut dyn Clipboard,
197 shell: &mut Shell<'_, Message>,
198 ) -> event::Status {
199 if let event::Status::Captured = self.content.as_widget_mut().on_event(
200 &mut tree.children[0],
201 event.clone(),
202 layout.children().next().unwrap(),
203 cursor_position,
204 renderer,
205 clipboard,
206 shell,
207 ) {
208 return event::Status::Captured;
209 }
210
211 update(
212 event,
213 layout,
214 cursor_position,
215 shell,
216 &self.on_press,
217 || tree.state.downcast_mut::<State>(),
218 )
219 }
220
221 fn draw(
222 &self,
223 tree: &Tree,
224 renderer: &mut Renderer,
225 theme: &Renderer::Theme,
226 _style: &renderer::Style,
227 layout: Layout<'_>,
228 cursor_position: Point,
229 _viewport: &Rectangle,
230 ) {
231 let bounds = layout.bounds();
232 let content_layout = layout.children().next().unwrap();
233
234 let styling = draw(
235 renderer,
236 bounds,
237 cursor_position,
238 self.on_press.is_some(),
239 theme,
240 &self.style,
241 || tree.state.downcast_ref::<State>(),
242 );
243
244 self.content.as_widget().draw(
245 &tree.children[0],
246 renderer,
247 theme,
248 &renderer::Style {
249 text_color: styling.text_color,
250 },
251 content_layout,
252 cursor_position,
253 &bounds,
254 );
255 }
256
257 fn mouse_interaction(
258 &self,
259 _tree: &Tree,
260 layout: Layout<'_>,
261 cursor_position: Point,
262 _viewport: &Rectangle,
263 _renderer: &Renderer,
264 ) -> mouse::Interaction {
265 mouse_interaction(layout, cursor_position, self.on_press.is_some())
266 }
267
268 fn overlay<'b>(
269 &'b mut self,
270 tree: &'b mut Tree,
271 layout: Layout<'_>,
272 renderer: &Renderer,
273 ) -> Option<overlay::Element<'b, Message, Renderer>> {
274 self.content.as_widget_mut().overlay(
275 &mut tree.children[0],
276 layout.children().next().unwrap(),
277 renderer,
278 )
279 }
280}
281
282impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> for Element<'a, Message, Renderer>
283where
284 Message: Clone + 'a,
285 Renderer: iced_native::Renderer + 'a,
286 Renderer::Theme: StyleSheet,
287{
288 fn from(button: Button<'a, Message, Renderer>) -> Self {
289 Self::new(button)
290 }
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
295pub struct State {
296 is_pressed: bool,
297}
298
299impl State {
300 pub fn new() -> State {
302 State::default()
303 }
304}
305
306pub fn update<'a, Message: Clone>(
309 event: Event,
310 layout: Layout<'_>,
311 cursor_position: Point,
312 shell: &mut Shell<'_, Message>,
313 on_press: &Option<Message>,
314 state: impl FnOnce() -> &'a mut State,
315) -> event::Status {
316 match event {
317 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
318 | Event::Touch(touch::Event::FingerPressed { .. }) => {
319 if on_press.is_some() {
320 let bounds = layout.bounds();
321
322 if bounds.contains(cursor_position) {
323 let state = state();
324
325 state.is_pressed = true;
326
327 return event::Status::Captured;
328 }
329 }
330 }
331 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
332 | Event::Touch(touch::Event::FingerLifted { .. }) => {
333 if let Some(on_press) = on_press.clone() {
334 let state = state();
335
336 if state.is_pressed {
337 state.is_pressed = false;
338
339 let bounds = layout.bounds();
340
341 if bounds.contains(cursor_position) {
342 shell.publish(on_press);
343 }
344
345 return event::Status::Captured;
346 }
347 }
348 }
349 Event::Touch(touch::Event::FingerLost { .. }) => {
350 let state = state();
351
352 state.is_pressed = false;
353 }
354 _ => {}
355 }
356
357 event::Status::Ignored
358}
359
360pub fn draw<'a, Renderer: iced_native::Renderer>(
362 renderer: &mut Renderer,
363 bounds: Rectangle,
364 cursor_position: Point,
365 is_enabled: bool,
366 style_sheet: &dyn StyleSheet<Style = <Renderer::Theme as StyleSheet>::Style>,
367 style: &StyleType<<Renderer::Theme as StyleSheet>::Style>,
368 state: impl FnOnce() -> &'a State,
369) -> Appearance
370where
371 Renderer::Theme: StyleSheet,
372{
373 let is_mouse_over = bounds.contains(cursor_position);
374
375 let styling = match style {
377 StyleType::Static(style) => {
378 if !is_enabled {
379 style_sheet.disabled(style)
380 } else if is_mouse_over {
381 let state = state();
382
383 if state.is_pressed {
384 style_sheet.pressed(style)
385 } else {
386 style_sheet.hovered(style)
387 }
388 } else {
389 style_sheet.active(style)
390 }
391 }
392 StyleType::Blend(style1, style2, percent) => {
393 let (one, two) = if !is_enabled {
394 (style_sheet.disabled(style1), style_sheet.disabled(style2))
395 } else if is_mouse_over {
396 let state = state();
397
398 if state.is_pressed {
399 (style_sheet.pressed(style1), style_sheet.pressed(style2))
400 } else {
401 (style_sheet.hovered(style1), style_sheet.hovered(style2))
402 }
403 } else {
404 (style_sheet.active(style1), style_sheet.active(style2))
405 };
406
407 blend_appearances(one, two, *percent)
408 }
409 };
410
411 if styling.background.is_some() || styling.border_width > 0.0 {
412 if styling.shadow_offset != Vector::default() {
413 renderer.fill_quad(
415 renderer::Quad {
416 bounds: Rectangle {
417 x: bounds.x + styling.shadow_offset.x,
418 y: bounds.y + styling.shadow_offset.y,
419 ..bounds
420 },
421 border_radius: styling.border_radius.into(),
422 border_width: 0.0,
423 border_color: Color::TRANSPARENT,
424 },
425 Background::Color([0.0, 0.0, 0.0, 0.5].into()),
426 );
427 }
428
429 renderer.fill_quad(
430 renderer::Quad {
431 bounds,
432 border_radius: styling.border_radius.into(),
433 border_width: styling.border_width,
434 border_color: styling.border_color,
435 },
436 styling
437 .background
438 .unwrap_or(Background::Color(Color::TRANSPARENT)),
439 );
440 }
441
442 styling
443}
444
445pub fn layout<Renderer>(
447 renderer: &Renderer,
448 limits: &layout::Limits,
449 width: Length,
450 height: Length,
451 padding: Padding,
452 layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
453) -> layout::Node {
454 let limits = limits.width(width).height(height);
455
456 let mut content = layout_content(renderer, &limits.pad(padding));
457 let padding = padding.fit(content.size(), limits.max());
458 let size = limits.pad(padding).resolve(content.size()).pad(padding);
459
460 content.move_to(Point::new(padding.left, padding.top));
461
462 layout::Node::with_children(size, vec![content])
463}
464
465pub fn mouse_interaction(
467 layout: Layout<'_>,
468 cursor_position: Point,
469 is_enabled: bool,
470) -> mouse::Interaction {
471 let is_mouse_over = layout.bounds().contains(cursor_position);
472
473 if is_mouse_over && is_enabled {
474 mouse::Interaction::Pointer
475 } else {
476 mouse::Interaction::default()
477 }
478}
479
480fn blend_appearances(
481 one: iced_style::button::Appearance,
482 mut two: iced_style::button::Appearance,
483 percent: f32,
484) -> iced_style::button::Appearance {
485 use crate::lerp;
486
487 let x1 = one.shadow_offset.x;
489 let y1 = one.shadow_offset.y;
490 let x2 = two.shadow_offset.x;
491 let y2 = two.shadow_offset.y;
492
493 let background_one: Color = one
495 .background
496 .map(|b| match b {
497 Background::Color(c) => c,
498 })
499 .unwrap_or(color!(0, 0, 0));
500 let background_two: Color = two
501 .background
502 .map(|b| match b {
503 Background::Color(c) => c,
504 })
505 .unwrap_or(color!(0, 0, 0));
506 let background_mix: [f32; 4] = background_one
507 .into_linear()
508 .iter()
509 .zip(background_two.into_linear().iter())
510 .map(|(o, t)| lerp(*o, *t, percent))
511 .collect::<Vec<f32>>()
512 .try_into()
513 .unwrap();
514 let new_background_color: Color = background_mix.into();
515
516 let border_color: [f32; 4] = one
518 .border_color
519 .into_linear()
520 .iter()
521 .zip(two.border_color.into_linear().iter())
522 .map(|(o, t)| lerp(*o, *t, percent))
523 .collect::<Vec<f32>>()
524 .try_into()
525 .unwrap();
526
527 let text: [f32; 4] = one
529 .text_color
530 .into_linear()
531 .iter()
532 .zip(two.text_color.into_linear().iter())
533 .map(|(o, t)| lerp(*o, *t, percent))
534 .collect::<Vec<f32>>()
535 .try_into()
536 .unwrap();
537
538 two.shadow_offset = Vector::new(lerp(x1, x2, percent), lerp(y1, y2, percent));
539 two.background = Some(new_background_color.into());
540 two.border_radius = lerp(one.border_radius, two.border_radius, percent);
541 two.border_width = lerp(one.border_width, two.border_width, percent);
542 two.border_color = border_color.into();
543 two.text_color = text.into();
544 two
545}