diff options
author | 2022-02-11 23:17:07 +0700 | |
---|---|---|
committer | 2022-02-11 23:17:07 +0700 | |
commit | ecb3df8e018930c407e469ce2b8f4208a9d15426 (patch) | |
tree | 9370945e6c5092e2730ec6835fa6c0556622ca9d | |
parent | 01c5004959c9b11f2580840f4553ad7d706f4564 (diff) | |
download | iced-ecb3df8e018930c407e469ce2b8f4208a9d15426.tar.gz iced-ecb3df8e018930c407e469ce2b8f4208a9d15426.tar.bz2 iced-ecb3df8e018930c407e469ce2b8f4208a9d15426.zip |
Expose reusable `Button` logic
... and reuse it in `iced_pure`!
Diffstat (limited to '')
-rw-r--r-- | examples/stopwatch/src/main.rs | 2 | ||||
-rw-r--r-- | examples/tour/src/main.rs | 2 | ||||
-rw-r--r-- | native/src/widget/button.rs | 295 | ||||
-rw-r--r-- | pure/src/widget/button.rs | 162 | ||||
-rw-r--r-- | pure/src/widget/tree.rs | 14 |
5 files changed, 228 insertions, 247 deletions
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index dc8a4de7..377d7a2d 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -105,8 +105,8 @@ impl Application for Stopwatch { Text::new(label) .horizontal_alignment(alignment::Horizontal::Center), ) - .min_width(80) .padding(10) + .width(Length::Units(80)) .style(style) }; diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index d4b41310..b01a1ce2 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -726,7 +726,7 @@ fn button<'a, Message: Clone>( Text::new(label).horizontal_alignment(alignment::Horizontal::Center), ) .padding(12) - .min_width(100) + .width(Length::Units(100)) } fn color_slider( diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index b4a3adc3..049b6544 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -63,8 +63,6 @@ pub struct Button<'a, Message, Renderer> { on_press: Option<Message>, width: Length, height: Length, - min_width: u32, - min_height: u32, padding: Padding, style_sheet: Box<dyn StyleSheet + 'a>, } @@ -86,8 +84,6 @@ where on_press: None, width: Length::Shrink, height: Length::Shrink, - min_width: 0, - min_height: 0, padding: Padding::new(5), style_sheet: Default::default(), } @@ -105,18 +101,6 @@ where self } - /// Sets the minimum width of the [`Button`]. - pub fn min_width(mut self, min_width: u32) -> Self { - self.min_width = min_width; - self - } - - /// Sets the minimum height of the [`Button`]. - pub fn min_height(mut self, min_height: u32) -> Self { - self.min_height = min_height; - self - } - /// Sets the [`Padding`] of the [`Button`]. pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { self.padding = padding.into(); @@ -153,6 +137,153 @@ impl State { } } +/// Processes the given [`Event`] and updates the [`State`] of a [`Button`] +/// accordingly. +pub fn update<'a, Message: Clone>( + event: Event, + layout: Layout<'_>, + cursor_position: Point, + shell: &mut Shell<'_, Message>, + on_press: &Option<Message>, + state: impl FnOnce() -> &'a mut State, +) -> event::Status { + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if on_press.is_some() { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let state = state(); + + state.is_pressed = true; + + return event::Status::Captured; + } + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + if let Some(on_press) = on_press.clone() { + let state = state(); + + if state.is_pressed { + state.is_pressed = false; + + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + shell.publish(on_press); + } + + return event::Status::Captured; + } + } + } + Event::Touch(touch::Event::FingerLost { .. }) => { + let state = state(); + + state.is_pressed = false; + } + _ => {} + } + + event::Status::Ignored +} + +/// Draws a [`Button`]. +pub fn draw<'a, Renderer: crate::Renderer>( + renderer: &mut Renderer, + bounds: Rectangle, + cursor_position: Point, + is_enabled: bool, + style_sheet: &dyn StyleSheet, + state: impl FnOnce() -> &'a State, +) -> Style { + let is_mouse_over = bounds.contains(cursor_position); + + let styling = if !is_enabled { + style_sheet.disabled() + } else if is_mouse_over { + let state = state(); + + if state.is_pressed { + style_sheet.pressed() + } else { + style_sheet.hovered() + } + } else { + style_sheet.active() + }; + + if styling.background.is_some() || styling.border_width > 0.0 { + if styling.shadow_offset != Vector::default() { + // TODO: Implement proper shadow support + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + ..bounds + }, + border_radius: styling.border_radius, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color([0.0, 0.0, 0.0, 0.5].into()), + ); + } + + renderer.fill_quad( + renderer::Quad { + bounds, + border_radius: styling.border_radius, + border_width: styling.border_width, + border_color: styling.border_color, + }, + styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + ); + } + + styling +} + +/// Computes the layout of a [`Button`]. +pub fn layout<Renderer>( + renderer: &Renderer, + limits: &layout::Limits, + width: Length, + height: Length, + padding: Padding, + layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, +) -> layout::Node { + let limits = limits.width(width).height(height).pad(padding); + + let mut content = layout_content(renderer, &limits); + content.move_to(Point::new(padding.left.into(), padding.top.into())); + + let size = limits.resolve(content.size()).pad(padding); + + layout::Node::with_children(size, vec![content]) +} + +/// Returns the [`mouse::Interaction`] of a [`Button`]. +pub fn mouse_interaction( + layout: Layout<'_>, + cursor_position: Point, + is_enabled: bool, +) -> mouse::Interaction { + let is_mouse_over = layout.bounds().contains(cursor_position); + + if is_mouse_over && is_enabled { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } +} + impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message, Renderer> where @@ -172,22 +303,14 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits - .min_width(self.min_width) - .min_height(self.min_height) - .width(self.width) - .height(self.height) - .pad(self.padding); - - let mut content = self.content.layout(renderer, &limits); - content.move_to(Point::new( - self.padding.left.into(), - self.padding.top.into(), - )); - - let size = limits.resolve(content.size()).pad(self.padding); - - layout::Node::with_children(size, vec![content]) + layout( + renderer, + limits, + self.width, + self.height, + self.padding, + |renderer, limits| self.content.layout(renderer, limits), + ) } fn on_event( @@ -210,42 +333,14 @@ where return event::Status::Captured; } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if self.on_press.is_some() { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - self.state.is_pressed = true; - - return event::Status::Captured; - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = self.on_press.clone() { - let bounds = layout.bounds(); - - if self.state.is_pressed { - self.state.is_pressed = false; - - if bounds.contains(cursor_position) { - shell.publish(on_press); - } - - return event::Status::Captured; - } - } - } - Event::Touch(touch::Event::FingerLost { .. }) => { - self.state.is_pressed = false; - } - _ => {} - } - - event::Status::Ignored + update( + event, + layout, + cursor_position, + shell, + &self.on_press, + || &mut self.state, + ) } fn mouse_interaction( @@ -255,14 +350,7 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); - let is_disabled = self.on_press.is_none(); - - if is_mouse_over && !is_disabled { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } + mouse_interaction(layout, cursor_position, self.on_press.is_some()) } fn draw( @@ -276,51 +364,14 @@ where let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); - let is_mouse_over = bounds.contains(cursor_position); - let is_disabled = self.on_press.is_none(); - - let styling = if is_disabled { - self.style_sheet.disabled() - } else if is_mouse_over { - if self.state.is_pressed { - self.style_sheet.pressed() - } else { - self.style_sheet.hovered() - } - } else { - self.style_sheet.active() - }; - - if styling.background.is_some() || styling.border_width > 0.0 { - if styling.shadow_offset != Vector::default() { - // TODO: Implement proper shadow support - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + styling.shadow_offset.x, - y: bounds.y + styling.shadow_offset.y, - ..bounds - }, - border_radius: styling.border_radius, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Background::Color([0.0, 0.0, 0.0, 0.5].into()), - ); - } - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: styling.border_radius, - border_width: styling.border_width, - border_color: styling.border_color, - }, - styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } + let styling = draw( + renderer, + bounds, + cursor_position, + self.on_press.is_some(), + self.style_sheet.as_ref(), + || &self.state, + ); self.content.draw( renderer, @@ -338,6 +389,8 @@ where std::any::TypeId::of::<Marker>().hash(state); self.width.hash(state); + self.height.hash(state); + self.padding.hash(state); self.content.hash_layout(state); } diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 198a3af9..89acb7f5 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -4,15 +4,16 @@ use iced_native::event::{self, Event}; use iced_native::layout; use iced_native::mouse; use iced_native::renderer; -use iced_native::touch; +use iced_native::widget::button; use iced_native::{ - Background, Clipboard, Color, Hasher, Layout, Length, Padding, Point, - Rectangle, Shell, Vector, + Clipboard, Hasher, Layout, Length, Padding, Point, Rectangle, Shell, }; use iced_style::button::StyleSheet; use std::any::Any; +pub use button::State; + pub struct Button<'a, Message, Renderer> { content: Element<'a, Message, Renderer>, on_press: Option<Message>, @@ -51,7 +52,7 @@ where } fn state(&self) -> Box<dyn Any> { - Box::new(State { is_pressed: false }) + Box::new(State::new()) } fn children(&self) -> &[Element<Message, Renderer>] { @@ -71,6 +72,8 @@ where self.tag().hash(state); self.width.hash(state); + self.height.hash(state); + self.padding.hash(state); self.content.as_widget().hash_layout(state); } @@ -79,20 +82,16 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits - .width(self.width) - .height(self.height) - .pad(self.padding); - - let mut content = self.content.as_widget().layout(renderer, &limits); - content.move_to(Point::new( - self.padding.left.into(), - self.padding.top.into(), - )); - - let size = limits.resolve(content.size()).pad(self.padding); - - layout::Node::with_children(size, vec![content]) + button::layout( + renderer, + limits, + self.width, + self.height, + self.padding, + |renderer, limits| { + self.content.as_widget().layout(renderer, &limits) + }, + ) } fn on_event( @@ -105,12 +104,6 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - let state = if let Some(state) = tree.state.downcast_mut::<State>() { - state - } else { - return event::Status::Ignored; - }; - if let event::Status::Captured = self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), @@ -123,42 +116,14 @@ where return event::Status::Captured; } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if self.on_press.is_some() { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - state.is_pressed = true; - - return event::Status::Captured; - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = self.on_press.clone() { - let bounds = layout.bounds(); - - if state.is_pressed { - state.is_pressed = false; - - if bounds.contains(cursor_position) { - shell.publish(on_press); - } - - return event::Status::Captured; - } - } - } - Event::Touch(touch::Event::FingerLost { .. }) => { - state.is_pressed = false; - } - _ => {} - } - - event::Status::Ignored + button::update( + event, + layout, + cursor_position, + shell, + &self.on_press, + || tree.state_mut::<State>(), + ) } fn draw( @@ -170,60 +135,17 @@ where cursor_position: Point, _viewport: &Rectangle, ) { - let state = if let Some(state) = tree.state.downcast_ref::<State>() { - state - } else { - return; - }; - let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); - let is_mouse_over = bounds.contains(cursor_position); - let is_disabled = self.on_press.is_none(); - - let styling = if is_disabled { - self.style_sheet.disabled() - } else if is_mouse_over { - if state.is_pressed { - self.style_sheet.pressed() - } else { - self.style_sheet.hovered() - } - } else { - self.style_sheet.active() - }; - - if styling.background.is_some() || styling.border_width > 0.0 { - if styling.shadow_offset != Vector::default() { - // TODO: Implement proper shadow support - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + styling.shadow_offset.x, - y: bounds.y + styling.shadow_offset.y, - ..bounds - }, - border_radius: styling.border_radius, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Background::Color([0.0, 0.0, 0.0, 0.5].into()), - ); - } - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: styling.border_radius, - border_width: styling.border_width, - border_color: styling.border_color, - }, - styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } + let styling = button::draw( + renderer, + bounds, + cursor_position, + self.on_press.is_some(), + self.style_sheet.as_ref(), + || tree.state::<State>(), + ); self.content.as_widget().draw( &tree.children[0], @@ -245,22 +167,14 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); - let is_disabled = self.on_press.is_none(); - - if is_mouse_over && !is_disabled { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } + button::mouse_interaction( + layout, + cursor_position, + self.on_press.is_some(), + ) } } -#[derive(Debug, Clone)] -struct State { - is_pressed: bool, -} - impl<'a, Message, Renderer> Into<Element<'a, Message, Renderer>> for Button<'a, Message, Renderer> where diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 2353edc5..1ab6d80b 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -58,4 +58,18 @@ impl Tree { *self = Self::new(new); } } + + pub fn state<T>(&self) -> &T + where + T: 'static, + { + self.state.downcast_ref().expect("Downcast widget state") + } + + pub fn state_mut<T>(&mut self) -> &mut T + where + T: 'static, + { + self.state.downcast_mut().expect("Downcast widget state") + } } |