From ce309db37b8eb9860ae1f1be1710fb39e7a9edea Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Mar 2024 03:57:03 +0100 Subject: Try new approach to theming for `Slider` --- widget/src/slider.rs | 548 ++++++++++++++++++----------------------- widget/src/vertical_slider.rs | 558 +++++++++++++++++++----------------------- 2 files changed, 483 insertions(+), 623 deletions(-) (limited to 'widget') diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 65bc1772..ce02a0a6 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -17,7 +17,7 @@ use crate::core::{ use std::ops::RangeInclusive; pub use iced_style::slider::{ - Appearance, Handle, HandleShape, Rail, StyleSheet, + Appearance, Handle, HandleShape, Rail, Status, StyleSheet, }; /// An horizontal bar and a handle that selects a single value from a range of @@ -58,7 +58,7 @@ where on_release: Option, width: Length, height: f32, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> @@ -104,7 +104,7 @@ where on_release: None, width: Length::Fill, height: Self::DEFAULT_HEIGHT, - style: Default::default(), + style: Theme::default(), } } @@ -140,8 +140,8 @@ where } /// Sets the style of the [`Slider`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { + self.style = style; self } @@ -173,7 +173,7 @@ where } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::default()) } fn size(&self) -> Size { @@ -203,355 +203,283 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - event, - layout, - cursor, - shell, - tree.state.downcast_mut::(), - &mut self.value, - self.default, - &self.range, - self.step, - self.shift_step, - self.on_change.as_ref(), - &self.on_release, - ) - } + let state = tree.state.downcast_mut::(); - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - ) { - draw( - renderer, - layout, - cursor, - tree.state.downcast_ref::(), - self.value, - &self.range, - theme, - &self.style, - ); - } + let is_dragging = state.is_dragging; + let current_value = self.value; - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor, tree.state.downcast_ref::()) - } -} + let locate = |cursor_position: Point| -> Option { + let bounds = layout.bounds(); + let new_value = if cursor_position.x <= bounds.x { + Some(*self.range.start()) + } else if cursor_position.x >= bounds.x + bounds.width { + Some(*self.range.end()) + } else { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); -impl<'a, T, Message, Theme, Renderer> From> - for Element<'a, Message, Theme, Renderer> -where - T: Copy + Into + num_traits::FromPrimitive + 'a, - Message: Clone + 'a, - Theme: StyleSheet + 'a, - Renderer: crate::core::Renderer + 'a, -{ - fn from( - slider: Slider<'a, T, Message, Theme>, - ) -> Element<'a, Message, Theme, Renderer> { - Element::new(slider) - } -} + let start = (*self.range.start()).into(); + let end = (*self.range.end()).into(); -/// Processes an [`Event`] and updates the [`State`] of a [`Slider`] -/// accordingly. -pub fn update( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - state: &mut State, - value: &mut T, - default: Option, - range: &RangeInclusive, - step: T, - shift_step: Option, - on_change: &dyn Fn(T) -> Message, - on_release: &Option, -) -> event::Status -where - T: Copy + Into + num_traits::FromPrimitive, - Message: Clone, -{ - let is_dragging = state.is_dragging; - let current_value = *value; + let percent = f64::from(cursor_position.x - bounds.x) + / f64::from(bounds.width); - let locate = |cursor_position: Point| -> Option { - let bounds = layout.bounds(); - let new_value = if cursor_position.x <= bounds.x { - Some(*range.start()) - } else if cursor_position.x >= bounds.x + bounds.width { - Some(*range.end()) - } else { + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; + + T::from_f64(value) + }; + + new_value + }; + + let increment = |value: T| -> Option { let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) + self.shift_step.unwrap_or(self.step) } else { - step + self.step } .into(); - let start = (*range.start()).into(); - let end = (*range.end()).into(); + let steps = (value.into() / step).round(); + let new_value = step * (steps + 1.0); - let percent = f64::from(cursor_position.x - bounds.x) - / f64::from(bounds.width); - - let steps = (percent * (end - start) / step).round(); - let value = steps * step + start; + if new_value > (*self.range.end()).into() { + return Some(*self.range.end()); + } - T::from_f64(value) + T::from_f64(new_value) }; - new_value - }; - - let increment = |value: T| -> Option { - let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) - } else { - step - } - .into(); - - let steps = (value.into() / step).round(); - let new_value = step * (steps + 1.0); - - if new_value > (*range.end()).into() { - return Some(*range.end()); - } + let decrement = |value: T| -> Option { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - T::from_f64(new_value) - }; + let steps = (value.into() / step).round(); + let new_value = step * (steps - 1.0); - let decrement = |value: T| -> Option { - let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) - } else { - step - } - .into(); + if new_value < (*self.range.start()).into() { + return Some(*self.range.start()); + } - let steps = (value.into() / step).round(); - let new_value = step * (steps - 1.0); + T::from_f64(new_value) + }; - if new_value < (*range.start()).into() { - return Some(*range.start()); - } + let change = |new_value: T| { + if (self.value.into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((self.on_change)(new_value)); - T::from_f64(new_value) - }; + self.value = new_value; + } + }; - let change = |new_value: T| { - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor_position) = + cursor.position_over(layout.bounds()) + { + if state.keyboard_modifiers.command() { + let _ = self.default.map(change); + state.is_dragging = false; + } else { + let _ = locate(cursor_position).map(change); + state.is_dragging = true; + } - *value = new_value; - } - }; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(cursor_position) = cursor.position_over(layout.bounds()) - { - if state.keyboard_modifiers.command() { - let _ = default.map(change); - state.is_dragging = false; - } else { - let _ = locate(cursor_position).map(change); - state.is_dragging = true; + return event::Status::Captured; } - - return event::Status::Captured; } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if is_dragging { - if let Some(on_release) = on_release.clone() { - shell.publish(on_release); - } - state.is_dragging = false; + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if is_dragging { + if let Some(on_release) = self.on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; - return event::Status::Captured; + return event::Status::Captured; + } } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if is_dragging { - let _ = cursor.position().and_then(locate).map(change); + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if is_dragging { + let _ = cursor.position().and_then(locate).map(change); - return event::Status::Captured; + return event::Status::Captured; + } } - } - Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { - if cursor.position_over(layout.bounds()).is_some() { - match key { - Key::Named(key::Named::ArrowUp) => { - let _ = increment(current_value).map(change); + Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { + if cursor.position_over(layout.bounds()).is_some() { + match key { + Key::Named(key::Named::ArrowUp) => { + let _ = increment(current_value).map(change); + } + Key::Named(key::Named::ArrowDown) => { + let _ = decrement(current_value).map(change); + } + _ => (), } - Key::Named(key::Named::ArrowDown) => { - let _ = decrement(current_value).map(change); - } - _ => (), - } - return event::Status::Captured; + return event::Status::Captured; + } } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + state.keyboard_modifiers = modifiers; + } + _ => {} } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; - } - _ => {} + + event::Status::Ignored } - event::Status::Ignored -} + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); -/// Draws a [`Slider`]. -pub fn draw( - renderer: &mut Renderer, - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, - value: T, - range: &RangeInclusive, - theme: &Theme, - style: &Theme::Style, -) where - T: Into + Copy, - Theme: StyleSheet, - Renderer: crate::core::Renderer, -{ - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - let style = if state.is_dragging { - theme.dragging(style) - } else if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; - - let (handle_width, handle_height, handle_border_radius) = - match style.handle.shape { - HandleShape::Circle { radius } => { - (radius * 2.0, radius * 2.0, radius.into()) - } - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), bounds.height, border_radius), + let style = (self.style)( + theme, + if state.is_dragging { + Status::Dragging + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }, + ); + + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (radius * 2.0, radius * 2.0, radius.into()) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), bounds.height, border_radius), + }; + + let value = self.value.into() as f32; + let (range_start, range_end) = { + let (start, end) = self.range.clone().into_inner(); + + (start.into() as f32, end.into() as f32) }; - let value = value.into() as f32; - let (range_start, range_end) = { - let (start, end) = range.clone().into_inner(); - - (start.into() as f32, end.into() as f32) - }; - - let offset = if range_start >= range_end { - 0.0 - } else { - (bounds.width - handle_width) * (value - range_start) - / (range_end - range_start) - }; - - let rail_y = bounds.y + bounds.height / 2.0; - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y - style.rail.width / 2.0, - width: offset + handle_width / 2.0, - height: style.rail.width, - }, - border: Border::with_radius(style.rail.border_radius), - ..renderer::Quad::default() - }, - style.rail.colors.0, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + offset + handle_width / 2.0, - y: rail_y - style.rail.width / 2.0, - width: bounds.width - offset - handle_width / 2.0, - height: style.rail.width, + let offset = if range_start >= range_end { + 0.0 + } else { + (bounds.width - handle_width) * (value - range_start) + / (range_end - range_start) + }; + + let rail_y = bounds.y + bounds.height / 2.0; + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y - style.rail.width / 2.0, + width: offset + handle_width / 2.0, + height: style.rail.width, + }, + border: Border::with_radius(style.rail.border_radius), + ..renderer::Quad::default() }, - border: Border::with_radius(style.rail.border_radius), - ..renderer::Quad::default() - }, - style.rail.colors.1, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + offset, - y: rail_y - handle_height / 2.0, - width: handle_width, - height: handle_height, + style.rail.colors.0, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + offset + handle_width / 2.0, + y: rail_y - style.rail.width / 2.0, + width: bounds.width - offset - handle_width / 2.0, + height: style.rail.width, + }, + border: Border::with_radius(style.rail.border_radius), + ..renderer::Quad::default() }, - border: Border { - radius: handle_border_radius, - width: style.handle.border_width, - color: style.handle.border_color, + style.rail.colors.1, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + offset, + y: rail_y - handle_height / 2.0, + width: handle_width, + height: handle_height, + }, + border: Border { + radius: handle_border_radius, + width: style.handle.border_width, + color: style.handle.border_color, + }, + ..renderer::Quad::default() }, - ..renderer::Quad::default() - }, - style.handle.color, - ); + style.handle.color, + ); + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + + if state.is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + } + } } -/// Computes the current [`mouse::Interaction`] of a [`Slider`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - if state.is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() +impl<'a, T, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + T: Copy + Into + num_traits::FromPrimitive + 'a, + Message: Clone + 'a, + Theme: StyleSheet + 'a, + Renderer: crate::core::Renderer + 'a, +{ + fn from( + slider: Slider<'a, T, Message, Theme>, + ) -> Element<'a, Message, Theme, Renderer> { + Element::new(slider) } } -/// The local state of a [`Slider`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { +struct State { is_dragging: bool, keyboard_modifiers: keyboard::Modifiers, } - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 8f7c88da..b6903001 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -3,7 +3,9 @@ //! A [`VerticalSlider`] has some local [`State`]. use std::ops::RangeInclusive; -pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet}; +pub use crate::style::slider::{ + Appearance, Handle, HandleShape, Status, StyleSheet, +}; use crate::core; use crate::core::event::{self, Event}; @@ -55,7 +57,7 @@ where on_release: Option, width: f32, height: Length, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme> @@ -101,7 +103,7 @@ where on_release: None, width: Self::DEFAULT_WIDTH, height: Length::Fill, - style: Default::default(), + style: Theme::default(), } } @@ -137,7 +139,10 @@ where } /// Sets the style of the [`VerticalSlider`]. - pub fn style(mut self, style: impl Into) -> Self { + pub fn style( + mut self, + style: impl Into Appearance>, + ) -> Self { self.style = style.into(); self } @@ -170,7 +175,7 @@ where } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::default()) } fn size(&self) -> Size { @@ -200,360 +205,287 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - event, - layout, - cursor, - shell, - tree.state.downcast_mut::(), - &mut self.value, - self.default, - &self.range, - self.step, - self.shift_step, - self.on_change.as_ref(), - &self.on_release, - ) - } + let state = tree.state.downcast_mut::(); + let is_dragging = state.is_dragging; + let current_value = self.value; - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - ) { - draw( - renderer, - layout, - cursor, - tree.state.downcast_ref::(), - self.value, - &self.range, - theme, - &self.style, - ); - } + let locate = |cursor_position: Point| -> Option { + let bounds = layout.bounds(); - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor, tree.state.downcast_ref::()) - } -} + let new_value = if cursor_position.y >= bounds.y + bounds.height { + Some(*self.range.start()) + } else if cursor_position.y <= bounds.y { + Some(*self.range.end()) + } else { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); -impl<'a, T, Message, Theme, Renderer> - From> - for Element<'a, Message, Theme, Renderer> -where - T: Copy + Into + num_traits::FromPrimitive + 'a, - Message: Clone + 'a, - Theme: StyleSheet + 'a, - Renderer: core::Renderer + 'a, -{ - fn from( - slider: VerticalSlider<'a, T, Message, Theme>, - ) -> Element<'a, Message, Theme, Renderer> { - Element::new(slider) - } -} + let start = (*self.range.start()).into(); + let end = (*self.range.end()).into(); -/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`] -/// accordingly. -pub fn update( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - state: &mut State, - value: &mut T, - default: Option, - range: &RangeInclusive, - step: T, - shift_step: Option, - on_change: &dyn Fn(T) -> Message, - on_release: &Option, -) -> event::Status -where - T: Copy + Into + num_traits::FromPrimitive, - Message: Clone, -{ - let is_dragging = state.is_dragging; - let current_value = *value; + let percent = 1.0 + - f64::from(cursor_position.y - bounds.y) + / f64::from(bounds.height); - let locate = |cursor_position: Point| -> Option { - let bounds = layout.bounds(); + let steps = (percent * (end - start) / step).round(); + let value = steps * step + start; - let new_value = if cursor_position.y >= bounds.y + bounds.height { - Some(*range.start()) - } else if cursor_position.y <= bounds.y { - Some(*range.end()) - } else { + T::from_f64(value) + }; + + new_value + }; + + let increment = |value: T| -> Option { let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) + self.shift_step.unwrap_or(self.step) } else { - step + self.step } .into(); - let start = (*range.start()).into(); - let end = (*range.end()).into(); + let steps = (value.into() / step).round(); + let new_value = step * (steps + 1.0); - let percent = 1.0 - - f64::from(cursor_position.y - bounds.y) - / f64::from(bounds.height); - - let steps = (percent * (end - start) / step).round(); - let value = steps * step + start; + if new_value > (*self.range.end()).into() { + return Some(*self.range.end()); + } - T::from_f64(value) + T::from_f64(new_value) }; - new_value - }; - - let increment = |value: T| -> Option { - let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) - } else { - step - } - .into(); - - let steps = (value.into() / step).round(); - let new_value = step * (steps + 1.0); - - if new_value > (*range.end()).into() { - return Some(*range.end()); - } - - T::from_f64(new_value) - }; + let decrement = |value: T| -> Option { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - let decrement = |value: T| -> Option { - let step = if state.keyboard_modifiers.shift() { - shift_step.unwrap_or(step) - } else { - step - } - .into(); + let steps = (value.into() / step).round(); + let new_value = step * (steps - 1.0); - let steps = (value.into() / step).round(); - let new_value = step * (steps - 1.0); + if new_value < (*self.range.start()).into() { + return Some(*self.range.start()); + } - if new_value < (*range.start()).into() { - return Some(*range.start()); - } + T::from_f64(new_value) + }; - T::from_f64(new_value) - }; + let change = |new_value: T| { + if (self.value.into() - new_value.into()).abs() > f64::EPSILON { + shell.publish((self.on_change)(new_value)); - let change = |new_value: T| { - if ((*value).into() - new_value.into()).abs() > f64::EPSILON { - shell.publish((on_change)(new_value)); + self.value = new_value; + } + }; - *value = new_value; - } - }; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if let Some(cursor_position) = cursor.position_over(layout.bounds()) - { - if state.keyboard_modifiers.control() - || state.keyboard_modifiers.command() + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if let Some(cursor_position) = + cursor.position_over(layout.bounds()) { - let _ = default.map(change); - state.is_dragging = false; - } else { - let _ = locate(cursor_position).map(change); - state.is_dragging = true; - } + if state.keyboard_modifiers.control() + || state.keyboard_modifiers.command() + { + let _ = self.default.map(change); + state.is_dragging = false; + } else { + let _ = locate(cursor_position).map(change); + state.is_dragging = true; + } - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if is_dragging { - if let Some(on_release) = on_release.clone() { - shell.publish(on_release); + return event::Status::Captured; } - state.is_dragging = false; + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if is_dragging { + if let Some(on_release) = self.on_release.clone() { + shell.publish(on_release); + } + state.is_dragging = false; - return event::Status::Captured; + return event::Status::Captured; + } } - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if is_dragging { - let _ = cursor.position().and_then(locate).map(change); + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if is_dragging { + let _ = cursor.position().and_then(locate).map(change); - return event::Status::Captured; + return event::Status::Captured; + } } - } - Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { - if cursor.position_over(layout.bounds()).is_some() { - match key { - Key::Named(key::Named::ArrowUp) => { - let _ = increment(current_value).map(change); - } - Key::Named(key::Named::ArrowDown) => { - let _ = decrement(current_value).map(change); + Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { + if cursor.position_over(layout.bounds()).is_some() { + match key { + Key::Named(key::Named::ArrowUp) => { + let _ = increment(current_value).map(change); + } + Key::Named(key::Named::ArrowDown) => { + let _ = decrement(current_value).map(change); + } + _ => (), } - _ => (), - } - return event::Status::Captured; + return event::Status::Captured; + } } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + state.keyboard_modifiers = modifiers; + } + _ => {} } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; - } - _ => {} + + event::Status::Ignored } - event::Status::Ignored -} + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); -/// Draws a [`VerticalSlider`]. -pub fn draw( - renderer: &mut Renderer, - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, - value: T, - range: &RangeInclusive, - style_sheet: &Theme, - style: &Theme::Style, -) where - T: Into + Copy, - Theme: StyleSheet, - Renderer: core::Renderer, -{ - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - let style = if state.is_dragging { - style_sheet.dragging(style) - } else if is_mouse_over { - style_sheet.hovered(style) - } else { - style_sheet.active(style) - }; - - let (handle_width, handle_height, handle_border_radius) = - match style.handle.shape { - HandleShape::Circle { radius } => { - (radius * 2.0, radius * 2.0, radius.into()) - } - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), bounds.width, border_radius), + let style = (self.style)( + theme, + if state.is_dragging { + Status::Dragging + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }, + ); + + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (radius * 2.0, radius * 2.0, radius.into()) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), bounds.width, border_radius), + }; + + let value = self.value.into() as f32; + let (range_start, range_end) = { + let (start, end) = self.range.clone().into_inner(); + + (start.into() as f32, end.into() as f32) }; - let value = value.into() as f32; - let (range_start, range_end) = { - let (start, end) = range.clone().into_inner(); - - (start.into() as f32, end.into() as f32) - }; - - let offset = if range_start >= range_end { - 0.0 - } else { - (bounds.height - handle_width) * (value - range_end) - / (range_start - range_end) - }; - - let rail_x = bounds.x + bounds.width / 2.0; - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: rail_x - style.rail.width / 2.0, - y: bounds.y, - width: style.rail.width, - height: offset + handle_width / 2.0, - }, - border: Border::with_radius(style.rail.border_radius), - ..renderer::Quad::default() - }, - style.rail.colors.1, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: rail_x - style.rail.width / 2.0, - y: bounds.y + offset + handle_width / 2.0, - width: style.rail.width, - height: bounds.height - offset - handle_width / 2.0, + let offset = if range_start >= range_end { + 0.0 + } else { + (bounds.height - handle_width) * (value - range_end) + / (range_start - range_end) + }; + + let rail_x = bounds.x + bounds.width / 2.0; + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - style.rail.width / 2.0, + y: bounds.y, + width: style.rail.width, + height: offset + handle_width / 2.0, + }, + border: Border::with_radius(style.rail.border_radius), + ..renderer::Quad::default() }, - border: Border::with_radius(style.rail.border_radius), - ..renderer::Quad::default() - }, - style.rail.colors.0, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: rail_x - handle_height / 2.0, - y: bounds.y + offset, - width: handle_height, - height: handle_width, + style.rail.colors.1, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - style.rail.width / 2.0, + y: bounds.y + offset + handle_width / 2.0, + width: style.rail.width, + height: bounds.height - offset - handle_width / 2.0, + }, + border: Border::with_radius(style.rail.border_radius), + ..renderer::Quad::default() }, - border: Border { - radius: handle_border_radius, - width: style.handle.border_width, - color: style.handle.border_color, + style.rail.colors.0, + ); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: rail_x - handle_height / 2.0, + y: bounds.y + offset, + width: handle_height, + height: handle_width, + }, + border: Border { + radius: handle_border_radius, + width: style.handle.border_width, + color: style.handle.border_color, + }, + ..renderer::Quad::default() }, - ..renderer::Quad::default() - }, - style.handle.color, - ); + style.handle.color, + ); + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + + if state.is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + } + } } -/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - if state.is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() +impl<'a, T, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + T: Copy + Into + num_traits::FromPrimitive + 'a, + Message: Clone + 'a, + Theme: StyleSheet + 'a, + Renderer: core::Renderer + 'a, +{ + fn from( + slider: VerticalSlider<'a, T, Message, Theme>, + ) -> Element<'a, Message, Theme, Renderer> { + Element::new(slider) } } -/// The local state of a [`VerticalSlider`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { +struct State { is_dragging: bool, keyboard_modifiers: keyboard::Modifiers, } - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} -- cgit From 4130ae4be95ce850263fbc55f490b68a95361d58 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Mar 2024 19:31:26 +0100 Subject: Simplify theming for `Text` widget --- widget/src/checkbox.rs | 2 +- widget/src/tooltip.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'widget') diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 0ff4d58b..3a192fba 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -39,7 +39,7 @@ pub struct Checkbox< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet + crate::text::StyleSheet, + Theme: StyleSheet, Renderer: text::Renderer, { is_checked: bool, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index d8a1e131..51969aec 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -20,7 +20,7 @@ pub struct Tooltip< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: container::StyleSheet + crate::text::StyleSheet, + Theme: container::StyleSheet, Renderer: text::Renderer, { content: Element<'a, Message, Theme, Renderer>, @@ -34,7 +34,7 @@ pub struct Tooltip< impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet + crate::text::StyleSheet, + Theme: container::StyleSheet, Renderer: text::Renderer, { /// The default padding of a [`Tooltip`] drawn by this renderer. -- cgit From db92e1c942154bee474fee5e2c187f8a52a1bb96 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Mar 2024 19:32:20 +0100 Subject: Enhnace `Themer` to allow derivation from current `Theme` --- widget/src/helpers.rs | 13 +++-- widget/src/themer.rs | 133 ++++++++++++++++++++++++++++---------------------- 2 files changed, 80 insertions(+), 66 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index ed385ea5..e6322926 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -15,7 +15,6 @@ use crate::rule::{self, Rule}; use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; -use crate::style::application; use crate::text::{self, Text}; use crate::text_editor::{self, TextEditor}; use crate::text_input::{self, TextInput}; @@ -440,13 +439,13 @@ where } /// A widget that applies any `Theme` to its contents. -pub fn themer<'a, Message, Theme, Renderer>( - theme: Theme, - content: impl Into>, -) -> Themer<'a, Message, Theme, Renderer> +pub fn themer<'a, Message, OldTheme, NewTheme, F, Renderer>( + to_theme: F, + content: impl Into>, +) -> Themer<'a, Message, OldTheme, NewTheme, F, Renderer> where + F: Fn(&OldTheme) -> NewTheme, Renderer: core::Renderer, - Theme: application::StyleSheet, { - Themer::new(theme, content) + Themer::new(to_theme, content) } diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 3a5fd823..a7eabd2c 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -7,58 +7,68 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ - Background, Clipboard, Element, Layout, Length, Point, Rectangle, Shell, - Size, Vector, Widget, + Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, + Shell, Size, Vector, Widget, }; -use crate::style::application; + +use std::marker::PhantomData; /// A widget that applies any `Theme` to its contents. /// /// This widget can be useful to leverage multiple `Theme` /// types in an application. #[allow(missing_debug_implementations)] -pub struct Themer<'a, Message, Theme, Renderer> +pub struct Themer<'a, Message, Theme, NewTheme, F, Renderer = crate::Renderer> where + F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, - Theme: application::StyleSheet, { - content: Element<'a, Message, Theme, Renderer>, - theme: Theme, - style: Theme::Style, - show_background: bool, + content: Element<'a, Message, NewTheme, Renderer>, + to_theme: F, + text_color: Option Color>, + background: Option Background>, + old_theme: PhantomData, } -impl<'a, Message, Theme, Renderer> Themer<'a, Message, Theme, Renderer> +impl<'a, Message, Theme, NewTheme, F, Renderer> + Themer<'a, Message, Theme, NewTheme, F, Renderer> where + F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, - Theme: application::StyleSheet, { /// Creates an empty [`Themer`] that applies the given `Theme` /// to the provided `content`. - pub fn new(theme: Theme, content: T) -> Self + pub fn new(to_theme: F, content: T) -> Self where - T: Into>, + T: Into>, { Self { content: content.into(), - theme, - style: Theme::Style::default(), - show_background: false, + to_theme, + text_color: None, + background: None, + old_theme: PhantomData, } } - /// Sets whether to draw the background color of the `Theme`. - pub fn background(mut self, background: bool) -> Self { - self.show_background = background; + /// Sets the default text [`Color`] of the [`Themer`]. + pub fn text_color(mut self, f: fn(&NewTheme) -> Color) -> Self { + self.text_color = Some(f); + self + } + + /// Sets the [`Background`] of the [`Themer`]. + pub fn background(mut self, f: fn(&NewTheme) -> Background) -> Self { + self.background = Some(f); self } } -impl<'a, AnyTheme, Message, Theme, Renderer> Widget - for Themer<'a, Message, Theme, Renderer> +impl<'a, Message, Theme, NewTheme, F, Renderer> Widget + for Themer<'a, Message, Theme, NewTheme, F, Renderer> where + F: Fn(&Theme) -> NewTheme, Renderer: crate::core::Renderer, - Theme: application::StyleSheet, { fn tag(&self) -> tree::Tag { self.content.as_widget().tag() @@ -134,38 +144,36 @@ where &self, tree: &Tree, renderer: &mut Renderer, - _theme: &AnyTheme, - _style: &renderer::Style, + theme: &Theme, + style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { - let appearance = self.theme.appearance(&self.style); + let theme = (self.to_theme)(theme); - if self.show_background { + if let Some(background) = self.background { container::draw_background( renderer, &container::Appearance { - background: Some(Background::Color( - appearance.background_color, - )), + background: Some(background(&theme)), ..container::Appearance::default() }, layout.bounds(), ); } - self.content.as_widget().draw( - tree, - renderer, - &self.theme, - &renderer::Style { - text_color: appearance.text_color, - }, - layout, - cursor, - viewport, - ); + let style = if let Some(text_color) = self.text_color { + renderer::Style { + text_color: text_color(&theme), + } + } else { + *style + }; + + self.content + .as_widget() + .draw(tree, renderer, &theme, &style, layout, cursor, viewport); } fn overlay<'b>( @@ -174,15 +182,15 @@ where layout: Layout<'_>, renderer: &Renderer, translation: Vector, - ) -> Option> { - struct Overlay<'a, Message, Theme, Renderer> { - theme: &'a Theme, - content: overlay::Element<'a, Message, Theme, Renderer>, + ) -> Option> { + struct Overlay<'a, Message, Theme, NewTheme, Renderer> { + to_theme: &'a dyn Fn(&Theme) -> NewTheme, + content: overlay::Element<'a, Message, NewTheme, Renderer>, } - impl<'a, AnyTheme, Message, Theme, Renderer> - overlay::Overlay - for Overlay<'a, Message, Theme, Renderer> + impl<'a, Message, Theme, NewTheme, Renderer> + overlay::Overlay + for Overlay<'a, Message, Theme, NewTheme, Renderer> where Renderer: crate::core::Renderer, { @@ -197,13 +205,18 @@ where fn draw( &self, renderer: &mut Renderer, - _theme: &AnyTheme, + theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, ) { - self.content - .draw(renderer, self.theme, style, layout, cursor); + self.content.draw( + renderer, + &(self.to_theme)(theme), + style, + layout, + cursor, + ); } fn on_event( @@ -252,12 +265,12 @@ where &'b mut self, layout: Layout<'_>, renderer: &Renderer, - ) -> Option> + ) -> Option> { self.content .overlay(layout, renderer) .map(|content| Overlay { - theme: self.theme, + to_theme: &self.to_theme, content, }) .map(|overlay| overlay::Element::new(Box::new(overlay))) @@ -268,24 +281,26 @@ where .as_widget_mut() .overlay(tree, layout, renderer, translation) .map(|content| Overlay { - theme: &self.theme, + to_theme: &self.to_theme, content, }) .map(|overlay| overlay::Element::new(Box::new(overlay))) } } -impl<'a, AnyTheme, Message, Theme, Renderer> - From> - for Element<'a, Message, AnyTheme, Renderer> +impl<'a, Message, Theme, NewTheme, F, Renderer> + From> + for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: 'a + application::StyleSheet, + Theme: 'a, + NewTheme: 'a, + F: Fn(&Theme) -> NewTheme + 'a, Renderer: 'a + crate::core::Renderer, { fn from( - themer: Themer<'a, Message, Theme, Renderer>, - ) -> Element<'a, Message, AnyTheme, Renderer> { + themer: Themer<'a, Message, Theme, NewTheme, F, Renderer>, + ) -> Element<'a, Message, Theme, Renderer> { Element::new(themer) } } -- cgit From f4a4845ddbdced81ae4ff60bfa19f0e602d84709 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 4 Mar 2024 20:42:37 +0100 Subject: Simplify theming for `Button` widget --- widget/src/button.rs | 382 +++++++++++++++++++++++++++++++------------------- widget/src/helpers.rs | 2 +- 2 files changed, 235 insertions(+), 149 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 867fbfaf..798a8206 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -10,11 +10,11 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, - Shell, Size, Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Padding, + Rectangle, Shadow, Shell, Size, Vector, Widget, }; - -pub use crate::style::button::{Appearance, StyleSheet}; +use crate::style::theme::palette; +use crate::style::Theme; /// A generic widget that produces a message when pressed. /// @@ -53,7 +53,7 @@ pub use crate::style::button::{Appearance, StyleSheet}; #[allow(missing_debug_implementations)] pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> where - Theme: StyleSheet, + Theme: Style, Renderer: crate::core::Renderer, { content: Element<'a, Message, Theme, Renderer>, @@ -62,12 +62,12 @@ where height: Length, padding: Padding, clip: bool, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> where - Theme: StyleSheet, + Theme: Style, Renderer: crate::core::Renderer, { /// Creates a new [`Button`] with the given content. @@ -84,7 +84,7 @@ where height: size.height.fluid(), padding: Padding::new(5.0), clip: false, - style: Theme::Style::default(), + style: Theme::DEFAULT, } } @@ -124,7 +124,7 @@ where } /// Sets the style variant of this [`Button`]. - pub fn style(mut self, style: impl Into) -> Self { + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { self.style = style.into(); self } @@ -137,11 +137,16 @@ where } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +struct State { + is_pressed: bool, +} + impl<'a, Message, Theme, Renderer> Widget for Button<'a, Message, Theme, Renderer> where Message: 'a + Clone, - Theme: StyleSheet, + Theme: Style, Renderer: 'a + crate::core::Renderer, { fn tag(&self) -> tree::Tag { @@ -149,7 +154,7 @@ where } fn state(&self) -> tree::State { - tree::State::new(State::new()) + tree::State::new(State::default()) } fn children(&self) -> Vec { @@ -173,13 +178,19 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout(limits, self.width, self.height, self.padding, |limits| { - self.content.as_widget().layout( - &mut tree.children[0], - renderer, - limits, - ) - }) + layout::padded( + limits, + self.width, + self.height, + self.padding, + |limits| { + self.content.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ) + }, + ) } fn operate( @@ -223,9 +234,48 @@ where return event::Status::Captured; } - update(event, layout, cursor, shell, &self.on_press, || { - tree.state.downcast_mut::() - }) + 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 cursor.is_over(bounds) { + let state = tree.state.downcast_mut::(); + + 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 state = tree.state.downcast_mut::(); + + if state.is_pressed { + state.is_pressed = false; + + let bounds = layout.bounds(); + + if cursor.is_over(bounds) { + shell.publish(on_press); + } + + return event::Status::Captured; + } + } + } + Event::Touch(touch::Event::FingerLost { .. }) => { + let state = tree.state.downcast_mut::(); + + state.is_pressed = false; + } + _ => {} + } + + event::Status::Ignored } fn draw( @@ -240,16 +290,39 @@ where ) { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); + let is_mouse_over = cursor.is_over(bounds); - let styling = draw( - renderer, - bounds, - cursor, - self.on_press.is_some(), - theme, - &self.style, - || tree.state.downcast_ref::(), - ); + let status = if self.on_press.is_none() { + Status::Disabled + } else if is_mouse_over { + let state = tree.state.downcast_ref::(); + + if state.is_pressed { + Status::Pressed + } else { + Status::Hovered + } + } else { + Status::Active + }; + + let styling = (self.style)(theme, status); + + if styling.background.is_some() + || styling.border.width > 0.0 + || styling.shadow.color.a > 0.0 + { + renderer.fill_quad( + renderer::Quad { + bounds, + border: styling.border, + shadow: styling.shadow, + }, + styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + ); + } let viewport = if self.clip { bounds.intersection(viewport).unwrap_or(*viewport) @@ -278,7 +351,13 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction(layout, cursor, self.on_press.is_some()) + let is_mouse_over = cursor.is_over(layout.bounds()); + + if is_mouse_over && self.on_press.is_some() { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } } fn overlay<'b>( @@ -301,7 +380,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: Clone + 'a, - Theme: StyleSheet + 'a, + Theme: Style + 'a, Renderer: crate::core::Renderer + 'a, { fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { @@ -309,143 +388,150 @@ where } } -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_pressed: bool, +/// The possible status of a [`Button`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Button`] can be pressed. + Active, + /// The [`Button`] can be pressed and it is being hovered. + Hovered, + /// The [`Button`] is being pressed. + Pressed, + /// The [`Button`] cannot be pressed. + Disabled, } -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } +/// The appearance of a button. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The amount of offset to apply to the shadow of the button. + pub shadow_offset: Vector, + /// The [`Background`] of the button. + pub background: Option, + /// The text [`Color`] of the button. + pub text_color: Color, + /// The [`Border`] of the buton. + pub border: Border, + /// The [`Shadow`] of the butoon. + pub shadow: Shadow, } -/// Processes the given [`Event`] and updates the [`State`] of a [`Button`] -/// accordingly. -pub fn update<'a, Message: Clone>( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - on_press: &Option, - 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 cursor.is_over(bounds) { - let state = state(); - - state.is_pressed = true; - - return event::Status::Captured; - } - } +impl std::default::Default for Appearance { + fn default() -> Self { + Self { + shadow_offset: Vector::default(), + background: None, + text_color: Color::BLACK, + border: Border::default(), + shadow: Shadow::default(), } - 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(); +/// The default style of a [`Button`] for a given theme. +pub trait Style { + /// The default style. + const DEFAULT: fn(&Self, Status) -> Appearance; +} - if cursor.is_over(bounds) { - shell.publish(on_press); - } +impl Style for Theme { + const DEFAULT: fn(&Self, Status) -> Appearance = primary; +} - return event::Status::Captured; - } - } - } - Event::Touch(touch::Event::FingerLost { .. }) => { - let state = state(); +/// A primary button; denoting a main action. +pub fn primary(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + let base = styled(palette.primary.strong); + + match status { + Status::Active | Status::Pressed => base, + Status::Hovered => Appearance { + background: Some(Background::Color(palette.primary.base.color)), + ..base + }, + Status::Disabled => disabled(base), + } +} - state.is_pressed = false; - } - _ => {} +/// A secondary button; denoting a complementary action. +pub fn secondary(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + let base = styled(palette.secondary.base); + + match status { + Status::Active | Status::Pressed => base, + Status::Hovered => Appearance { + background: Some(Background::Color(palette.secondary.strong.color)), + ..base + }, + Status::Disabled => disabled(base), } +} - event::Status::Ignored +/// A positive button; denoting a good outcome. +pub fn positive(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + let base = styled(palette.success.base); + + match status { + Status::Active | Status::Pressed => base, + Status::Hovered => Appearance { + background: Some(Background::Color(palette.success.strong.color)), + ..base + }, + Status::Disabled => disabled(base), + } } -/// Draws a [`Button`]. -pub fn draw<'a, Theme, Renderer: crate::core::Renderer>( - renderer: &mut Renderer, - bounds: Rectangle, - cursor: mouse::Cursor, - is_enabled: bool, - theme: &Theme, - style: &Theme::Style, - state: impl FnOnce() -> &'a State, -) -> Appearance -where - Theme: StyleSheet, -{ - let is_mouse_over = cursor.is_over(bounds); +/// A destructive button; denoting a dangerous action. +pub fn destructive(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + let base = styled(palette.danger.base); + + match status { + Status::Active | Status::Pressed => base, + Status::Hovered => Appearance { + background: Some(Background::Color(palette.danger.strong.color)), + ..base + }, + Status::Disabled => disabled(base), + } +} - let styling = if !is_enabled { - theme.disabled(style) - } else if is_mouse_over { - let state = state(); +/// A text button; useful for links. +pub fn text(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); - if state.is_pressed { - theme.pressed(style) - } else { - theme.hovered(style) - } - } else { - theme.active(style) + let base = Appearance { + text_color: palette.background.base.text, + ..Appearance::default() }; - if styling.background.is_some() - || styling.border.width > 0.0 - || styling.shadow.color.a > 0.0 - { - renderer.fill_quad( - renderer::Quad { - bounds, - border: styling.border, - shadow: styling.shadow, - }, - styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); + match status { + Status::Active | Status::Pressed => base, + Status::Hovered => Appearance { + text_color: palette.background.base.text.transparentize(0.8), + ..base + }, + Status::Disabled => disabled(base), } - - styling } -/// Computes the layout of a [`Button`]. -pub fn layout( - limits: &layout::Limits, - width: Length, - height: Length, - padding: Padding, - layout_content: impl FnOnce(&layout::Limits) -> layout::Node, -) -> layout::Node { - layout::padded(limits, width, height, padding, layout_content) +fn styled(pair: palette::Pair) -> Appearance { + Appearance { + background: Some(Background::Color(pair.color)), + text_color: pair.text, + border: Border::with_radius(2), + ..Appearance::default() + } } -/// Returns the [`mouse::Interaction`] of a [`Button`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, - is_enabled: bool, -) -> mouse::Interaction { - let is_mouse_over = cursor.is_over(layout.bounds()); - - if is_mouse_over && is_enabled { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() +fn disabled(appearance: Appearance) -> Appearance { + Appearance { + background: appearance + .background + .map(|background| background.transparentize(0.5)), + text_color: appearance.text_color.transparentize(0.5), + ..appearance } } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e6322926..86331e14 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -118,7 +118,7 @@ pub fn button<'a, Message, Theme, Renderer>( ) -> Button<'a, Message, Theme, Renderer> where Renderer: core::Renderer, - Theme: button::StyleSheet, + Theme: button::Style, { Button::new(content) } -- cgit From 1f0a0c235a7729cf2d5716efeecb9c4dc972fdfa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 02:08:19 +0100 Subject: Simplify theming for `Checkbox` widget --- widget/src/checkbox.rs | 212 ++++++++++++++++++++++++++++++++++++++++++++----- widget/src/helpers.rs | 2 +- 2 files changed, 192 insertions(+), 22 deletions(-) (limited to 'widget') diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 3a192fba..54513b7d 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -9,10 +9,11 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, + Rectangle, Shell, Size, Widget, }; - -pub use crate::style::checkbox::{Appearance, StyleSheet}; +use crate::style::theme::palette; +use crate::style::Theme; /// A box that can be checked. /// @@ -39,7 +40,6 @@ pub struct Checkbox< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: text::Renderer, { is_checked: bool, @@ -53,13 +53,13 @@ pub struct Checkbox< text_shaping: text::Shaping, font: Option, icon: Icon, - style: ::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, - Theme: StyleSheet + crate::text::StyleSheet, + Theme: Style, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 20.0; @@ -91,7 +91,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Default::default(), + style: Theme::default(), } } @@ -174,10 +174,7 @@ where } /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { self.style = style.into(); self } @@ -186,7 +183,6 @@ where impl<'a, Message, Theme, Renderer> Widget for Checkbox<'a, Message, Theme, Renderer> where - Theme: StyleSheet + crate::text::StyleSheet, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { @@ -293,17 +289,20 @@ where ) { let is_mouse_over = cursor.is_over(layout.bounds()); let is_disabled = self.on_toggle.is_none(); + let is_checked = self.is_checked; let mut children = layout.children(); - let custom_style = if is_disabled { - theme.disabled(&self.style, self.is_checked) + let status = if is_disabled { + Status::Disabled { is_checked } } else if is_mouse_over { - theme.hovered(&self.style, self.is_checked) + Status::Hovered { is_checked } } else { - theme.active(&self.style, self.is_checked) + Status::Active { is_checked } }; + let appearance = (self.style)(theme, status); + { let layout = children.next().unwrap(); let bounds = layout.bounds(); @@ -311,10 +310,10 @@ where renderer.fill_quad( renderer::Quad { bounds, - border: custom_style.border, + border: appearance.border, ..renderer::Quad::default() }, - custom_style.background, + appearance.background, ); let Icon { @@ -339,7 +338,7 @@ where shaping: *shaping, }, bounds.center(), - custom_style.icon_color, + appearance.icon_color, *viewport, ); } @@ -354,7 +353,7 @@ where label_layout, tree.state.downcast_ref(), crate::text::Appearance { - color: custom_style.text_color, + color: appearance.text_color, }, viewport, ); @@ -366,7 +365,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: 'a + StyleSheet + crate::text::StyleSheet, + Theme: 'a, Renderer: 'a + text::Renderer, { fn from( @@ -390,3 +389,174 @@ pub struct Icon { /// The shaping strategy of the icon. pub shaping: text::Shaping, } + +/// The possible status of a [`Checkbox`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Checkbox`] can be interacted with. + Active { + /// Indicates if the [`Checkbox`] is currently checked. + is_checked: bool, + }, + /// The [`Checkbox`] can be interacted with and it is being hovered. + Hovered { + /// Indicates if the [`Checkbox`] is currently checked. + is_checked: bool, + }, + /// The [`Checkbox`] cannot be interacted with. + Disabled { + /// Indicates if the [`Checkbox`] is currently checked. + is_checked: bool, + }, +} + +/// The appearance of a checkbox. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the checkbox. + pub background: Background, + /// The icon [`Color`] of the checkbox. + pub icon_color: Color, + /// The [`Border`] of hte checkbox. + pub border: Border, + /// The text [`Color`] of the checkbox. + pub text_color: Option, +} + +/// A set of rules that dictate the style of a checkbox. +pub trait Style { + /// The supported style of the [`StyleSheet`]. + fn default() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn default() -> fn(&Self, Status) -> Appearance { + primary + } +} + +/// A primary checkbox; denoting a main toggle. +pub fn primary(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + match status { + Status::Active { is_checked } => styled( + palette.primary.strong.text, + palette.background.base, + palette.primary.strong, + is_checked, + ), + Status::Hovered { is_checked } => styled( + palette.primary.strong.text, + palette.background.weak, + palette.primary.base, + is_checked, + ), + Status::Disabled { is_checked } => styled( + palette.primary.strong.text, + palette.background.weak, + palette.background.strong, + is_checked, + ), + } +} + +/// A secondary checkbox; denoting a complementary toggle. +pub fn secondary(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + match status { + Status::Active { is_checked } => styled( + palette.background.base.text, + palette.background.base, + palette.background.strong, + is_checked, + ), + Status::Hovered { is_checked } => styled( + palette.background.base.text, + palette.background.weak, + palette.background.strong, + is_checked, + ), + Status::Disabled { is_checked } => styled( + palette.background.strong.color, + palette.background.weak, + palette.background.weak, + is_checked, + ), + } +} + +/// A success checkbox; denoting a positive toggle. +pub fn success(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + match status { + Status::Active { is_checked } => styled( + palette.success.base.text, + palette.background.base, + palette.success.base, + is_checked, + ), + Status::Hovered { is_checked } => styled( + palette.success.base.text, + palette.background.weak, + palette.success.base, + is_checked, + ), + Status::Disabled { is_checked } => styled( + palette.success.base.text, + palette.background.weak, + palette.success.weak, + is_checked, + ), + } +} + +/// A danger checkbox; denoting a negaive toggle. +pub fn danger(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + match status { + Status::Active { is_checked } => styled( + palette.danger.base.text, + palette.background.base, + palette.danger.base, + is_checked, + ), + Status::Hovered { is_checked } => styled( + palette.danger.base.text, + palette.background.weak, + palette.danger.base, + is_checked, + ), + Status::Disabled { is_checked } => styled( + palette.danger.base.text, + palette.background.weak, + palette.danger.weak, + is_checked, + ), + } +} + +fn styled( + icon_color: Color, + base: palette::Pair, + accent: palette::Pair, + is_checked: bool, +) -> Appearance { + Appearance { + background: Background::Color(if is_checked { + accent.color + } else { + base.color + }), + icon_color, + border: Border { + radius: 2.0.into(), + width: 1.0, + color: accent.color, + }, + text_color: None, + } +} diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 86331e14..397cc452 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -161,7 +161,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>( is_checked: bool, ) -> Checkbox<'a, Message, Theme, Renderer> where - Theme: checkbox::StyleSheet + text::StyleSheet, + Theme: checkbox::Style, Renderer: core::text::Renderer, { Checkbox::new(label, is_checked) -- cgit From 29326215ccf13e1d1e25bf3bf5ada007856bff69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 03:48:08 +0100 Subject: Simplify theming for `Container` widget --- widget/src/combo_box.rs | 4 +- widget/src/container.rs | 118 +++++++++++++++++++++++++++++++++----- widget/src/helpers.rs | 6 +- widget/src/overlay/menu.rs | 8 +-- widget/src/pane_grid.rs | 9 ++- widget/src/pane_grid/content.rs | 32 +++++++---- widget/src/pane_grid/title_bar.rs | 27 ++++++--- widget/src/pick_list.rs | 8 +-- widget/src/scrollable.rs | 12 ++-- widget/src/tooltip.rs | 26 ++++----- 10 files changed, 178 insertions(+), 72 deletions(-) (limited to 'widget') diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index e3862174..665b1da9 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -299,7 +299,7 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Display + Clone + 'static, Message: Clone, - Theme: container::StyleSheet + Theme: container::Style + text_input::StyleSheet + scrollable::StyleSheet + menu::StyleSheet, @@ -719,7 +719,7 @@ impl<'a, T, Message, Theme, Renderer> where T: Display + Clone + 'static, Message: Clone + 'a, - Theme: container::StyleSheet + Theme: container::Style + text_input::StyleSheet + scrollable::StyleSheet + menu::StyleSheet diff --git a/widget/src/container.rs b/widget/src/container.rs index e0174177..66e80820 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -8,12 +8,11 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Operation}; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Padding, + Pixels, Point, Rectangle, Shadow, Shell, Size, Vector, Widget, }; use crate::runtime::Command; - -pub use iced_style::container::{Appearance, StyleSheet}; +use crate::style::Theme; /// An element decorating some content. /// @@ -25,7 +24,6 @@ pub struct Container< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: crate::core::Renderer, { id: Option, @@ -36,19 +34,19 @@ pub struct Container< max_height: f32, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, clip: bool, content: Element<'a, Message, Theme, Renderer>, } impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: crate::core::Renderer, { /// Creates an empty [`Container`]. pub fn new(content: T) -> Self where + Theme: Style, T: Into>, { let content = content.into(); @@ -63,7 +61,7 @@ where max_height: f32::INFINITY, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, - style: Default::default(), + style: Theme::default(), clip: false, content, } @@ -130,8 +128,8 @@ where } /// Sets the style of the [`Container`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { + self.style = style; self } @@ -146,7 +144,6 @@ where impl<'a, Message, Theme, Renderer> Widget for Container<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: crate::core::Renderer, { fn tag(&self) -> tree::Tag { @@ -262,10 +259,18 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - let style = theme.appearance(&self.style); + let bounds = layout.bounds(); + + let status = if cursor.is_over(bounds) { + Status::Hovered + } else { + Status::Idle + }; - if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { - draw_background(renderer, &style, layout.bounds()); + let style = (self.style)(theme, status); + + if let Some(clipped_viewport) = bounds.intersection(viewport) { + draw_background(renderer, &style, bounds); self.content.as_widget().draw( tree, @@ -307,7 +312,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: 'a + StyleSheet, + Theme: 'a, Renderer: 'a + crate::core::Renderer, { fn from( @@ -482,3 +487,86 @@ pub fn visible_bounds(id: Id) -> Command> { bounds: None, }) } + +/// The appearance of a container. +#[derive(Debug, Clone, Copy, Default)] +pub struct Appearance { + /// The text [`Color`] of the container. + pub text_color: Option, + /// The [`Background`] of the container. + pub background: Option, + /// The [`Border`] of the container. + pub border: Border, + /// The [`Shadow`] of the container. + pub shadow: Shadow, +} + +impl Appearance { + /// Derives a new [`Appearance`] with a border of the given [`Color`] and + /// `width`. + pub fn with_border( + self, + color: impl Into, + width: impl Into, + ) -> Self { + Self { + border: Border { + color: color.into(), + width: width.into().0, + ..Border::default() + }, + ..self + } + } + + /// Derives a new [`Appearance`] with the given [`Background`]. + pub fn with_background(self, background: impl Into) -> Self { + Self { + background: Some(background.into()), + ..self + } + } +} + +/// The possible status of a [`Container`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Container`] is idle. + Idle, + /// The [`Container`] is being hovered. + Hovered, +} + +/// The style of a [`Container`] for a theme. +pub trait Style { + /// The default style of a [`Container`]. + fn default() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn default() -> fn(&Self, Status) -> Appearance { + transparent + } +} + +impl Style for Appearance { + fn default() -> fn(&Self, Status) -> Appearance { + |appearance, _status| *appearance + } +} + +/// A transparent [`Container`]. +pub fn transparent(_theme: &Theme, _status: Status) -> Appearance { + ::default() +} + +/// A rounded [`Container`] with a background. +pub fn box_(theme: &Theme, _status: Status) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + background: Some(palette.background.weak.color.into()), + border: Border::with_radius(2), + ..::default() + } +} diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 397cc452..a14a307e 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -58,7 +58,7 @@ pub fn container<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Container<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, + Theme: container::Style, Renderer: core::Renderer, { Container::new(content) @@ -134,7 +134,7 @@ pub fn tooltip<'a, Message, Theme, Renderer>( position: tooltip::Position, ) -> crate::Tooltip<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet + text::StyleSheet, + Theme: container::Style + text::StyleSheet, Renderer: core::text::Renderer, { Tooltip::new(content, tooltip, position) @@ -278,7 +278,7 @@ where Theme: pick_list::StyleSheet + scrollable::StyleSheet + overlay::menu::StyleSheet - + container::StyleSheet, + + container::Style, ::Style: From<::Style>, { diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 8a4d6a98..a666b98e 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -47,7 +47,7 @@ impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer> where T: ToString + Clone, Message: 'a, - Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet + 'a, + Theme: StyleSheet + container::Style + scrollable::StyleSheet + 'a, Renderer: text::Renderer + 'a, { /// Creates a new [`Menu`] with the given [`State`], a list of options, and @@ -165,7 +165,7 @@ impl Default for State { struct Overlay<'a, Message, Theme, Renderer> where - Theme: StyleSheet + container::StyleSheet, + Theme: StyleSheet + container::Style, Renderer: crate::core::Renderer, { position: Point, @@ -179,7 +179,7 @@ where impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet + 'a, + Theme: StyleSheet + container::Style + scrollable::StyleSheet + 'a, Renderer: text::Renderer + 'a, { pub fn new( @@ -235,7 +235,7 @@ impl<'a, Message, Theme, Renderer> crate::core::Overlay for Overlay<'a, Message, Theme, Renderer> where - Theme: StyleSheet + container::StyleSheet, + Theme: StyleSheet + container::Style, Renderer: text::Renderer, { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 478a7024..a18d0fbf 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -32,7 +32,6 @@ pub use title_bar::TitleBar; pub use crate::style::pane_grid::{Appearance, Line, StyleSheet}; -use crate::container; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -105,7 +104,7 @@ pub struct PaneGrid< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet + container::StyleSheet, + Theme: StyleSheet, Renderer: crate::core::Renderer, { contents: Contents<'a, Content<'a, Message, Theme, Renderer>>, @@ -120,7 +119,7 @@ pub struct PaneGrid< impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> where - Theme: StyleSheet + container::StyleSheet, + Theme: StyleSheet, Renderer: crate::core::Renderer, { /// Creates a [`PaneGrid`] with the given [`State`] and view function. @@ -240,7 +239,7 @@ impl<'a, Message, Theme, Renderer> Widget for PaneGrid<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, - Theme: StyleSheet + container::StyleSheet, + Theme: StyleSheet, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -470,7 +469,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + container::StyleSheet + 'a, + Theme: StyleSheet + 'a, Renderer: crate::core::Renderer + 'a, { fn from( diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index dfe0fdcf..78a4f347 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -20,25 +20,26 @@ pub struct Content< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { title_bar: Option>, body: Element<'a, Message, Theme, Renderer>, - style: Theme::Style, + style: fn(&Theme, container::Status) -> container::Appearance, } impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { /// Creates a new [`Content`] with the provided body. - pub fn new(body: impl Into>) -> Self { + pub fn new(body: impl Into>) -> Self + where + Theme: container::Style, + { Self { title_bar: None, body: body.into(), - style: Default::default(), + style: Theme::default(), } } @@ -52,15 +53,17 @@ where } /// Sets the style of the [`Content`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style( + mut self, + style: fn(&Theme, container::Status) -> container::Appearance, + ) -> Self { + self.style = style; self } } impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { pub(super) fn state(&self) -> Tree { @@ -104,7 +107,15 @@ where let bounds = layout.bounds(); { - let style = theme.appearance(&self.style); + let style = { + let status = if cursor.is_over(bounds) { + container::Status::Hovered + } else { + container::Status::Idle + }; + + (self.style)(theme, status) + }; container::draw_background(renderer, &style, bounds); } @@ -370,7 +381,6 @@ where impl<'a, Message, Theme, Renderer> Draggable for &Content<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { fn can_be_dragged_at( @@ -393,7 +403,7 @@ impl<'a, T, Message, Theme, Renderer> From for Content<'a, Message, Theme, Renderer> where T: Into>, - Theme: container::StyleSheet, + Theme: container::Style, Renderer: crate::core::Renderer, { fn from(element: T) -> Self { diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 5b57509b..6d786f96 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -19,24 +19,23 @@ pub struct TitleBar< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { content: Element<'a, Message, Theme, Renderer>, controls: Option>, padding: Padding, always_show_controls: bool, - style: Theme::Style, + style: fn(&Theme, container::Status) -> container::Appearance, } impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { /// Creates a new [`TitleBar`] with the given content. pub fn new(content: E) -> Self where + Theme: container::Style, E: Into>, { Self { @@ -44,7 +43,7 @@ where controls: None, padding: Padding::ZERO, always_show_controls: false, - style: Default::default(), + style: Theme::default(), } } @@ -64,8 +63,11 @@ where } /// Sets the style of the [`TitleBar`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style( + mut self, + style: fn(&Theme, container::Status) -> container::Appearance, + ) -> Self { + self.style = style; self } @@ -85,7 +87,6 @@ where impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, Renderer: crate::core::Renderer, { pub(super) fn state(&self) -> Tree { @@ -128,7 +129,17 @@ where show_controls: bool, ) { let bounds = layout.bounds(); - let style = theme.appearance(&self.style); + + let style = { + let status = if cursor.is_over(bounds) { + container::Status::Hovered + } else { + container::Status::Idle + }; + + (self.style)(theme, status) + }; + let inherited_style = renderer::Style { text_color: style.text_color.unwrap_or(inherited_style.text_color), }; diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 1f20e2bc..4d6ca695 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -64,7 +64,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet, + + container::Style, ::Style: From<::Style>, Renderer: text::Renderer, { @@ -179,7 +179,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet, + + container::Style, ::Style: From<::Style>, Renderer: text::Renderer + 'a, { @@ -320,7 +320,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet + + container::Style + 'a, ::Style: From<::Style>, Renderer: text::Renderer + 'a, @@ -630,7 +630,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet + + container::Style + 'a, ::Style: From<::Style>, Renderer: text::Renderer + 'a, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index f736d92e..12e23def 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1,5 +1,5 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::container; +// use crate::container; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::layout; @@ -917,11 +917,11 @@ pub fn draw( } }; - container::draw_background( - renderer, - &appearance.container, - layout.bounds(), - ); + // container::draw_background( + // renderer, + // &appearance.container, + // layout.bounds(), + // ); // Draw inner content if scrollbars.active() { diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 51969aec..11df391e 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -20,7 +20,6 @@ pub struct Tooltip< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: container::StyleSheet, Renderer: text::Renderer, { content: Element<'a, Message, Theme, Renderer>, @@ -29,12 +28,11 @@ pub struct Tooltip< gap: f32, padding: f32, snap_within_viewport: bool, - style: ::Style, + style: fn(&Theme, container::Status) -> container::Appearance, } impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet, Renderer: text::Renderer, { /// The default padding of a [`Tooltip`] drawn by this renderer. @@ -47,7 +45,10 @@ where content: impl Into>, tooltip: impl Into>, position: Position, - ) -> Self { + ) -> Self + where + Theme: container::Style, + { Tooltip { content: content.into(), tooltip: tooltip.into(), @@ -55,7 +56,7 @@ where gap: 0.0, padding: Self::DEFAULT_PADDING, snap_within_viewport: true, - style: Default::default(), + style: Theme::default(), } } @@ -80,9 +81,9 @@ where /// Sets the style of the [`Tooltip`]. pub fn style( mut self, - style: impl Into<::Style>, + style: fn(&Theme, container::Status) -> container::Appearance, ) -> Self { - self.style = style.into(); + self.style = style; self } } @@ -90,7 +91,6 @@ where impl<'a, Message, Theme, Renderer> Widget for Tooltip<'a, Message, Theme, Renderer> where - Theme: container::StyleSheet + crate::text::StyleSheet, Renderer: text::Renderer, { fn children(&self) -> Vec { @@ -239,7 +239,7 @@ where positioning: self.position, gap: self.gap, padding: self.padding, - style: &self.style, + style: self.style, }))) } else { None @@ -262,7 +262,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: container::StyleSheet + crate::text::StyleSheet + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { fn from( @@ -298,7 +298,6 @@ enum State { struct Overlay<'a, 'b, Message, Theme, Renderer> where - Theme: container::StyleSheet + widget::text::StyleSheet, Renderer: text::Renderer, { position: Point, @@ -310,14 +309,13 @@ where positioning: Position, gap: f32, padding: f32, - style: &'b ::Style, + style: fn(&Theme, container::Status) -> container::Appearance, } impl<'a, 'b, Message, Theme, Renderer> overlay::Overlay for Overlay<'a, 'b, Message, Theme, Renderer> where - Theme: container::StyleSheet + widget::text::StyleSheet, Renderer: text::Renderer, { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { @@ -426,7 +424,7 @@ where layout: Layout<'_>, cursor_position: mouse::Cursor, ) { - let style = container::StyleSheet::appearance(theme, self.style); + let style = (self.style)(theme, container::Status::Idle); container::draw_background(renderer, &style, layout.bounds()); -- cgit From d681aaa57e3106cf0ce90b74ade040ca7bb97832 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 04:42:25 +0100 Subject: Simplify theming for `Scrollable` widget --- widget/src/combo_box.rs | 4 +- widget/src/helpers.rs | 4 +- widget/src/overlay/menu.rs | 4 +- widget/src/pick_list.rs | 8 +- widget/src/scrollable.rs | 549 +++++++++++++++++++++++++++------------------ 5 files changed, 342 insertions(+), 227 deletions(-) (limited to 'widget') diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 665b1da9..0cca8d56 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -301,7 +301,7 @@ where Message: Clone, Theme: container::Style + text_input::StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet, Renderer: text::Renderer, { @@ -721,7 +721,7 @@ where Message: Clone + 'a, Theme: container::Style + text_input::StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + 'a, Renderer: text::Renderer + 'a, diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index a14a307e..3274b8d2 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -104,7 +104,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Scrollable<'a, Message, Theme, Renderer> where - Theme: scrollable::StyleSheet, + Theme: scrollable::Tradition, Renderer: core::Renderer, { Scrollable::new(content) @@ -276,7 +276,7 @@ where Message: Clone, Renderer: core::text::Renderer, Theme: pick_list::StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + overlay::menu::StyleSheet + container::Style, ::Style: diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index a666b98e..b9b735e4 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -47,7 +47,7 @@ impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer> where T: ToString + Clone, Message: 'a, - Theme: StyleSheet + container::Style + scrollable::StyleSheet + 'a, + Theme: StyleSheet + container::Style + scrollable::Tradition + 'a, Renderer: text::Renderer + 'a, { /// Creates a new [`Menu`] with the given [`State`], a list of options, and @@ -179,7 +179,7 @@ where impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + container::Style + scrollable::StyleSheet + 'a, + Theme: StyleSheet + container::Style + scrollable::Tradition + 'a, Renderer: text::Renderer + 'a, { pub fn new( diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 4d6ca695..b75baa74 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -62,7 +62,7 @@ where V: Borrow + 'a, Message: Clone, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style, ::Style: From<::Style>, @@ -177,7 +177,7 @@ where V: Borrow, Message: Clone + 'a, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style, ::Style: From<::Style>, @@ -318,7 +318,7 @@ where V: Borrow + 'a, Message: Clone + 'a, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style + 'a, @@ -628,7 +628,7 @@ where T: Clone + ToString, Message: 'a, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style + 'a, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 12e23def..8231685b 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1,5 +1,6 @@ //! Navigate an endless amount of content with a scrollbar. // use crate::container; +use crate::container; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::layout; @@ -11,14 +12,12 @@ use crate::core::widget; use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, + Point, Rectangle, Shell, Size, Vector, Widget, }; use crate::runtime::Command; +use crate::style::Theme; -pub use crate::style::scrollable::{ - Appearance, Scrollbar, Scroller, StyleSheet, -}; pub use operation::scrollable::{AbsoluteOffset, RelativeOffset}; /// A widget that can vertically display an infinite amount of content with a @@ -30,7 +29,6 @@ pub struct Scrollable< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: crate::core::Renderer, { id: Option, @@ -39,18 +37,20 @@ pub struct Scrollable< direction: Direction, content: Element<'a, Message, Theme, Renderer>, on_scroll: Option Message + 'a>>, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: crate::core::Renderer, { /// Creates a new vertical [`Scrollable`]. pub fn new( content: impl Into>, - ) -> Self { + ) -> Self + where + Theme: Tradition, + { Self::with_direction(content, Direction::default()) } @@ -58,7 +58,10 @@ where pub fn with_direction( content: impl Into>, direction: Direction, - ) -> Self { + ) -> Self + where + Theme: Tradition, + { let content = content.into(); debug_assert!( @@ -80,7 +83,7 @@ where direction, content, on_scroll: None, - style: Default::default(), + style: Theme::tradition(), } } @@ -111,8 +114,8 @@ where } /// Sets the style of the [`Scrollable`] . - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { + self.style = style; self } } @@ -223,7 +226,6 @@ pub enum Alignment { impl<'a, Message, Theme, Renderer> Widget for Scrollable<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: crate::core::Renderer, { fn tag(&self) -> tree::Tag { @@ -352,26 +354,181 @@ where cursor: mouse::Cursor, _viewport: &Rectangle, ) { - draw( - tree.state.downcast_ref::(), + let state = tree.state.downcast_ref::(); + + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + + let scrollbars = + Scrollbars::new(state, self.direction, bounds, content_bounds); + + let cursor_over_scrollable = cursor.position_over(bounds); + let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = + scrollbars.is_mouse_over(cursor); + + let translation = + state.translation(self.direction, bounds, content_bounds); + + let cursor = match cursor_over_scrollable { + Some(cursor_position) + if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => + { + mouse::Cursor::Available(cursor_position + translation) + } + _ => mouse::Cursor::Unavailable, + }; + + let status = if state.y_scroller_grabbed_at.is_some() + || state.x_scroller_grabbed_at.is_some() + { + Status::Dragged { + is_horizontal_scrollbar_dragged: state + .x_scroller_grabbed_at + .is_some(), + is_vertical_scrollbar_dragged: state + .y_scroller_grabbed_at + .is_some(), + } + } else if cursor_over_scrollable.is_some() { + Status::Hovered { + is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar, + is_vertical_scrollbar_hovered: mouse_over_y_scrollbar, + } + } else { + Status::Active + }; + + let appearance = (self.style)(theme, status); + + container::draw_background( renderer, - theme, - layout, - cursor, - self.direction, - &self.style, - |renderer, layout, cursor, viewport| { - self.content.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - layout, - cursor, - viewport, - ); - }, + &appearance.container, + layout.bounds(), ); + + // Draw inner content + if scrollbars.active() { + renderer.with_layer(bounds, |renderer| { + renderer.with_translation( + Vector::new(-translation.x, -translation.y), + |renderer| { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + content_layout, + cursor, + &Rectangle { + y: bounds.y + translation.y, + x: bounds.x + translation.x, + ..bounds + }, + ); + }, + ); + }); + + let draw_scrollbar = + |renderer: &mut Renderer, + style: Scrollbar, + scrollbar: &internals::Scrollbar| { + if scrollbar.bounds.width > 0.0 + && scrollbar.bounds.height > 0.0 + && (style.background.is_some() + || (style.border.color != Color::TRANSPARENT + && style.border.width > 0.0)) + { + renderer.fill_quad( + renderer::Quad { + bounds: scrollbar.bounds, + border: style.border, + ..renderer::Quad::default() + }, + style.background.unwrap_or(Background::Color( + Color::TRANSPARENT, + )), + ); + } + + if scrollbar.scroller.bounds.width > 0.0 + && scrollbar.scroller.bounds.height > 0.0 + && (style.scroller.color != Color::TRANSPARENT + || (style.scroller.border.color + != Color::TRANSPARENT + && style.scroller.border.width > 0.0)) + { + renderer.fill_quad( + renderer::Quad { + bounds: scrollbar.scroller.bounds, + border: style.scroller.border, + ..renderer::Quad::default() + }, + style.scroller.color, + ); + } + }; + + renderer.with_layer( + Rectangle { + width: bounds.width + 2.0, + height: bounds.height + 2.0, + ..bounds + }, + |renderer| { + if let Some(scrollbar) = scrollbars.y { + draw_scrollbar( + renderer, + appearance.vertical_scrollbar, + &scrollbar, + ); + } + + if let Some(scrollbar) = scrollbars.x { + draw_scrollbar( + renderer, + appearance.horizontal_scrollbar, + &scrollbar, + ); + } + + if let (Some(x), Some(y)) = (scrollbars.x, scrollbars.y) { + let background = + appearance.gap.or(appearance.container.background); + + if let Some(background) = background { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: y.bounds.x, + y: x.bounds.y, + width: y.bounds.width, + height: x.bounds.height, + }, + ..renderer::Quad::default() + }, + background, + ); + } + } + }, + ); + } else { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + content_layout, + cursor, + &Rectangle { + x: bounds.x + translation.x, + y: bounds.y + translation.y, + ..bounds + }, + ); + } } fn mouse_interaction( @@ -430,7 +587,7 @@ impl<'a, Message, Theme, Renderer> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: 'a + crate::core::Renderer, { fn from( @@ -862,190 +1019,6 @@ pub fn mouse_interaction( } } -/// Draws a [`Scrollable`]. -pub fn draw( - state: &State, - renderer: &mut Renderer, - theme: &Theme, - layout: Layout<'_>, - cursor: mouse::Cursor, - direction: Direction, - style: &Theme::Style, - draw_content: impl FnOnce(&mut Renderer, Layout<'_>, mouse::Cursor, &Rectangle), -) where - Theme: StyleSheet, - Renderer: crate::core::Renderer, -{ - let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); - let content_bounds = content_layout.bounds(); - - let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); - - let cursor_over_scrollable = cursor.position_over(bounds); - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = - scrollbars.is_mouse_over(cursor); - - let translation = state.translation(direction, bounds, content_bounds); - - let cursor = match cursor_over_scrollable { - Some(cursor_position) - if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => - { - mouse::Cursor::Available(cursor_position + translation) - } - _ => mouse::Cursor::Unavailable, - }; - - let appearance = if state.y_scroller_grabbed_at.is_some() - || state.x_scroller_grabbed_at.is_some() - { - theme.dragging(style) - } else if cursor_over_scrollable.is_some() { - theme.hovered(style, mouse_over_y_scrollbar || mouse_over_x_scrollbar) - } else { - theme.active(style) - }; - - let scrollbar_style = |is_dragging: bool, mouse_over_scrollbar: bool| { - if is_dragging { - theme.dragging(style).scrollbar - } else if cursor_over_scrollable.is_some() { - theme.hovered(style, mouse_over_scrollbar).scrollbar - } else { - theme.active(style).scrollbar - } - }; - - // container::draw_background( - // renderer, - // &appearance.container, - // layout.bounds(), - // ); - - // Draw inner content - if scrollbars.active() { - renderer.with_layer(bounds, |renderer| { - renderer.with_translation( - Vector::new(-translation.x, -translation.y), - |renderer| { - draw_content( - renderer, - content_layout, - cursor, - &Rectangle { - y: bounds.y + translation.y, - x: bounds.x + translation.x, - ..bounds - }, - ); - }, - ); - }); - - let draw_scrollbar = - |renderer: &mut Renderer, - style: Scrollbar, - scrollbar: &internals::Scrollbar| { - if scrollbar.bounds.width > 0.0 - && scrollbar.bounds.height > 0.0 - && (style.background.is_some() - || (style.border.color != Color::TRANSPARENT - && style.border.width > 0.0)) - { - renderer.fill_quad( - renderer::Quad { - bounds: scrollbar.bounds, - border: style.border, - ..renderer::Quad::default() - }, - style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } - - if scrollbar.scroller.bounds.width > 0.0 - && scrollbar.scroller.bounds.height > 0.0 - && (style.scroller.color != Color::TRANSPARENT - || (style.scroller.border.color != Color::TRANSPARENT - && style.scroller.border.width > 0.0)) - { - renderer.fill_quad( - renderer::Quad { - bounds: scrollbar.scroller.bounds, - border: style.scroller.border, - ..renderer::Quad::default() - }, - style.scroller.color, - ); - } - }; - - renderer.with_layer( - Rectangle { - width: bounds.width + 2.0, - height: bounds.height + 2.0, - ..bounds - }, - |renderer| { - if let Some(scrollbar) = scrollbars.y { - draw_scrollbar( - renderer, - scrollbar_style( - state.y_scroller_grabbed_at.is_some(), - mouse_over_y_scrollbar, - ), - &scrollbar, - ); - } - - if let Some(scrollbar) = scrollbars.x { - draw_scrollbar( - renderer, - scrollbar_style( - state.x_scroller_grabbed_at.is_some(), - mouse_over_x_scrollbar, - ), - &scrollbar, - ); - } - - if let (Some(x), Some(y)) = (scrollbars.x, scrollbars.y) { - let background = - appearance.gap.or(appearance.container.background); - - if let Some(background) = background { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: y.bounds.x, - y: x.bounds.y, - width: y.bounds.width, - height: x.bounds.height, - }, - ..renderer::Quad::default() - }, - background, - ); - } - } - }, - ); - } else { - draw_content( - renderer, - content_layout, - cursor, - &Rectangle { - x: bounds.x + translation.x, - y: bounds.y + translation.y, - ..bounds - }, - ); - } -} - fn notify_on_scroll( state: &mut State, on_scroll: &Option Message + '_>>, @@ -1625,3 +1598,145 @@ pub(super) mod internals { pub bounds: Rectangle, } } + +/// The possible status of a [`Scrollable`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Scrollable`] can be interacted with. + Active, + /// The [`Scrollable`] is being hovered. + Hovered { + /// Indicates if the horizontal scrollbar is being hovered. + is_horizontal_scrollbar_hovered: bool, + /// Indicates if the vertical scrollbar is being hovered. + is_vertical_scrollbar_hovered: bool, + }, + /// The [`Scrollable`] is being dragged. + Dragged { + /// Indicates if the horizontal scrollbar is being dragged. + is_horizontal_scrollbar_dragged: bool, + /// Indicates if the vertical scrollbar is being dragged. + is_vertical_scrollbar_dragged: bool, + }, +} + +/// The appearance of a scrolable. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`container::Appearance`] of a scrollable. + pub container: container::Appearance, + /// The vertical [`Scrollbar`] appearance. + pub vertical_scrollbar: Scrollbar, + /// The horizontal [`Scrollbar`] appearance. + pub horizontal_scrollbar: Scrollbar, + /// The [`Background`] of the gap between a horizontal and vertical scrollbar. + pub gap: Option, +} + +/// The appearance of the scrollbar of a scrollable. +#[derive(Debug, Clone, Copy)] +pub struct Scrollbar { + /// The [`Background`] of a scrollbar. + pub background: Option, + /// The [`Border`] of a scrollbar. + pub border: Border, + /// The appearance of the [`Scroller`] of a scrollbar. + pub scroller: Scroller, +} + +/// The appearance of the scroller of a scrollable. +#[derive(Debug, Clone, Copy)] +pub struct Scroller { + /// The [`Color`] of the scroller. + pub color: Color, + /// The [`Border`] of the scroller. + pub border: Border, +} + +/// The definition of the traditional style of a [`Scrollable`]. +pub trait Tradition { + /// Returns the traditional style of a [`Scrollable`]. + fn tradition() -> fn(&Self, Status) -> Appearance; +} + +impl Tradition for Theme { + fn tradition() -> fn(&Self, Status) -> Appearance { + default + } +} + +fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let scrollbar = Scrollbar { + background: Some(palette.background.weak.color.into()), + border: Border::with_radius(2), + scroller: Scroller { + color: palette.background.strong.color, + border: Border::with_radius(2), + }, + }; + + match status { + Status::Active => Appearance { + container: container::Appearance::default(), + vertical_scrollbar: scrollbar, + horizontal_scrollbar: scrollbar, + gap: None, + }, + Status::Hovered { + is_horizontal_scrollbar_hovered, + is_vertical_scrollbar_hovered, + } => { + let hovered_scrollbar = Scrollbar { + scroller: Scroller { + color: palette.primary.strong.color, + ..scrollbar.scroller + }, + ..scrollbar + }; + + Appearance { + container: container::Appearance::default(), + vertical_scrollbar: if is_vertical_scrollbar_hovered { + hovered_scrollbar + } else { + scrollbar + }, + horizontal_scrollbar: if is_horizontal_scrollbar_hovered { + hovered_scrollbar + } else { + scrollbar + }, + gap: None, + } + } + Status::Dragged { + is_horizontal_scrollbar_dragged, + is_vertical_scrollbar_dragged, + } => { + let dragged_scrollbar = Scrollbar { + scroller: Scroller { + color: palette.primary.base.color, + ..scrollbar.scroller + }, + ..scrollbar + }; + + Appearance { + container: container::Appearance::default(), + vertical_scrollbar: if is_vertical_scrollbar_dragged { + dragged_scrollbar + } else { + scrollbar + }, + horizontal_scrollbar: if is_horizontal_scrollbar_dragged { + dragged_scrollbar + } else { + scrollbar + }, + gap: None, + } + } + } +} -- cgit From 704ec9cb5cdc1d44f2df2f15de700b0af330b1d7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 15:53:59 +0100 Subject: Simplify theming for `TextInput` widget --- widget/src/combo_box.rs | 27 +- widget/src/helpers.rs | 8 +- widget/src/overlay/menu.rs | 4 +- widget/src/pick_list.rs | 14 +- widget/src/scrollable.rs | 18 +- widget/src/text_input.rs | 1669 ++++++++++++++++++++++---------------------- 6 files changed, 855 insertions(+), 885 deletions(-) (limited to 'widget') diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 0cca8d56..2ecf799d 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -32,7 +32,7 @@ pub struct ComboBox< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: text_input::StyleSheet + menu::StyleSheet, + Theme: text_input::Style + menu::StyleSheet, Renderer: text::Renderer, { state: &'a State, @@ -51,7 +51,7 @@ pub struct ComboBox< impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer> where T: std::fmt::Display + Clone, - Theme: text_input::StyleSheet + menu::StyleSheet, + Theme: text_input::Style + menu::StyleSheet, Renderer: text::Renderer, { /// Creates a new [`ComboBox`] with the given list of options, a placeholder, @@ -121,20 +121,17 @@ where // TODO: Define its own `StyleSheet` trait pub fn style(mut self, style: S) -> Self where - S: Into<::Style> - + Into<::Style> - + Clone, + S: Into<::Style>, { - self.menu_style = style.clone().into(); - self.text_input = self.text_input.style(style); + self.menu_style = style.into(); self } /// Sets the style of the [`TextInput`] of the [`ComboBox`]. - pub fn text_input_style(mut self, style: S) -> Self - where - S: Into<::Style> + Clone, - { + pub fn text_input_style( + mut self, + style: fn(&Theme, text_input::Status) -> text_input::Appearance, + ) -> Self { self.text_input = self.text_input.style(style); self } @@ -300,8 +297,8 @@ where T: Display + Clone + 'static, Message: Clone, Theme: container::Style - + text_input::StyleSheet - + scrollable::Tradition + + text_input::Style + + scrollable::Style + menu::StyleSheet, Renderer: text::Renderer, { @@ -720,8 +717,8 @@ where T: Display + Clone + 'static, Message: Clone + 'a, Theme: container::Style - + text_input::StyleSheet - + scrollable::Tradition + + text_input::Style + + scrollable::Style + menu::StyleSheet + 'a, Renderer: text::Renderer + 'a, diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 3274b8d2..2153ed50 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -104,7 +104,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Scrollable<'a, Message, Theme, Renderer> where - Theme: scrollable::Tradition, + Theme: scrollable::Style, Renderer: core::Renderer, { Scrollable::new(content) @@ -209,7 +209,7 @@ pub fn text_input<'a, Message, Theme, Renderer>( ) -> TextInput<'a, Message, Theme, Renderer> where Message: Clone, - Theme: text_input::StyleSheet, + Theme: text_input::Style, Renderer: core::text::Renderer, { TextInput::new(placeholder, value) @@ -276,7 +276,7 @@ where Message: Clone, Renderer: core::text::Renderer, Theme: pick_list::StyleSheet - + scrollable::Tradition + + scrollable::Style + overlay::menu::StyleSheet + container::Style, ::Style: @@ -296,7 +296,7 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>( ) -> ComboBox<'a, T, Message, Theme, Renderer> where T: std::fmt::Display + Clone, - Theme: text_input::StyleSheet + overlay::menu::StyleSheet, + Theme: text_input::Style + overlay::menu::StyleSheet, Renderer: core::text::Renderer, { ComboBox::new(state, placeholder, selection, on_selected) diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b9b735e4..d820592d 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -47,7 +47,7 @@ impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer> where T: ToString + Clone, Message: 'a, - Theme: StyleSheet + container::Style + scrollable::Tradition + 'a, + Theme: StyleSheet + container::Style + scrollable::Style + 'a, Renderer: text::Renderer + 'a, { /// Creates a new [`Menu`] with the given [`State`], a list of options, and @@ -179,7 +179,7 @@ where impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + container::Style + scrollable::Tradition + 'a, + Theme: StyleSheet + container::Style + scrollable::Style + 'a, Renderer: text::Renderer + 'a, { pub fn new( diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index b75baa74..aeb0f246 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -61,10 +61,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone, - Theme: StyleSheet - + scrollable::Tradition - + menu::StyleSheet - + container::Style, + Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, ::Style: From<::Style>, Renderer: text::Renderer, { @@ -176,10 +173,7 @@ where L: Borrow<[T]>, V: Borrow, Message: Clone + 'a, - Theme: StyleSheet - + scrollable::Tradition - + menu::StyleSheet - + container::Style, + Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, ::Style: From<::Style>, Renderer: text::Renderer + 'a, { @@ -318,7 +312,7 @@ where V: Borrow + 'a, Message: Clone + 'a, Theme: StyleSheet - + scrollable::Tradition + + scrollable::Style + menu::StyleSheet + container::Style + 'a, @@ -628,7 +622,7 @@ where T: Clone + ToString, Message: 'a, Theme: StyleSheet - + scrollable::Tradition + + scrollable::Style + menu::StyleSheet + container::Style + 'a, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 8231685b..864fbec8 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -49,7 +49,7 @@ where content: impl Into>, ) -> Self where - Theme: Tradition, + Theme: Style, { Self::with_direction(content, Direction::default()) } @@ -60,7 +60,7 @@ where direction: Direction, ) -> Self where - Theme: Tradition, + Theme: Style, { let content = content.into(); @@ -83,7 +83,7 @@ where direction, content, on_scroll: None, - style: Theme::tradition(), + style: Theme::style(), } } @@ -1653,14 +1653,14 @@ pub struct Scroller { pub border: Border, } -/// The definition of the traditional style of a [`Scrollable`]. -pub trait Tradition { - /// Returns the traditional style of a [`Scrollable`]. - fn tradition() -> fn(&Self, Status) -> Appearance; +/// The definition of the default style of a [`Scrollable`]. +pub trait Style { + /// Returns the default style of a [`Scrollable`]. + fn style() -> fn(&Self, Status) -> Appearance; } -impl Tradition for Theme { - fn tradition() -> fn(&Self, Status) -> Appearance { +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { default } } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 92c4892c..11b0a5d5 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -27,12 +27,11 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point, + Rectangle, Shell, Size, Vector, Widget, }; use crate::runtime::Command; - -pub use iced_style::text_input::{Appearance, StyleSheet}; +use crate::style::Theme; /// A field that can be filled with text. /// @@ -63,7 +62,6 @@ pub struct TextInput< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: text::Renderer, { id: Option, @@ -79,7 +77,7 @@ pub struct TextInput< on_paste: Option Message + 'a>>, on_submit: Option, icon: Option>, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } /// The default [`Padding`] of a [`TextInput`]. @@ -88,7 +86,6 @@ pub const DEFAULT_PADDING: Padding = Padding::new(5.0); impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer> where Message: Clone, - Theme: StyleSheet, Renderer: text::Renderer, { /// Creates a new [`TextInput`]. @@ -96,7 +93,10 @@ where /// It expects: /// - a placeholder, /// - the current value - pub fn new(placeholder: &str, value: &str) -> Self { + pub fn new(placeholder: &str, value: &str) -> Self + where + Theme: Style, + { TextInput { id: None, placeholder: String::from(placeholder), @@ -111,7 +111,7 @@ where on_paste: None, on_submit: None, icon: None, - style: Default::default(), + style: Theme::style(), } } @@ -198,8 +198,8 @@ where } /// Sets the style of the [`TextInput`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { + self.style = style; self } @@ -213,20 +213,90 @@ where limits: &layout::Limits, value: Option<&Value>, ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.padding, - self.size, - self.font, - self.line_height, - self.icon.as_ref(), - tree.state.downcast_mut::>(), - value.unwrap_or(&self.value), - &self.placeholder, - self.is_secure, - ) + let state = tree.state.downcast_mut::>(); + let value = value.unwrap_or(&self.value); + + let font = self.font.unwrap_or_else(|| renderer.default_font()); + let text_size = self.size.unwrap_or_else(|| renderer.default_size()); + let padding = self.padding.fit(Size::ZERO, limits.max()); + let height = self.line_height.to_absolute(text_size); + + let limits = limits.width(self.width).shrink(padding); + let text_bounds = limits.resolve(self.width, height, Size::ZERO); + + let placeholder_text = Text { + font, + line_height: self.line_height, + content: &self.placeholder, + bounds: Size::new(f32::INFINITY, text_bounds.height), + size: text_size, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, + }; + + state.placeholder.update(placeholder_text); + + let secure_value = self.is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + state.value.update(Text { + content: &value.to_string(), + ..placeholder_text + }); + + if let Some(icon) = &self.icon { + let icon_text = Text { + line_height: self.line_height, + content: &icon.code_point.to_string(), + font: icon.font, + size: icon.size.unwrap_or_else(|| renderer.default_size()), + bounds: Size::new(f32::INFINITY, text_bounds.height), + horizontal_alignment: alignment::Horizontal::Center, + vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, + }; + + state.icon.update(icon_text); + + let icon_width = state.icon.min_width(); + + let (text_position, icon_position) = match icon.side { + Side::Left => ( + Point::new( + padding.left + icon_width + icon.spacing, + padding.top, + ), + Point::new(padding.left, padding.top), + ), + Side::Right => ( + Point::new(padding.left, padding.top), + Point::new( + padding.left + text_bounds.width - icon_width, + padding.top, + ), + ), + }; + + let text_node = layout::Node::new( + text_bounds - Size::new(icon_width + icon.spacing, 0.0), + ) + .move_to(text_position); + + let icon_node = + layout::Node::new(Size::new(icon_width, text_bounds.height)) + .move_to(icon_position); + + layout::Node::with_children( + text_bounds.expand(padding), + vec![text_node, icon_node], + ) + } else { + let text = layout::Node::new(text_bounds) + .move_to(Point::new(padding.left, padding.top)); + + layout::Node::with_children(text_bounds.expand(padding), vec![text]) + } } /// Draws the [`TextInput`] with the given [`Renderer`], overriding its @@ -243,19 +313,173 @@ where value: Option<&Value>, viewport: &Rectangle, ) { - draw( - renderer, - theme, - layout, - cursor, - tree.state.downcast_ref::>(), - value.unwrap_or(&self.value), - self.on_input.is_none(), - self.is_secure, - self.icon.as_ref(), - &self.style, - viewport, + let state = tree.state.downcast_ref::>(); + let value = value.unwrap_or(&self.value); + let is_disabled = self.on_input.is_none(); + + let secure_value = self.is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + let bounds = layout.bounds(); + + let mut children_layout = layout.children(); + let text_bounds = children_layout.next().unwrap().bounds(); + + let is_mouse_over = cursor.is_over(bounds); + + let status = if is_disabled { + Status::Disabled + } else if state.is_focused() { + Status::Focused + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }; + + let appearance = (self.style)(theme, status); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.border, + ..renderer::Quad::default() + }, + appearance.background, ); + + if self.icon.is_some() { + let icon_layout = children_layout.next().unwrap(); + + renderer.fill_paragraph( + &state.icon, + icon_layout.bounds().center(), + appearance.icon, + *viewport, + ); + } + + let text = value.to_string(); + + let (cursor, offset) = if let Some(focus) = state + .is_focused + .as_ref() + .filter(|focus| focus.is_window_focused) + { + match state.cursor.state(value) { + cursor::State::Index(position) => { + let (text_value_width, offset) = + measure_cursor_and_scroll_offset( + &state.value, + text_bounds, + position, + ); + + let is_cursor_visible = ((focus.now - focus.updated_at) + .as_millis() + / CURSOR_BLINK_INTERVAL_MILLIS) + % 2 + == 0; + + let cursor = if is_cursor_visible { + Some(( + renderer::Quad { + bounds: Rectangle { + x: text_bounds.x + text_value_width, + y: text_bounds.y, + width: 1.0, + height: text_bounds.height, + }, + ..renderer::Quad::default() + }, + appearance.value, + )) + } else { + None + }; + + (cursor, offset) + } + cursor::State::Selection { start, end } => { + let left = start.min(end); + let right = end.max(start); + + let (left_position, left_offset) = + measure_cursor_and_scroll_offset( + &state.value, + text_bounds, + left, + ); + + let (right_position, right_offset) = + measure_cursor_and_scroll_offset( + &state.value, + text_bounds, + right, + ); + + let width = right_position - left_position; + + ( + Some(( + renderer::Quad { + bounds: Rectangle { + x: text_bounds.x + left_position, + y: text_bounds.y, + width, + height: text_bounds.height, + }, + ..renderer::Quad::default() + }, + appearance.selection, + )), + if end == right { + right_offset + } else { + left_offset + }, + ) + } + } + } else { + (None, 0.0) + }; + + let draw = |renderer: &mut Renderer, viewport| { + if let Some((cursor, color)) = cursor { + renderer.with_translation( + Vector::new(-offset, 0.0), + |renderer| { + renderer.fill_quad(cursor, color); + }, + ); + } else { + renderer.with_translation(Vector::ZERO, |_| {}); + } + + renderer.fill_paragraph( + if text.is_empty() { + &state.placeholder + } else { + &state.value + }, + Point::new(text_bounds.x, text_bounds.center_y()) + - Vector::new(offset, 0.0), + if text.is_empty() { + appearance.placeholder + } else { + appearance.value + }, + viewport, + ); + }; + + if cursor.is_some() { + renderer + .with_layer(text_bounds, |renderer| draw(renderer, *viewport)); + } else { + draw(renderer, text_bounds); + } } } @@ -263,7 +487,6 @@ impl<'a, Message, Theme, Renderer> Widget for TextInput<'a, Message, Theme, Renderer> where Message: Clone, - Theme: StyleSheet, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { @@ -299,20 +522,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.padding, - self.size, - self.font, - self.line_height, - self.icon.as_ref(), - tree.state.downcast_mut::>(), - &self.value, - &self.placeholder, - self.is_secure, - ) + self.layout(tree, renderer, limits, None) } fn operate( @@ -339,23 +549,468 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - event, - layout, - cursor, - renderer, - clipboard, - shell, - &mut self.value, - self.size, - self.line_height, - self.font, - self.is_secure, - self.on_input.as_deref(), - self.on_paste.as_deref(), - &self.on_submit, - || tree.state.downcast_mut::>(), - ) + let update_cache = |state, value| { + replace_paragraph( + renderer, + state, + layout, + value, + self.font, + self.size, + self.line_height, + ); + }; + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let state = state::(tree); + + let click_position = if self.on_input.is_some() { + cursor.position_over(layout.bounds()) + } else { + None + }; + + state.is_focused = if click_position.is_some() { + state.is_focused.or_else(|| { + let now = Instant::now(); + + Some(Focus { + updated_at: now, + now, + is_window_focused: true, + }) + }) + } else { + None + }; + + if let Some(cursor_position) = click_position { + let text_layout = layout.children().next().unwrap(); + let target = cursor_position.x - text_layout.bounds().x; + + let click = + mouse::Click::new(cursor_position, state.last_click); + + match click.kind() { + click::Kind::Single => { + let position = if target > 0.0 { + let value = if self.is_secure { + self.value.secure() + } else { + self.value.clone() + }; + + find_cursor_position( + text_layout.bounds(), + &value, + state, + target, + ) + } else { + None + } + .unwrap_or(0); + + if state.keyboard_modifiers.shift() { + state.cursor.select_range( + state.cursor.start(&self.value), + position, + ); + } else { + state.cursor.move_to(position); + } + state.is_dragging = true; + } + click::Kind::Double => { + if self.is_secure { + state.cursor.select_all(&self.value); + } else { + let position = find_cursor_position( + text_layout.bounds(), + &self.value, + state, + target, + ) + .unwrap_or(0); + + state.cursor.select_range( + self.value.previous_start_of_word(position), + self.value.next_end_of_word(position), + ); + } + + state.is_dragging = false; + } + click::Kind::Triple => { + state.cursor.select_all(&self.value); + state.is_dragging = false; + } + } + + state.last_click = Some(click); + + return event::Status::Captured; + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + state::(tree).is_dragging = false; + } + Event::Mouse(mouse::Event::CursorMoved { position }) + | Event::Touch(touch::Event::FingerMoved { position, .. }) => { + let state = state::(tree); + + if state.is_dragging { + let text_layout = layout.children().next().unwrap(); + let target = position.x - text_layout.bounds().x; + + let value = if self.is_secure { + self.value.secure() + } else { + self.value.clone() + }; + + let position = find_cursor_position( + text_layout.bounds(), + &value, + state, + target, + ) + .unwrap_or(0); + + state + .cursor + .select_range(state.cursor.start(&value), position); + + return event::Status::Captured; + } + } + Event::Keyboard(keyboard::Event::KeyPressed { + key, text, .. + }) => { + let state = state::(tree); + + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = &self.on_input else { + return event::Status::Ignored; + }; + + let modifiers = state.keyboard_modifiers; + focus.updated_at = Instant::now(); + + match key.as_ref() { + keyboard::Key::Character("c") + if state.keyboard_modifiers.command() => + { + if let Some((start, end)) = + state.cursor.selection(&self.value) + { + clipboard.write( + clipboard::Kind::Standard, + self.value.select(start, end).to_string(), + ); + } + + return event::Status::Captured; + } + keyboard::Key::Character("x") + if state.keyboard_modifiers.command() => + { + if let Some((start, end)) = + state.cursor.selection(&self.value) + { + clipboard.write( + clipboard::Kind::Standard, + self.value.select(start, end).to_string(), + ); + } + + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + editor.delete(); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + update_cache(state, &self.value); + + return event::Status::Captured; + } + keyboard::Key::Character("v") + if state.keyboard_modifiers.command() + && !state.keyboard_modifiers.alt() => + { + let content = match state.is_pasting.take() { + Some(content) => content, + None => { + let content: String = clipboard + .read(clipboard::Kind::Standard) + .unwrap_or_default() + .chars() + .filter(|c| !c.is_control()) + .collect(); + + Value::new(&content) + } + }; + + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + + editor.paste(content.clone()); + + let message = if let Some(paste) = &self.on_paste { + (paste)(editor.contents()) + } else { + (on_input)(editor.contents()) + }; + shell.publish(message); + + state.is_pasting = Some(content); + + update_cache(state, &self.value); + + return event::Status::Captured; + } + keyboard::Key::Character("a") + if state.keyboard_modifiers.command() => + { + state.cursor.select_all(&self.value); + + return event::Status::Captured; + } + _ => {} + } + + if let Some(text) = text { + state.is_pasting = None; + + if let Some(c) = + text.chars().next().filter(|c| !c.is_control()) + { + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + + editor.insert(c); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + focus.updated_at = Instant::now(); + + update_cache(state, &self.value); + + return event::Status::Captured; + } + } + + match key.as_ref() { + keyboard::Key::Named(key::Named::Enter) => { + if let Some(on_submit) = self.on_submit.clone() { + shell.publish(on_submit); + } + } + keyboard::Key::Named(key::Named::Backspace) => { + if platform::is_jump_modifier_pressed(modifiers) + && state.cursor.selection(&self.value).is_none() + { + if self.is_secure { + let cursor_pos = + state.cursor.end(&self.value); + state.cursor.select_range(0, cursor_pos); + } else { + state + .cursor + .select_left_by_words(&self.value); + } + } + + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + editor.backspace(); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + update_cache(state, &self.value); + } + keyboard::Key::Named(key::Named::Delete) => { + if platform::is_jump_modifier_pressed(modifiers) + && state.cursor.selection(&self.value).is_none() + { + if self.is_secure { + let cursor_pos = + state.cursor.end(&self.value); + state.cursor.select_range( + cursor_pos, + self.value.len(), + ); + } else { + state + .cursor + .select_right_by_words(&self.value); + } + } + + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + editor.delete(); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + update_cache(state, &self.value); + } + keyboard::Key::Named(key::Named::ArrowLeft) => { + if platform::is_jump_modifier_pressed(modifiers) + && !self.is_secure + { + if modifiers.shift() { + state + .cursor + .select_left_by_words(&self.value); + } else { + state + .cursor + .move_left_by_words(&self.value); + } + } else if modifiers.shift() { + state.cursor.select_left(&self.value); + } else { + state.cursor.move_left(&self.value); + } + } + keyboard::Key::Named(key::Named::ArrowRight) => { + if platform::is_jump_modifier_pressed(modifiers) + && !self.is_secure + { + if modifiers.shift() { + state + .cursor + .select_right_by_words(&self.value); + } else { + state + .cursor + .move_right_by_words(&self.value); + } + } else if modifiers.shift() { + state.cursor.select_right(&self.value); + } else { + state.cursor.move_right(&self.value); + } + } + keyboard::Key::Named(key::Named::Home) => { + if modifiers.shift() { + state.cursor.select_range( + state.cursor.start(&self.value), + 0, + ); + } else { + state.cursor.move_to(0); + } + } + keyboard::Key::Named(key::Named::End) => { + if modifiers.shift() { + state.cursor.select_range( + state.cursor.start(&self.value), + self.value.len(), + ); + } else { + state.cursor.move_to(self.value.len()); + } + } + keyboard::Key::Named(key::Named::Escape) => { + state.is_focused = None; + state.is_dragging = false; + state.is_pasting = None; + + state.keyboard_modifiers = + keyboard::Modifiers::default(); + } + keyboard::Key::Named( + key::Named::Tab + | key::Named::ArrowUp + | key::Named::ArrowDown, + ) => { + return event::Status::Ignored; + } + _ => {} + } + + return event::Status::Captured; + } + } + Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { + let state = state::(tree); + + if state.is_focused.is_some() { + match key.as_ref() { + keyboard::Key::Character("v") => { + state.is_pasting = None; + } + keyboard::Key::Named( + key::Named::Tab + | key::Named::ArrowUp + | key::Named::ArrowDown, + ) => { + return event::Status::Ignored; + } + _ => {} + } + + return event::Status::Captured; + } + + state.is_pasting = None; + } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + let state = state::(tree); + + state.keyboard_modifiers = modifiers; + } + Event::Window(_, window::Event::Unfocused) => { + let state = state::(tree); + + if let Some(focus) = &mut state.is_focused { + focus.is_window_focused = false; + } + } + Event::Window(_, window::Event::Focused) => { + let state = state::(tree); + + if let Some(focus) = &mut state.is_focused { + focus.is_window_focused = true; + focus.updated_at = Instant::now(); + + shell.request_redraw(window::RedrawRequest::NextFrame); + } + } + Event::Window(_, window::Event::RedrawRequested(now)) => { + let state = state::(tree); + + if let Some(focus) = &mut state.is_focused { + if focus.is_window_focused { + focus.now = now; + + let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS + - (now - focus.updated_at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; + + shell.request_redraw(window::RedrawRequest::At( + now + Duration::from_millis( + millis_until_redraw as u64, + ), + )); + } + } + } + _ => {} + } + + event::Status::Ignored } fn draw( @@ -368,19 +1023,7 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - draw( - renderer, - theme, - layout, - cursor, - tree.state.downcast_ref::>(), - &self.value, - self.on_input.is_none(), - self.is_secure, - self.icon.as_ref(), - &self.style, - viewport, - ); + self.draw(tree, renderer, theme, layout, cursor, None, viewport); } fn mouse_interaction( @@ -391,7 +1034,15 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction(layout, cursor, self.on_input.is_none()) + if cursor.is_over(layout.bounds()) { + if self.on_input.is_none() { + mouse::Interaction::NotAllowed + } else { + mouse::Interaction::Text + } + } else { + mouse::Interaction::default() + } } } @@ -399,7 +1050,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a + Clone, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { fn from( @@ -488,767 +1139,6 @@ pub fn select_all(id: Id) -> Command { Command::widget(operation::text_input::select_all(id.0)) } -/// Computes the layout of a [`TextInput`]. -pub fn layout( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - padding: Padding, - size: Option, - font: Option, - line_height: text::LineHeight, - icon: Option<&Icon>, - state: &mut State, - value: &Value, - placeholder: &str, - is_secure: bool, -) -> layout::Node -where - Renderer: text::Renderer, -{ - let font = font.unwrap_or_else(|| renderer.default_font()); - let text_size = size.unwrap_or_else(|| renderer.default_size()); - let padding = padding.fit(Size::ZERO, limits.max()); - let height = line_height.to_absolute(text_size); - - let limits = limits.width(width).shrink(padding); - let text_bounds = limits.resolve(width, height, Size::ZERO); - - let placeholder_text = Text { - font, - line_height, - content: placeholder, - bounds: Size::new(f32::INFINITY, text_bounds.height), - size: text_size, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text::Shaping::Advanced, - }; - - state.placeholder.update(placeholder_text); - - let secure_value = is_secure.then(|| value.secure()); - let value = secure_value.as_ref().unwrap_or(value); - - state.value.update(Text { - content: &value.to_string(), - ..placeholder_text - }); - - if let Some(icon) = icon { - let icon_text = Text { - line_height, - content: &icon.code_point.to_string(), - font: icon.font, - size: icon.size.unwrap_or_else(|| renderer.default_size()), - bounds: Size::new(f32::INFINITY, text_bounds.height), - horizontal_alignment: alignment::Horizontal::Center, - vertical_alignment: alignment::Vertical::Center, - shaping: text::Shaping::Advanced, - }; - - state.icon.update(icon_text); - - let icon_width = state.icon.min_width(); - - let (text_position, icon_position) = match icon.side { - Side::Left => ( - Point::new( - padding.left + icon_width + icon.spacing, - padding.top, - ), - Point::new(padding.left, padding.top), - ), - Side::Right => ( - Point::new(padding.left, padding.top), - Point::new( - padding.left + text_bounds.width - icon_width, - padding.top, - ), - ), - }; - - let text_node = layout::Node::new( - text_bounds - Size::new(icon_width + icon.spacing, 0.0), - ) - .move_to(text_position); - - let icon_node = - layout::Node::new(Size::new(icon_width, text_bounds.height)) - .move_to(icon_position); - - layout::Node::with_children( - text_bounds.expand(padding), - vec![text_node, icon_node], - ) - } else { - let text = layout::Node::new(text_bounds) - .move_to(Point::new(padding.left, padding.top)); - - layout::Node::with_children(text_bounds.expand(padding), vec![text]) - } -} - -/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`] -/// accordingly. -pub fn update<'a, Message, Renderer>( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - value: &mut Value, - size: Option, - line_height: text::LineHeight, - font: Option, - is_secure: bool, - on_input: Option<&dyn Fn(String) -> Message>, - on_paste: Option<&dyn Fn(String) -> Message>, - on_submit: &Option, - state: impl FnOnce() -> &'a mut State, -) -> event::Status -where - Message: Clone, - Renderer: text::Renderer, -{ - let update_cache = |state, value| { - replace_paragraph( - renderer, - state, - layout, - value, - font, - size, - line_height, - ); - }; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); - - let click_position = if on_input.is_some() { - cursor.position_over(layout.bounds()) - } else { - None - }; - - state.is_focused = if click_position.is_some() { - state.is_focused.or_else(|| { - let now = Instant::now(); - - Some(Focus { - updated_at: now, - now, - is_window_focused: true, - }) - }) - } else { - None - }; - - if let Some(cursor_position) = click_position { - let text_layout = layout.children().next().unwrap(); - let target = cursor_position.x - text_layout.bounds().x; - - let click = - mouse::Click::new(cursor_position, state.last_click); - - match click.kind() { - click::Kind::Single => { - let position = if target > 0.0 { - let value = if is_secure { - value.secure() - } else { - value.clone() - }; - - find_cursor_position( - text_layout.bounds(), - &value, - state, - target, - ) - } else { - None - } - .unwrap_or(0); - - if state.keyboard_modifiers.shift() { - state.cursor.select_range( - state.cursor.start(value), - position, - ); - } else { - state.cursor.move_to(position); - } - state.is_dragging = true; - } - click::Kind::Double => { - if is_secure { - state.cursor.select_all(value); - } else { - let position = find_cursor_position( - text_layout.bounds(), - value, - state, - target, - ) - .unwrap_or(0); - - state.cursor.select_range( - value.previous_start_of_word(position), - value.next_end_of_word(position), - ); - } - - state.is_dragging = false; - } - click::Kind::Triple => { - state.cursor.select_all(value); - state.is_dragging = false; - } - } - - state.last_click = Some(click); - - return event::Status::Captured; - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - state().is_dragging = false; - } - Event::Mouse(mouse::Event::CursorMoved { position }) - | Event::Touch(touch::Event::FingerMoved { position, .. }) => { - let state = state(); - - if state.is_dragging { - let text_layout = layout.children().next().unwrap(); - let target = position.x - text_layout.bounds().x; - - let value = if is_secure { - value.secure() - } else { - value.clone() - }; - - let position = find_cursor_position( - text_layout.bounds(), - &value, - state, - target, - ) - .unwrap_or(0); - - state - .cursor - .select_range(state.cursor.start(&value), position); - - return event::Status::Captured; - } - } - Event::Keyboard(keyboard::Event::KeyPressed { key, text, .. }) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - let Some(on_input) = on_input else { - return event::Status::Ignored; - }; - - let modifiers = state.keyboard_modifiers; - focus.updated_at = Instant::now(); - - match key.as_ref() { - keyboard::Key::Character("c") - if state.keyboard_modifiers.command() => - { - if let Some((start, end)) = - state.cursor.selection(value) - { - clipboard.write( - clipboard::Kind::Standard, - value.select(start, end).to_string(), - ); - } - - return event::Status::Captured; - } - keyboard::Key::Character("x") - if state.keyboard_modifiers.command() => - { - if let Some((start, end)) = - state.cursor.selection(value) - { - clipboard.write( - clipboard::Kind::Standard, - value.select(start, end).to_string(), - ); - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.delete(); - - let message = (on_input)(editor.contents()); - shell.publish(message); - - update_cache(state, value); - - return event::Status::Captured; - } - keyboard::Key::Character("v") - if state.keyboard_modifiers.command() - && !state.keyboard_modifiers.alt() => - { - let content = match state.is_pasting.take() { - Some(content) => content, - None => { - let content: String = clipboard - .read(clipboard::Kind::Standard) - .unwrap_or_default() - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - } - }; - - let mut editor = Editor::new(value, &mut state.cursor); - - editor.paste(content.clone()); - - let message = if let Some(paste) = &on_paste { - (paste)(editor.contents()) - } else { - (on_input)(editor.contents()) - }; - shell.publish(message); - - state.is_pasting = Some(content); - - update_cache(state, value); - - return event::Status::Captured; - } - keyboard::Key::Character("a") - if state.keyboard_modifiers.command() => - { - state.cursor.select_all(value); - - return event::Status::Captured; - } - _ => {} - } - - if let Some(text) = text { - state.is_pasting = None; - - if let Some(c) = - text.chars().next().filter(|c| !c.is_control()) - { - let mut editor = Editor::new(value, &mut state.cursor); - - editor.insert(c); - - let message = (on_input)(editor.contents()); - shell.publish(message); - - focus.updated_at = Instant::now(); - - update_cache(state, value); - - return event::Status::Captured; - } - } - - match key.as_ref() { - keyboard::Key::Named(key::Named::Enter) => { - if let Some(on_submit) = on_submit.clone() { - shell.publish(on_submit); - } - } - keyboard::Key::Named(key::Named::Backspace) => { - if platform::is_jump_modifier_pressed(modifiers) - && state.cursor.selection(value).is_none() - { - if is_secure { - let cursor_pos = state.cursor.end(value); - state.cursor.select_range(0, cursor_pos); - } else { - state.cursor.select_left_by_words(value); - } - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.backspace(); - - let message = (on_input)(editor.contents()); - shell.publish(message); - - update_cache(state, value); - } - keyboard::Key::Named(key::Named::Delete) => { - if platform::is_jump_modifier_pressed(modifiers) - && state.cursor.selection(value).is_none() - { - if is_secure { - let cursor_pos = state.cursor.end(value); - state - .cursor - .select_range(cursor_pos, value.len()); - } else { - state.cursor.select_right_by_words(value); - } - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.delete(); - - let message = (on_input)(editor.contents()); - shell.publish(message); - - update_cache(state, value); - } - keyboard::Key::Named(key::Named::ArrowLeft) => { - if platform::is_jump_modifier_pressed(modifiers) - && !is_secure - { - if modifiers.shift() { - state.cursor.select_left_by_words(value); - } else { - state.cursor.move_left_by_words(value); - } - } else if modifiers.shift() { - state.cursor.select_left(value); - } else { - state.cursor.move_left(value); - } - } - keyboard::Key::Named(key::Named::ArrowRight) => { - if platform::is_jump_modifier_pressed(modifiers) - && !is_secure - { - if modifiers.shift() { - state.cursor.select_right_by_words(value); - } else { - state.cursor.move_right_by_words(value); - } - } else if modifiers.shift() { - state.cursor.select_right(value); - } else { - state.cursor.move_right(value); - } - } - keyboard::Key::Named(key::Named::Home) => { - if modifiers.shift() { - state - .cursor - .select_range(state.cursor.start(value), 0); - } else { - state.cursor.move_to(0); - } - } - keyboard::Key::Named(key::Named::End) => { - if modifiers.shift() { - state.cursor.select_range( - state.cursor.start(value), - value.len(), - ); - } else { - state.cursor.move_to(value.len()); - } - } - keyboard::Key::Named(key::Named::Escape) => { - state.is_focused = None; - state.is_dragging = false; - state.is_pasting = None; - - state.keyboard_modifiers = - keyboard::Modifiers::default(); - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; - } - _ => {} - } - - return event::Status::Captured; - } - } - Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { - let state = state(); - - if state.is_focused.is_some() { - match key.as_ref() { - keyboard::Key::Character("v") => { - state.is_pasting = None; - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; - } - _ => {} - } - - return event::Status::Captured; - } - - state.is_pasting = None; - } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - - state.keyboard_modifiers = modifiers; - } - Event::Window(_, window::Event::Unfocused) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - focus.is_window_focused = false; - } - } - Event::Window(_, window::Event::Focused) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - focus.is_window_focused = true; - focus.updated_at = Instant::now(); - - shell.request_redraw(window::RedrawRequest::NextFrame); - } - } - Event::Window(_, window::Event::RedrawRequested(now)) => { - let state = state(); - - if let Some(focus) = &mut state.is_focused { - if focus.is_window_focused { - focus.now = now; - - let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() - % CURSOR_BLINK_INTERVAL_MILLIS; - - shell.request_redraw(window::RedrawRequest::At( - now + Duration::from_millis(millis_until_redraw as u64), - )); - } - } - } - _ => {} - } - - event::Status::Ignored -} - -/// Draws the [`TextInput`] with the given [`Renderer`], overriding its -/// [`Value`] if provided. -/// -/// [`Renderer`]: text::Renderer -pub fn draw( - renderer: &mut Renderer, - theme: &Theme, - layout: Layout<'_>, - cursor: mouse::Cursor, - state: &State, - value: &Value, - is_disabled: bool, - is_secure: bool, - icon: Option<&Icon>, - style: &Theme::Style, - viewport: &Rectangle, -) where - Theme: StyleSheet, - Renderer: text::Renderer, -{ - let secure_value = is_secure.then(|| value.secure()); - let value = secure_value.as_ref().unwrap_or(value); - - let bounds = layout.bounds(); - - let mut children_layout = layout.children(); - let text_bounds = children_layout.next().unwrap().bounds(); - - let is_mouse_over = cursor.is_over(bounds); - - let appearance = if is_disabled { - theme.disabled(style) - } else if state.is_focused() { - theme.focused(style) - } else if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border: appearance.border, - ..renderer::Quad::default() - }, - appearance.background, - ); - - if icon.is_some() { - let icon_layout = children_layout.next().unwrap(); - - renderer.fill_paragraph( - &state.icon, - icon_layout.bounds().center(), - appearance.icon_color, - *viewport, - ); - } - - let text = value.to_string(); - - let (cursor, offset) = if let Some(focus) = state - .is_focused - .as_ref() - .filter(|focus| focus.is_window_focused) - { - match state.cursor.state(value) { - cursor::State::Index(position) => { - let (text_value_width, offset) = - measure_cursor_and_scroll_offset( - &state.value, - text_bounds, - position, - ); - - let is_cursor_visible = ((focus.now - focus.updated_at) - .as_millis() - / CURSOR_BLINK_INTERVAL_MILLIS) - % 2 - == 0; - - let cursor = if is_cursor_visible { - Some(( - renderer::Quad { - bounds: Rectangle { - x: text_bounds.x + text_value_width, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }, - ..renderer::Quad::default() - }, - theme.value_color(style), - )) - } else { - None - }; - - (cursor, offset) - } - cursor::State::Selection { start, end } => { - let left = start.min(end); - let right = end.max(start); - - let (left_position, left_offset) = - measure_cursor_and_scroll_offset( - &state.value, - text_bounds, - left, - ); - - let (right_position, right_offset) = - measure_cursor_and_scroll_offset( - &state.value, - text_bounds, - right, - ); - - let width = right_position - left_position; - - ( - Some(( - renderer::Quad { - bounds: Rectangle { - x: text_bounds.x + left_position, - y: text_bounds.y, - width, - height: text_bounds.height, - }, - ..renderer::Quad::default() - }, - theme.selection_color(style), - )), - if end == right { - right_offset - } else { - left_offset - }, - ) - } - } - } else { - (None, 0.0) - }; - - let draw = |renderer: &mut Renderer, viewport| { - if let Some((cursor, color)) = cursor { - renderer.with_translation(Vector::new(-offset, 0.0), |renderer| { - renderer.fill_quad(cursor, color); - }); - } else { - renderer.with_translation(Vector::ZERO, |_| {}); - } - - renderer.fill_paragraph( - if text.is_empty() { - &state.placeholder - } else { - &state.value - }, - Point::new(text_bounds.x, text_bounds.center_y()) - - Vector::new(offset, 0.0), - if text.is_empty() { - theme.placeholder_color(style) - } else if is_disabled { - theme.disabled_color(style) - } else { - theme.value_color(style) - }, - viewport, - ); - }; - - if cursor.is_some() { - renderer.with_layer(text_bounds, |renderer| draw(renderer, *viewport)); - } else { - draw(renderer, text_bounds); - } -} - -/// Computes the current [`mouse::Interaction`] of the [`TextInput`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, - is_disabled: bool, -) -> mouse::Interaction { - if cursor.is_over(layout.bounds()) { - if is_disabled { - mouse::Interaction::NotAllowed - } else { - mouse::Interaction::Text - } - } else { - mouse::Interaction::default() - } -} - /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { @@ -1264,6 +1154,12 @@ pub struct State { // TODO: Add stateful horizontal scrolling offset } +fn state( + tree: &mut Tree, +) -> &mut State { + tree.state.downcast_mut::>() +} + #[derive(Debug, Clone, Copy)] struct Focus { updated_at: Instant, @@ -1479,3 +1375,86 @@ fn replace_paragraph( } const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; + +/// The possible status of a [`TextInput`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`TextInput`] can be interacted with. + Active, + /// The [`TextInput`] is being hovered. + Hovered, + /// The [`TextInput`] is focused. + Focused, + /// The [`TextInput`] cannot be interacted with. + Disabled, +} + +/// The appearance of a text input. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the text input. + pub background: Background, + /// The [`Border`] of the text input. + pub border: Border, + /// The [`Color`] of the icon of the text input. + pub icon: Color, + /// The [`Color`] of the placeholder of the text input. + pub placeholder: Color, + /// The [`Color`] of the value of the text input. + pub value: Color, + /// The [`Color`] of the selection of the text input. + pub selection: Color, +} + +/// The definiton of the default style of a [`TextInput`]. +pub trait Style { + /// Returns the default style of a [`TextInput`]. + fn style() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { + default + } +} + +/// The default style of a [`TextInput`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let active = Appearance { + background: Background::Color(palette.background.base.color), + border: Border { + radius: 2.0.into(), + width: 1.0, + color: palette.background.strong.color, + }, + icon: palette.background.weak.text, + placeholder: palette.background.strong.color, + value: palette.background.base.text, + selection: palette.primary.weak.color, + }; + + match status { + Status::Active => active, + Status::Hovered => Appearance { + border: Border { + color: palette.background.base.text, + ..active.border + }, + ..active + }, + Status::Focused => Appearance { + border: Border { + color: palette.primary.strong.color, + ..active.border + }, + ..active + }, + Status::Disabled => Appearance { + background: Background::Color(palette.background.weak.color), + value: active.placeholder, + ..active + }, + } +} -- cgit From 330a6252054b729e4d4d3f5a5d09f32e06cec282 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 21:16:22 +0100 Subject: Simplify theming for `QRCode` widget --- widget/src/helpers.rs | 2 +- widget/src/qr_code.rs | 68 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 22 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 2153ed50..8f0dcd5c 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -396,7 +396,7 @@ where #[cfg(feature = "qr_code")] pub fn qr_code(data: &crate::qr_code::Data) -> crate::QRCode<'_, Theme> where - Theme: crate::qr_code::StyleSheet, + Theme: crate::qr_code::Style, { crate::QRCode::new(data) } diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index eeb1526f..f13c9102 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -5,41 +5,37 @@ use crate::core::mouse; use crate::core::renderer::{self, Renderer as _}; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, + Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use crate::graphics::geometry::Renderer as _; +use crate::style::Theme; use crate::Renderer; use std::cell::RefCell; use thiserror::Error; -pub use crate::style::qr_code::{Appearance, StyleSheet}; - const DEFAULT_CELL_SIZE: u16 = 4; const QUIET_ZONE: usize = 2; /// A type of matrix barcode consisting of squares arranged in a grid which /// can be read by an imaging device, such as a camera. #[derive(Debug)] -pub struct QRCode<'a, Theme = crate::Theme> -where - Theme: StyleSheet, -{ +pub struct QRCode<'a, Theme = crate::Theme> { data: &'a Data, cell_size: u16, - style: Theme::Style, + style: fn(&Theme) -> Appearance, } -impl<'a, Theme> QRCode<'a, Theme> -where - Theme: StyleSheet, -{ +impl<'a, Theme> QRCode<'a, Theme> { /// Creates a new [`QRCode`] with the provided [`Data`]. - pub fn new(data: &'a Data) -> Self { + pub fn new(data: &'a Data) -> Self + where + Theme: Style, + { Self { data, cell_size: DEFAULT_CELL_SIZE, - style: Default::default(), + style: Theme::style(), } } @@ -50,15 +46,14 @@ where } /// Sets the style of the [`QRCode`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { + self.style = style; self } } -impl<'a, Message, Theme> Widget for QRCode<'a, Theme> -where - Theme: StyleSheet, +impl<'a, Message, Theme> Widget + for QRCode<'a, Theme> { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -102,7 +97,7 @@ where let bounds = layout.bounds(); let side_length = self.data.width + 2 * QUIET_ZONE; - let appearance = theme.appearance(&self.style); + let appearance = (self.style)(theme); let mut last_appearance = state.last_appearance.borrow_mut(); if Some(appearance) != *last_appearance { @@ -156,7 +151,7 @@ where impl<'a, Message, Theme> From> for Element<'a, Message, Theme, Renderer> where - Theme: StyleSheet + 'a, + Theme: 'a, { fn from(qr_code: QRCode<'a, Theme>) -> Self { Self::new(qr_code) @@ -330,3 +325,34 @@ impl From for Error { struct State { last_appearance: RefCell>, } + +/// The appearance of a QR code. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { + /// The color of the QR code data cells + pub cell: Color, + /// The color of the QR code background + pub background: Color, +} + +/// The definiton of the default style of a [`QRCode`]. +pub trait Style { + /// Returns the default style of a [`QRCode`]. + fn style() -> fn(&Self) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self) -> Appearance { + default + } +} + +/// The default style of a [`QRCode`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.palette(); + + Appearance { + cell: palette.text, + background: palette.background, + } +} -- cgit From d735209fc32238185ea0f27f1f4d1d0044b90e06 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 21:23:14 +0100 Subject: Move style types of `slider` to `iced_widget` --- widget/src/helpers.rs | 7 ++- widget/src/slider.rs | 140 +++++++++++++++++++++++++++++++++++++----- widget/src/vertical_slider.rs | 18 +++--- 3 files changed, 136 insertions(+), 29 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 8f0dcd5c..c63a9706 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -20,7 +20,8 @@ use crate::text_editor::{self, TextEditor}; use crate::text_input::{self, TextInput}; use crate::toggler::{self, Toggler}; use crate::tooltip::{self, Tooltip}; -use crate::{Column, MouseArea, Row, Space, Themer, VerticalSlider}; +use crate::vertical_slider::{self, VerticalSlider}; +use crate::{Column, MouseArea, Row, Space, Themer}; use std::borrow::Borrow; use std::ops::RangeInclusive; @@ -240,7 +241,7 @@ pub fn slider<'a, T, Message, Theme>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Theme: slider::StyleSheet, + Theme: slider::Style, { Slider::new(range, value, on_change) } @@ -256,7 +257,7 @@ pub fn vertical_slider<'a, T, Message, Theme>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Theme: slider::StyleSheet, + Theme: vertical_slider::Style, { VerticalSlider::new(range, value, on_change) } diff --git a/widget/src/slider.rs b/widget/src/slider.rs index ce02a0a6..e4dc809e 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -1,6 +1,7 @@ //! Display an interactive selector of a single value from a range of values. //! //! A [`Slider`] has some local [`State`]. +use crate::core::border; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key::{self, Key}; @@ -10,16 +11,13 @@ use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Border, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, - Shell, Size, Widget, + Border, Clipboard, Color, Element, Layout, Length, Pixels, Point, + Rectangle, Shell, Size, Widget, }; +use crate::style::Theme; use std::ops::RangeInclusive; -pub use iced_style::slider::{ - Appearance, Handle, HandleShape, Rail, Status, StyleSheet, -}; - /// An horizontal bar and a handle that selects a single value from a range of /// values. /// @@ -45,10 +43,7 @@ pub use iced_style::slider::{ /// /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) #[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Theme = crate::Theme> -where - Theme: StyleSheet, -{ +pub struct Slider<'a, T, Message, Theme = crate::Theme> { range: RangeInclusive, step: T, shift_step: Option, @@ -65,7 +60,6 @@ impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> where T: Copy + From + PartialOrd, Message: Clone, - Theme: StyleSheet, { /// The default height of a [`Slider`]. pub const DEFAULT_HEIGHT: f32 = 22.0; @@ -80,6 +74,7 @@ where /// `Message`. pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self where + Theme: Style, F: 'a + Fn(T) -> Message, { let value = if value >= *range.start() { @@ -104,7 +99,7 @@ where on_release: None, width: Length::Fill, height: Self::DEFAULT_HEIGHT, - style: Theme::default(), + style: Theme::style(), } } @@ -165,7 +160,6 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Copy + Into + num_traits::FromPrimitive, Message: Clone, - Theme: StyleSheet, Renderer: crate::core::Renderer, { fn tag(&self) -> tree::Tag { @@ -359,7 +353,7 @@ where let style = (self.style)( theme, if state.is_dragging { - Status::Dragging + Status::Dragged } else if is_mouse_over { Status::Hovered } else { @@ -468,7 +462,7 @@ impl<'a, T, Message, Theme, Renderer> From> where T: Copy + Into + num_traits::FromPrimitive + 'a, Message: Clone + 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: crate::core::Renderer + 'a, { fn from( @@ -483,3 +477,119 @@ struct State { is_dragging: bool, keyboard_modifiers: keyboard::Modifiers, } + +/// The possible status of a [`Slider`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Slider`] can be interacted with. + Active, + /// The [`Slider`] is being hovered. + Hovered, + /// The [`Slider`] is being dragged. + Dragged, +} + +/// The appearance of a slider. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The colors of the rail of the slider. + pub rail: Rail, + /// The appearance of the [`Handle`] of the slider. + pub handle: Handle, +} + +impl Appearance { + /// Changes the [`HandleShape`] of the [`Appearance`] to a circle + /// with the given radius. + pub fn with_circular_handle(mut self, radius: impl Into) -> Self { + self.handle.shape = HandleShape::Circle { + radius: radius.into().0, + }; + self + } +} + +/// The appearance of a slider rail +#[derive(Debug, Clone, Copy)] +pub struct Rail { + /// The colors of the rail of the slider. + pub colors: (Color, Color), + /// The width of the stroke of a slider rail. + pub width: f32, + /// The border radius of the corners of the rail. + pub border_radius: border::Radius, +} + +/// The appearance of the handle of a slider. +#[derive(Debug, Clone, Copy)] +pub struct Handle { + /// The shape of the handle. + pub shape: HandleShape, + /// The [`Color`] of the handle. + pub color: Color, + /// The border width of the handle. + pub border_width: f32, + /// The border [`Color`] of the handle. + pub border_color: Color, +} + +/// The shape of the handle of a slider. +#[derive(Debug, Clone, Copy)] +pub enum HandleShape { + /// A circular handle. + Circle { + /// The radius of the circle. + radius: f32, + }, + /// A rectangular shape. + Rectangle { + /// The width of the rectangle. + width: u16, + /// The border radius of the corners of the rectangle. + border_radius: border::Radius, + }, +} + +/// The definiton of the default style of a [`TextInput`]. +pub trait Style { + /// Returns the default style of a [`TextInput`]. + fn style() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { + default + } +} + +/// The default style of a [`Slider`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let handle = Handle { + shape: HandleShape::Rectangle { + width: 8, + border_radius: 4.0.into(), + }, + color: Color::WHITE, + border_color: Color::WHITE, + border_width: 1.0, + }; + + Appearance { + rail: Rail { + colors: (palette.primary.base.color, palette.secondary.base.color), + width: 4.0, + border_radius: 2.0.into(), + }, + handle: Handle { + color: match status { + Status::Active => palette.background.base.color, + Status::Hovered => palette.primary.weak.color, + Status::Dragged => palette.primary.base.color, + }, + border_color: palette.primary.base.color, + ..handle + }, + } +} diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index b6903001..b51aa2bf 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -3,8 +3,8 @@ //! A [`VerticalSlider`] has some local [`State`]. use std::ops::RangeInclusive; -pub use crate::style::slider::{ - Appearance, Handle, HandleShape, Status, StyleSheet, +pub use crate::slider::{ + default, Appearance, Handle, HandleShape, Status, Style, }; use crate::core; @@ -44,10 +44,7 @@ use crate::core::{ /// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); /// ``` #[allow(missing_debug_implementations)] -pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> -where - Theme: StyleSheet, -{ +pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> { range: RangeInclusive, step: T, shift_step: Option, @@ -64,7 +61,6 @@ impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme> where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Theme: StyleSheet, { /// The default width of a [`VerticalSlider`]. pub const DEFAULT_WIDTH: f32 = 22.0; @@ -79,6 +75,7 @@ where /// `Message`. pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self where + Theme: Style, F: 'a + Fn(T) -> Message, { let value = if value >= *range.start() { @@ -103,7 +100,7 @@ where on_release: None, width: Self::DEFAULT_WIDTH, height: Length::Fill, - style: Theme::default(), + style: Theme::style(), } } @@ -167,7 +164,6 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Copy + Into + num_traits::FromPrimitive, Message: Clone, - Theme: StyleSheet, Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { @@ -364,7 +360,7 @@ where let style = (self.style)( theme, if state.is_dragging { - Status::Dragging + Status::Dragged } else if is_mouse_over { Status::Hovered } else { @@ -474,7 +470,7 @@ impl<'a, T, Message, Theme, Renderer> where T: Copy + Into + num_traits::FromPrimitive + 'a, Message: Clone + 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: core::Renderer + 'a, { fn from( -- cgit From 7d84c9c9c3619513519ac1ef7ea1c5f6e4e2cf5d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 21:55:24 +0100 Subject: Simplify theming for `Radio` widget --- widget/src/helpers.rs | 2 +- widget/src/radio.rs | 106 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 22 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index c63a9706..355f7814 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -179,7 +179,7 @@ pub fn radio( ) -> Radio where Message: Clone, - Theme: radio::StyleSheet, + Theme: radio::Style, Renderer: core::text::Renderer, V: Copy + Eq, { diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 68e9bc7e..c4283af8 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -9,11 +9,10 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, - Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, + Rectangle, Shell, Size, Widget, }; - -pub use iced_style::radio::{Appearance, StyleSheet}; +use crate::style::Theme; /// A circular button representing a choice. /// @@ -71,7 +70,6 @@ pub use iced_style::radio::{Appearance, StyleSheet}; #[allow(missing_debug_implementations)] pub struct Radio where - Theme: StyleSheet, Renderer: text::Renderer, { is_selected: bool, @@ -84,20 +82,19 @@ where text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl Radio where Message: Clone, - Theme: StyleSheet, Renderer: text::Renderer, { /// The default size of a [`Radio`] button. - pub const DEFAULT_SIZE: f32 = 28.0; + pub const DEFAULT_SIZE: f32 = 14.0; /// The default spacing of a [`Radio`] button. - pub const DEFAULT_SPACING: f32 = 15.0; + pub const DEFAULT_SPACING: f32 = 10.0; /// Creates a new [`Radio`] button. /// @@ -114,6 +111,7 @@ where f: F, ) -> Self where + Theme: Style, V: Eq + Copy, F: FnOnce(V) -> Message, { @@ -128,7 +126,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - style: Default::default(), + style: Theme::style(), } } @@ -178,7 +176,7 @@ where } /// Sets the style of the [`Radio`] button. - pub fn style(mut self, style: impl Into) -> Self { + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { self.style = style.into(); self } @@ -188,7 +186,6 @@ impl Widget for Radio where Message: Clone, - Theme: StyleSheet + crate::text::StyleSheet, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { @@ -291,15 +288,18 @@ where viewport: &Rectangle, ) { let is_mouse_over = cursor.is_over(layout.bounds()); + let is_selected = self.is_selected; let mut children = layout.children(); - let custom_style = if is_mouse_over { - theme.hovered(&self.style, self.is_selected) + let status = if is_mouse_over { + Status::Hovered { is_selected } } else { - theme.active(&self.style, self.is_selected) + Status::Active { is_selected } }; + let appearance = (self.style)(theme, status); + { let layout = children.next().unwrap(); let bounds = layout.bounds(); @@ -312,12 +312,12 @@ where bounds, border: Border { radius: (size / 2.0).into(), - width: custom_style.border_width, - color: custom_style.border_color, + width: appearance.border_width, + color: appearance.border_color, }, ..renderer::Quad::default() }, - custom_style.background, + appearance.background, ); if self.is_selected { @@ -332,7 +332,7 @@ where border: Border::with_radius(dot_size / 2.0), ..renderer::Quad::default() }, - custom_style.dot_color, + appearance.dot_color, ); } } @@ -346,7 +346,7 @@ where label_layout, tree.state.downcast_ref(), crate::text::Appearance { - color: custom_style.text_color, + color: appearance.text_color, }, viewport, ); @@ -358,7 +358,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a + Clone, - Theme: StyleSheet + crate::text::StyleSheet + 'a, + Theme: 'a, Renderer: 'a + text::Renderer, { fn from( @@ -367,3 +367,67 @@ where Element::new(radio) } } + +/// The possible status of a [`TextInput`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Radio`] button can be interacted with. + Active { + /// Indicates whether the [`Radio`] button is currently selected. + is_selected: bool, + }, + /// The [`Radio`] button is being hovered. + Hovered { + /// Indicates whether the [`Radio`] button is currently selected. + is_selected: bool, + }, +} + +/// The appearance of a radio button. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the radio button. + pub background: Background, + /// The [`Color`] of the dot of the radio button. + pub dot_color: Color, + /// The border width of the radio button. + pub border_width: f32, + /// The border [`Color`] of the radio button. + pub border_color: Color, + /// The text [`Color`] of the radio button. + pub text_color: Option, +} + +/// The definiton of the default style of a [`Radio`] button. +pub trait Style { + /// Returns the default style of a [`Radio`] button. + fn style() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { + default + } +} + +/// The default style of a [`Radio`] button. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let active = Appearance { + background: Color::TRANSPARENT.into(), + dot_color: palette.primary.strong.color, + border_width: 1.0, + border_color: palette.primary.strong.color, + text_color: None, + }; + + match status { + Status::Active { .. } => active, + Status::Hovered { .. } => Appearance { + dot_color: palette.primary.strong.color, + background: palette.primary.weak.color.into(), + ..active + }, + } +} -- cgit From 87d16a090b14fa206bb87041a32d66348bc294e4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 22:03:10 +0100 Subject: Reduce default size of `checkbox` to `15.0` --- widget/src/checkbox.rs | 8 ++++---- widget/src/radio.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'widget') diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 54513b7d..91838291 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -62,7 +62,7 @@ where Theme: Style, { /// The default size of a [`Checkbox`]. - const DEFAULT_SIZE: f32 = 20.0; + const DEFAULT_SIZE: f32 = 15.0; /// The default spacing of a [`Checkbox`]. const DEFAULT_SPACING: f32 = 10.0; @@ -91,7 +91,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Theme::default(), + style: Theme::style(), } } @@ -426,11 +426,11 @@ pub struct Appearance { /// A set of rules that dictate the style of a checkbox. pub trait Style { /// The supported style of the [`StyleSheet`]. - fn default() -> fn(&Self, Status) -> Appearance; + fn style() -> fn(&Self, Status) -> Appearance; } impl Style for Theme { - fn default() -> fn(&Self, Status) -> Appearance { + fn style() -> fn(&Self, Status) -> Appearance { primary } } diff --git a/widget/src/radio.rs b/widget/src/radio.rs index c4283af8..90a10a0b 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -91,7 +91,7 @@ where Renderer: text::Renderer, { /// The default size of a [`Radio`] button. - pub const DEFAULT_SIZE: f32 = 14.0; + pub const DEFAULT_SIZE: f32 = 15.0; /// The default spacing of a [`Radio`] button. pub const DEFAULT_SPACING: f32 = 10.0; -- cgit From 5824ceb1fe8420b9337eee9a0e6db6cf7cb7f269 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 22:13:36 +0100 Subject: Simplify theming for `ProgressBar` widget --- widget/src/helpers.rs | 2 +- widget/src/progress_bar.rs | 108 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 24 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 355f7814..01645bc9 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -351,7 +351,7 @@ pub fn progress_bar( value: f32, ) -> ProgressBar where - Theme: progress_bar::StyleSheet, + Theme: progress_bar::Style, { ProgressBar::new(range, value) } diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 694fdd28..40ee5e02 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -3,12 +3,13 @@ use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::Tree; -use crate::core::{Border, Element, Layout, Length, Rectangle, Size, Widget}; +use crate::core::{ + Background, Border, Element, Layout, Length, Rectangle, Size, Widget, +}; +use crate::style::Theme; use std::ops::RangeInclusive; -pub use iced_style::progress_bar::{Appearance, StyleSheet}; - /// A bar that displays progress. /// /// # Example @@ -22,21 +23,15 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet}; /// /// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png) #[allow(missing_debug_implementations)] -pub struct ProgressBar -where - Theme: StyleSheet, -{ +pub struct ProgressBar { range: RangeInclusive, value: f32, width: Length, height: Option, - style: Theme::Style, + style: fn(&Theme) -> Appearance, } -impl ProgressBar -where - Theme: StyleSheet, -{ +impl ProgressBar { /// The default height of a [`ProgressBar`]. pub const DEFAULT_HEIGHT: f32 = 30.0; @@ -45,13 +40,16 @@ where /// It expects: /// * an inclusive range of possible values /// * the current value of the [`ProgressBar`] - pub fn new(range: RangeInclusive, value: f32) -> Self { + pub fn new(range: RangeInclusive, value: f32) -> Self + where + Theme: Style, + { ProgressBar { value: value.clamp(*range.start(), *range.end()), range, width: Length::Fill, height: None, - style: Default::default(), + style: Theme::style(), } } @@ -68,8 +66,8 @@ where } /// Sets the style of the [`ProgressBar`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { + self.style = style; self } } @@ -78,7 +76,6 @@ impl Widget for ProgressBar where Renderer: crate::core::Renderer, - Theme: StyleSheet, { fn size(&self) -> Size { Size { @@ -120,15 +117,15 @@ where / (range_end - range_start) }; - let style = theme.appearance(&self.style); + let appearance = (self.style)(theme); renderer.fill_quad( renderer::Quad { bounds: Rectangle { ..bounds }, - border: Border::with_radius(style.border_radius), + border: appearance.border, ..renderer::Quad::default() }, - style.background, + appearance.background, ); if active_progress_width > 0.0 { @@ -138,10 +135,10 @@ where width: active_progress_width, ..bounds }, - border: Border::with_radius(style.border_radius), + border: Border::with_radius(appearance.border.radius), ..renderer::Quad::default() }, - style.bar, + appearance.bar, ); } } @@ -151,7 +148,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: 'a + crate::core::Renderer, { fn from( @@ -160,3 +157,68 @@ where Element::new(progress_bar) } } + +/// The appearance of a progress bar. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the progress bar. + pub background: Background, + /// The [`Background`] of the bar of the progress bar. + pub bar: Background, + /// The [`Border`] of the progress bar. + pub border: Border, +} + +/// The definiton of the default style of a [`ProgressBar`]. +pub trait Style { + /// Returns the default style of a [`ProgressBar`]. + fn style() -> fn(&Self) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self) -> Appearance { + primary + } +} + +/// The primary style of a [`ProgressBar`]. +pub fn primary(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + styled(palette.background.strong.color, palette.primary.base.color) +} + +/// The secondary style of a [`ProgressBar`]. +pub fn secondary(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + styled( + palette.background.strong.color, + palette.secondary.base.color, + ) +} + +/// The success style of a [`ProgressBar`]. +pub fn success(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + styled(palette.background.strong.color, palette.success.base.color) +} + +/// The danger style of a [`ProgressBar`]. +pub fn danger(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + styled(palette.background.strong.color, palette.danger.base.color) +} + +fn styled( + background: impl Into, + bar: impl Into, +) -> Appearance { + Appearance { + background: background.into(), + bar: bar.into(), + border: Border::with_radius(2), + } +} -- cgit From 420f49bef5f6938868a84086e729deaa1df9107f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 22:31:01 +0100 Subject: Improve default styling of `Slider` widget --- widget/src/progress_bar.rs | 5 ++++- widget/src/slider.rs | 31 ++++++++++++------------------- 2 files changed, 16 insertions(+), 20 deletions(-) (limited to 'widget') diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 40ee5e02..889c5558 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -185,7 +185,10 @@ impl Style for Theme { pub fn primary(theme: &Theme) -> Appearance { let palette = theme.extended_palette(); - styled(palette.background.strong.color, palette.primary.base.color) + styled( + palette.background.strong.color, + palette.primary.strong.color, + ) } /// The secondary style of a [`ProgressBar`]. diff --git a/widget/src/slider.rs b/widget/src/slider.rs index e4dc809e..a40f8792 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -62,7 +62,7 @@ where Message: Clone, { /// The default height of a [`Slider`]. - pub const DEFAULT_HEIGHT: f32 = 22.0; + pub const DEFAULT_HEIGHT: f32 = 15.0; /// Creates a new [`Slider`]. /// @@ -566,30 +566,23 @@ impl Style for Theme { pub fn default(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); - let handle = Handle { - shape: HandleShape::Rectangle { - width: 8, - border_radius: 4.0.into(), - }, - color: Color::WHITE, - border_color: Color::WHITE, - border_width: 1.0, + let color = match status { + Status::Active => palette.primary.strong.color, + Status::Hovered => palette.primary.base.color, + Status::Dragged => palette.primary.strong.color, }; Appearance { rail: Rail { - colors: (palette.primary.base.color, palette.secondary.base.color), - width: 4.0, - border_radius: 2.0.into(), + colors: (color, palette.secondary.base.color), + width: 6.0, + border_radius: 3.0.into(), }, handle: Handle { - color: match status { - Status::Active => palette.background.base.color, - Status::Hovered => palette.primary.weak.color, - Status::Dragged => palette.primary.base.color, - }, - border_color: palette.primary.base.color, - ..handle + shape: HandleShape::Circle { radius: 7.0 }, + color, + border_color: Color::TRANSPARENT, + border_width: 0.0, }, } } -- cgit From a43afc791e8f78b03d802f980a10f32dc932f0b2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 22:38:27 +0100 Subject: Simplify theming for `Rule` widget --- widget/src/helpers.rs | 4 +- widget/src/rule.rs | 159 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 132 insertions(+), 31 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 01645bc9..33c9f300 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -324,7 +324,7 @@ pub fn vertical_space() -> Space { /// [`Rule`]: crate::Rule pub fn horizontal_rule(height: impl Into) -> Rule where - Theme: rule::StyleSheet, + Theme: rule::Style, { Rule::horizontal(height) } @@ -334,7 +334,7 @@ where /// [`Rule`]: crate::Rule pub fn vertical_rule(width: impl Into) -> Rule where - Theme: rule::StyleSheet, + Theme: rule::Style, { Rule::vertical(width) } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index bca34541..1a1ba106 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -1,53 +1,53 @@ //! Display a horizontal or vertical rule for dividing content. +use crate::core::border::{self, Border}; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::Tree; use crate::core::{ - Border, Element, Layout, Length, Pixels, Rectangle, Size, Widget, + Color, Element, Layout, Length, Pixels, Rectangle, Size, Widget, }; - -pub use crate::style::rule::{Appearance, FillMode, StyleSheet}; +use crate::style::Theme; /// Display a horizontal or vertical rule for dividing content. #[allow(missing_debug_implementations)] -pub struct Rule -where - Theme: StyleSheet, -{ +pub struct Rule { width: Length, height: Length, is_horizontal: bool, - style: Theme::Style, + style: fn(&Theme) -> Appearance, } -impl Rule -where - Theme: StyleSheet, -{ +impl Rule { /// Creates a horizontal [`Rule`] with the given height. - pub fn horizontal(height: impl Into) -> Self { + pub fn horizontal(height: impl Into) -> Self + where + Theme: Style, + { Rule { width: Length::Fill, height: Length::Fixed(height.into().0), is_horizontal: true, - style: Default::default(), + style: Theme::style(), } } /// Creates a vertical [`Rule`] with the given width. - pub fn vertical(width: impl Into) -> Self { + pub fn vertical(width: impl Into) -> Self + where + Theme: Style, + { Rule { width: Length::Fixed(width.into().0), height: Length::Fill, is_horizontal: false, - style: Default::default(), + style: Theme::style(), } } /// Sets the style of the [`Rule`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { + self.style = style; self } } @@ -55,7 +55,6 @@ where impl Widget for Rule where Renderer: crate::core::Renderer, - Theme: StyleSheet, { fn size(&self) -> Size { Size { @@ -84,34 +83,35 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let style = theme.appearance(&self.style); + let appearance = (self.style)(theme); let bounds = if self.is_horizontal { let line_y = (bounds.y + (bounds.height / 2.0) - - (style.width as f32 / 2.0)) + - (appearance.width as f32 / 2.0)) .round(); - let (offset, line_width) = style.fill_mode.fill(bounds.width); + let (offset, line_width) = appearance.fill_mode.fill(bounds.width); let line_x = bounds.x + offset; Rectangle { x: line_x, y: line_y, width: line_width, - height: style.width as f32, + height: appearance.width as f32, } } else { let line_x = (bounds.x + (bounds.width / 2.0) - - (style.width as f32 / 2.0)) + - (appearance.width as f32 / 2.0)) .round(); - let (offset, line_height) = style.fill_mode.fill(bounds.height); + let (offset, line_height) = + appearance.fill_mode.fill(bounds.height); let line_y = bounds.y + offset; Rectangle { x: line_x, y: line_y, - width: style.width as f32, + width: appearance.width as f32, height: line_height, } }; @@ -119,10 +119,10 @@ where renderer.fill_quad( renderer::Quad { bounds, - border: Border::with_radius(style.radius), + border: Border::with_radius(appearance.radius), ..renderer::Quad::default() }, - style.color, + appearance.color, ); } } @@ -131,10 +131,111 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: 'a + crate::core::Renderer, { fn from(rule: Rule) -> Element<'a, Message, Theme, Renderer> { Element::new(rule) } } + +/// The appearance of a rule. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The color of the rule. + pub color: Color, + /// The width (thickness) of the rule line. + pub width: u16, + /// The radius of the line corners. + pub radius: border::Radius, + /// The [`FillMode`] of the rule. + pub fill_mode: FillMode, +} + +/// The fill mode of a rule. +#[derive(Debug, Clone, Copy)] +pub enum FillMode { + /// Fill the whole length of the container. + Full, + /// Fill a percent of the length of the container. The rule + /// will be centered in that container. + /// + /// The range is `[0.0, 100.0]`. + Percent(f32), + /// Uniform offset from each end, length units. + Padded(u16), + /// Different offset on each end of the rule, length units. + /// First = top or left. + AsymmetricPadding(u16, u16), +} + +impl FillMode { + /// Return the starting offset and length of the rule. + /// + /// * `space` - The space to fill. + /// + /// # Returns + /// + /// * (`starting_offset`, `length`) + pub fn fill(&self, space: f32) -> (f32, f32) { + match *self { + FillMode::Full => (0.0, space), + FillMode::Percent(percent) => { + if percent >= 100.0 { + (0.0, space) + } else { + let percent_width = (space * percent / 100.0).round(); + + (((space - percent_width) / 2.0).round(), percent_width) + } + } + FillMode::Padded(padding) => { + if padding == 0 { + (0.0, space) + } else { + let padding = padding as f32; + let mut line_width = space - (padding * 2.0); + if line_width < 0.0 { + line_width = 0.0; + } + + (padding, line_width) + } + } + FillMode::AsymmetricPadding(first_pad, second_pad) => { + let first_pad = first_pad as f32; + let second_pad = second_pad as f32; + let mut line_width = space - first_pad - second_pad; + if line_width < 0.0 { + line_width = 0.0; + } + + (first_pad, line_width) + } + } + } +} + +/// The definiton of the default style of a [`Rule`]. +pub trait Style { + /// Returns the default style of a [`Rule`]. + fn style() -> fn(&Self) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self) -> Appearance { + default + } +} + +/// The default styling of a [`Rule`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + color: palette.background.strong.color, + width: 1, + radius: 0.0.into(), + fill_mode: FillMode::Full, + } +} -- cgit From 40af65c3aa4a96281db8f642cfa1641479314d27 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 11:24:51 +0100 Subject: Simplify theming for `Toggler` widget --- widget/src/helpers.rs | 2 +- widget/src/toggler.rs | 131 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 21 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 33c9f300..8cb03691 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -196,7 +196,7 @@ pub fn toggler<'a, Message, Theme, Renderer>( ) -> Toggler<'a, Message, Theme, Renderer> where Renderer: core::text::Renderer, - Theme: toggler::StyleSheet, + Theme: toggler::Style, { Toggler::new(label, is_checked, f) } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 4e3925ba..1f19212d 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -9,11 +9,10 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Border, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, - Shell, Size, Widget, + Border, Clipboard, Color, Element, Event, Layout, Length, Pixels, + Rectangle, Shell, Size, Widget, }; - -pub use crate::style::toggler::{Appearance, StyleSheet}; +use crate::style::Theme; /// A toggler widget. /// @@ -38,7 +37,6 @@ pub struct Toggler< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: text::Renderer, { is_toggled: bool, @@ -52,12 +50,11 @@ pub struct Toggler< text_shaping: text::Shaping, spacing: f32, font: Option, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, } impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: text::Renderer, { /// The default size of a [`Toggler`]. @@ -77,6 +74,7 @@ where f: F, ) -> Self where + Theme: Style, F: 'a + Fn(bool) -> Message, { Toggler { @@ -91,7 +89,7 @@ where text_shaping: text::Shaping::Basic, spacing: Self::DEFAULT_SIZE / 2.0, font: None, - style: Default::default(), + style: Theme::style(), } } @@ -149,7 +147,7 @@ where } /// Sets the style of the [`Toggler`]. - pub fn style(mut self, style: impl Into) -> Self { + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { self.style = style.into(); self } @@ -158,7 +156,6 @@ where impl<'a, Message, Theme, Renderer> Widget for Toggler<'a, Message, Theme, Renderer> where - Theme: StyleSheet + crate::text::StyleSheet, Renderer: text::Renderer, { fn tag(&self) -> tree::Tag { @@ -294,12 +291,18 @@ where let bounds = toggler_layout.bounds(); let is_mouse_over = cursor.is_over(layout.bounds()); - let style = if is_mouse_over { - theme.hovered(&self.style, self.is_toggled) + let status = if is_mouse_over { + Status::Hovered { + is_toggled: self.is_toggled, + } } else { - theme.active(&self.style, self.is_toggled) + Status::Active { + is_toggled: self.is_toggled, + } }; + let appearance = (self.style)(theme, status); + let border_radius = bounds.height / BORDER_RADIUS_RATIO; let space = SPACE_RATIO * bounds.height; @@ -315,12 +318,12 @@ where bounds: toggler_background_bounds, border: Border { radius: border_radius.into(), - width: style.background_border_width, - color: style.background_border_color, + width: appearance.background_border_width, + color: appearance.background_border_color, }, ..renderer::Quad::default() }, - style.background, + appearance.background, ); let toggler_foreground_bounds = Rectangle { @@ -340,12 +343,12 @@ where bounds: toggler_foreground_bounds, border: Border { radius: border_radius.into(), - width: style.foreground_border_width, - color: style.foreground_border_color, + width: appearance.foreground_border_width, + color: appearance.foreground_border_color, }, ..renderer::Quad::default() }, - style.foreground, + appearance.foreground, ); } } @@ -354,7 +357,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + crate::text::StyleSheet + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { fn from( @@ -363,3 +366,91 @@ where Element::new(toggler) } } + +/// The possible status of a [`Toggler`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Toggler`] can be interacted with. + Active { + /// Indicates whether the [`Toggler`] is toggled. + is_toggled: bool, + }, + /// The [`Toggler`] is being hovered. + Hovered { + /// Indicates whether the [`Toggler`] is toggled. + is_toggled: bool, + }, +} + +/// The appearance of a toggler. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The background [`Color`] of the toggler. + pub background: Color, + /// The width of the background border of the toggler. + pub background_border_width: f32, + /// The [`Color`] of the background border of the toggler. + pub background_border_color: Color, + /// The foreground [`Color`] of the toggler. + pub foreground: Color, + /// The width of the foreground border of the toggler. + pub foreground_border_width: f32, + /// The [`Color`] of the foreground border of the toggler. + pub foreground_border_color: Color, +} + +/// The definiton of the default style of a [`Toggler`]. +pub trait Style { + /// Returns the default style of a [`Toggler`]. + fn style() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { + default + } +} + +/// The default style of a [`Toggler`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let background = match status { + Status::Active { is_toggled } | Status::Hovered { is_toggled } => { + if is_toggled { + palette.primary.strong.color + } else { + palette.background.strong.color + } + } + }; + + let foreground = match status { + Status::Active { is_toggled } => { + if is_toggled { + palette.primary.strong.text + } else { + palette.background.base.color + } + } + Status::Hovered { is_toggled } => { + if is_toggled { + Color { + a: 0.5, + ..palette.primary.strong.text + } + } else { + palette.background.weak.color + } + } + }; + + Appearance { + background, + foreground, + foreground_border_width: 0.0, + foreground_border_color: Color::TRANSPARENT, + background_border_width: 0.0, + background_border_color: Color::TRANSPARENT, + } +} -- cgit From 69bc1df252382a662228c8b0da6f60358e90f376 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 11:36:33 +0100 Subject: Simplify theming for `Svg` widget --- widget/src/helpers.rs | 2 +- widget/src/svg.rs | 75 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 22 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 8cb03691..60e7d34f 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -371,7 +371,7 @@ pub fn image(handle: impl Into) -> crate::Image { #[cfg(feature = "svg")] pub fn svg(handle: impl Into) -> crate::Svg where - Theme: crate::svg::StyleSheet, + Theme: crate::svg::Style, { crate::Svg::new(handle) } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 12ef3d92..a0402288 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -5,13 +5,13 @@ use crate::core::renderer; use crate::core::svg; use crate::core::widget::Tree; use crate::core::{ - ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget, + Color, ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget, }; +use crate::style::Theme; use std::path::PathBuf; -pub use crate::style::svg::{Appearance, StyleSheet}; -pub use svg::Handle; +pub use crate::core::svg::Handle; /// A vector graphics image. /// @@ -20,36 +20,36 @@ pub use svg::Handle; /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. #[allow(missing_debug_implementations)] -pub struct Svg -where - Theme: StyleSheet, -{ +pub struct Svg { handle: Handle, width: Length, height: Length, content_fit: ContentFit, - style: ::Style, + style: fn(&Theme, Status) -> Appearance, } -impl Svg -where - Theme: StyleSheet, -{ +impl Svg { /// Creates a new [`Svg`] from the given [`Handle`]. - pub fn new(handle: impl Into) -> Self { + pub fn new(handle: impl Into) -> Self + where + Theme: Style, + { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, - style: Default::default(), + style: Theme::style(), } } /// Creates a new [`Svg`] that will display the contents of the file at the /// provided path. #[must_use] - pub fn from_path(path: impl Into) -> Self { + pub fn from_path(path: impl Into) -> Self + where + Theme: Style, + { Self::new(Handle::from_path(path)) } @@ -80,7 +80,7 @@ where /// Sets the style variant of this [`Svg`]. #[must_use] - pub fn style(mut self, style: impl Into) -> Self { + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { self.style = style.into(); self } @@ -88,7 +88,6 @@ where impl Widget for Svg where - Theme: iced_style::svg::StyleSheet, Renderer: svg::Renderer, { fn size(&self) -> Size { @@ -158,12 +157,14 @@ where ..bounds }; - let appearance = if is_mouse_over { - theme.hovered(&self.style) + let status = if is_mouse_over { + Status::Hovered } else { - theme.appearance(&self.style) + Status::Idle }; + let appearance = (self.style)(theme, status); + renderer.draw( self.handle.clone(), appearance.color, @@ -184,10 +185,42 @@ where impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where - Theme: iced_style::svg::StyleSheet + 'a, + Theme: 'a, Renderer: svg::Renderer + 'a, { fn from(icon: Svg) -> Element<'a, Message, Theme, Renderer> { Element::new(icon) } } + +/// The possible status of an [`Svg`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Svg`] is idle. + Idle, + /// The [`Svg`] is being hovered. + Hovered, +} + +/// The appearance of an [`Svg`]. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct Appearance { + /// The [`Color`] filter of an [`Svg`]. + /// + /// Useful for coloring a symbolic icon. + /// + /// `None` keeps the original color. + pub color: Option, +} + +/// The definiton of the default style of an [`Svg`]. +pub trait Style { + /// Returns the default style of an [`Svg`]. + fn style() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { + |_, _| Appearance::default() + } +} -- cgit From 68c8f23f02a55373db728b115c3a4360669e2b80 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 15:20:10 +0100 Subject: Simplify theming for `TextEditor` widget --- widget/src/helpers.rs | 2 +- widget/src/text_editor.rs | 123 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 105 insertions(+), 20 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 60e7d34f..8dc2e60f 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -224,7 +224,7 @@ pub fn text_editor( ) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Theme, Renderer> where Message: Clone, - Theme: text_editor::StyleSheet, + Theme: text_editor::Style, Renderer: core::text::Renderer, { TextEditor::new(content) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index bad3ef4d..91670228 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -11,15 +11,16 @@ use crate::core::text::highlighter::{self, Highlighter}; use crate::core::text::{self, LineHeight}; use crate::core::widget::{self, Widget}; use crate::core::{ - Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, + Background, Border, Color, Element, Length, Padding, Pixels, Rectangle, + Shell, Size, Vector, }; +use crate::style::Theme; use std::cell::RefCell; use std::fmt; use std::ops::DerefMut; use std::sync::Arc; -pub use crate::style::text_editor::{Appearance, StyleSheet}; pub use text::editor::{Action, Edit, Motion}; /// A multi-line text input. @@ -32,7 +33,6 @@ pub struct TextEditor< Renderer = crate::Renderer, > where Highlighter: text::Highlighter, - Theme: StyleSheet, Renderer: text::Renderer, { content: &'a Content, @@ -42,7 +42,7 @@ pub struct TextEditor< width: Length, height: Length, padding: Padding, - style: Theme::Style, + style: fn(&Theme, Status) -> Appearance, on_edit: Option Message + 'a>>, highlighter_settings: Highlighter::Settings, highlighter_format: fn( @@ -54,11 +54,13 @@ pub struct TextEditor< impl<'a, Message, Theme, Renderer> TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: text::Renderer, { /// Creates new [`TextEditor`] with the given [`Content`]. - pub fn new(content: &'a Content) -> Self { + pub fn new(content: &'a Content) -> Self + where + Theme: Style, + { Self { content, font: None, @@ -67,7 +69,7 @@ where width: Length::Fill, height: Length::Shrink, padding: Padding::new(5.0), - style: Default::default(), + style: Theme::style(), on_edit: None, highlighter_settings: (), highlighter_format: |_highlight, _theme| { @@ -81,7 +83,6 @@ impl<'a, Highlighter, Message, Theme, Renderer> TextEditor<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: StyleSheet, Renderer: text::Renderer, { /// Sets the height of the [`TextEditor`]. @@ -142,8 +143,8 @@ where } /// Sets the style of the [`TextEditor`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { + self.style = style; self } } @@ -306,7 +307,6 @@ impl<'a, Highlighter, Message, Theme, Renderer> Widget for TextEditor<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, - Theme: StyleSheet, Renderer: text::Renderer, { fn tag(&self) -> widget::tree::Tag { @@ -496,16 +496,18 @@ where let is_disabled = self.on_edit.is_none(); let is_mouse_over = cursor.is_over(bounds); - let appearance = if is_disabled { - theme.disabled(&self.style) + let status = if is_disabled { + Status::Disabled } else if state.is_focused { - theme.focused(&self.style) + Status::Focused } else if is_mouse_over { - theme.hovered(&self.style) + Status::Hovered } else { - theme.active(&self.style) + Status::Active }; + let appearance = (self.style)(theme, status); + renderer.fill_quad( renderer::Quad { bounds, @@ -551,7 +553,7 @@ where }, ..renderer::Quad::default() }, - theme.value_color(&self.style), + appearance.value, ); } } @@ -564,7 +566,7 @@ where bounds: range, ..renderer::Quad::default() }, - theme.selection_color(&self.style), + appearance.selection, ); } } @@ -600,7 +602,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> where Highlighter: text::Highlighter, Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: text::Renderer, { fn from( @@ -776,3 +778,86 @@ mod platform { } } } + +/// The possible status of a [`TextInput`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`TextInput`] can be interacted with. + Active, + /// The [`TextInput`] is being hovered. + Hovered, + /// The [`TextInput`] is focused. + Focused, + /// The [`TextInput`] cannot be interacted with. + Disabled, +} + +/// The appearance of a text input. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the text input. + pub background: Background, + /// The [`Border`] of the text input. + pub border: Border, + /// The [`Color`] of the icon of the text input. + pub icon: Color, + /// The [`Color`] of the placeholder of the text input. + pub placeholder: Color, + /// The [`Color`] of the value of the text input. + pub value: Color, + /// The [`Color`] of the selection of the text input. + pub selection: Color, +} + +/// The definiton of the default style of a [`TextInput`]. +pub trait Style { + /// Returns the default style of a [`TextInput`]. + fn style() -> fn(&Self, Status) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self, Status) -> Appearance { + default + } +} + +/// The default style of a [`TextInput`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let active = Appearance { + background: Background::Color(palette.background.base.color), + border: Border { + radius: 2.0.into(), + width: 1.0, + color: palette.background.strong.color, + }, + icon: palette.background.weak.text, + placeholder: palette.background.strong.color, + value: palette.background.base.text, + selection: palette.primary.weak.color, + }; + + match status { + Status::Active => active, + Status::Hovered => Appearance { + border: Border { + color: palette.background.base.text, + ..active.border + }, + ..active + }, + Status::Focused => Appearance { + border: Border { + color: palette.primary.strong.color, + ..active.border + }, + ..active + }, + Status::Disabled => Appearance { + background: Background::Color(palette.background.weak.color), + value: active.placeholder, + ..active + }, + } +} -- cgit From 9b2fd6416775cb27af69e34fb20063d28b4314eb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 15:41:57 +0100 Subject: Simplify theming for `PaneGrid` widget --- widget/src/pane_grid.rs | 1073 +++++++++++++++++++++++------------------------ 1 file changed, 522 insertions(+), 551 deletions(-) (limited to 'widget') diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index a18d0fbf..62067e66 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -30,8 +30,6 @@ pub use split::Split; pub use state::State; pub use title_bar::TitleBar; -pub use crate::style::pane_grid::{Appearance, Line, StyleSheet}; - use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -41,9 +39,10 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, + Point, Rectangle, Shell, Size, Vector, Widget, }; +use crate::style::Theme; const DRAG_DEADBAND_DISTANCE: f32 = 10.0; const THICKNESS_RATIO: f32 = 25.0; @@ -104,7 +103,6 @@ pub struct PaneGrid< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: crate::core::Renderer, { contents: Contents<'a, Content<'a, Message, Theme, Renderer>>, @@ -114,12 +112,11 @@ pub struct PaneGrid< on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(f32, Box Message + 'a>)>, - style: ::Style, + style: fn(&Theme) -> Appearance, } impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: crate::core::Renderer, { /// Creates a [`PaneGrid`] with the given [`State`] and view function. @@ -129,7 +126,10 @@ where pub fn new( state: &'a State, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, - ) -> Self { + ) -> Self + where + Theme: Style, + { let contents = if let Some((pane, pane_state)) = state.maximized.and_then(|pane| { state.panes.get(&pane).map(|pane_state| (pane, pane_state)) @@ -160,7 +160,7 @@ where on_click: None, on_drag: None, on_resize: None, - style: Default::default(), + style: Theme::style(), } } @@ -220,11 +220,8 @@ where } /// Sets the style of the [`PaneGrid`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); + pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { + self.style = style; self } @@ -239,7 +236,6 @@ impl<'a, Message, Theme, Renderer> Widget for PaneGrid<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, - Theme: StyleSheet, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -284,19 +280,29 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - tree, - renderer, - limits, - self.contents.layout(), - self.width, - self.height, - self.spacing, - self.contents.iter(), - |content, tree, renderer, limits| { - content.layout(tree, renderer, limits) - }, - ) + let size = limits.resolve(self.width, self.height, Size::ZERO); + let node = self.contents.layout(); + let regions = node.pane_regions(self.spacing, size); + + let children = self + .contents + .iter() + .zip(tree.children.iter_mut()) + .filter_map(|((pane, content), tree)| { + let region = regions.get(&pane)?; + let size = Size::new(region.width, region.height); + + let node = content.layout( + tree, + renderer, + &layout::Limits::new(size, size), + ); + + Some(node.move_to(Point::new(region.x, region.y))) + }) + .collect(); + + layout::Node::with_children(size, children) } fn operate( @@ -328,7 +334,10 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { + let mut event_status = event::Status::Ignored; + let action = tree.state.downcast_mut::(); + let node = self.contents.layout(); let on_drag = if self.drag_enabled() { &self.on_drag @@ -336,19 +345,164 @@ where &None }; - let event_status = update( - action, - self.contents.layout(), - &event, - layout, - cursor, - shell, - self.spacing, - self.contents.iter(), - &self.on_click, - on_drag, - &self.on_resize, - ); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let bounds = layout.bounds(); + + if let Some(cursor_position) = cursor.position_over(bounds) { + event_status = event::Status::Captured; + + match &self.on_resize { + Some((leeway, _)) => { + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = node.split_regions( + self.spacing, + Size::new(bounds.width, bounds.height), + ); + + let clicked_split = hovered_split( + splits.iter(), + self.spacing + leeway, + relative_cursor, + ); + + if let Some((split, axis, _)) = clicked_split { + if action.picked_pane().is_none() { + *action = + state::Action::Resizing { split, axis }; + } + } else { + click_pane( + action, + layout, + cursor_position, + shell, + self.contents.iter(), + &self.on_click, + on_drag, + ); + } + } + None => { + click_pane( + action, + layout, + cursor_position, + shell, + self.contents.iter(), + &self.on_click, + on_drag, + ); + } + } + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + if let Some((pane, origin)) = action.picked_pane() { + if let Some(on_drag) = on_drag { + if let Some(cursor_position) = cursor.position() { + if cursor_position.distance(origin) + > DRAG_DEADBAND_DISTANCE + { + let event = if let Some(edge) = + in_edge(layout, cursor_position) + { + DragEvent::Dropped { + pane, + target: Target::Edge(edge), + } + } else { + let dropped_region = self + .contents + .iter() + .zip(layout.children()) + .find_map(|(target, layout)| { + layout_region( + layout, + cursor_position, + ) + .map(|region| (target, region)) + }); + + match dropped_region { + Some(((target, _), region)) + if pane != target => + { + DragEvent::Dropped { + pane, + target: Target::Pane( + target, region, + ), + } + } + _ => DragEvent::Canceled { pane }, + } + }; + + shell.publish(on_drag(event)); + } + } + } + + event_status = event::Status::Captured; + } else if action.picked_split().is_some() { + event_status = event::Status::Captured; + } + + *action = state::Action::Idle; + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let Some((_, on_resize)) = &self.on_resize { + if let Some((split, _)) = action.picked_split() { + let bounds = layout.bounds(); + + let splits = node.split_regions( + self.spacing, + Size::new(bounds.width, bounds.height), + ); + + if let Some((axis, rectangle, _)) = splits.get(&split) { + if let Some(cursor_position) = cursor.position() { + let ratio = match axis { + Axis::Horizontal => { + let position = cursor_position.y + - bounds.y + - rectangle.y; + + (position / rectangle.height) + .clamp(0.1, 0.9) + } + Axis::Vertical => { + let position = cursor_position.x + - bounds.x + - rectangle.x; + + (position / rectangle.width) + .clamp(0.1, 0.9) + } + }; + + shell.publish(on_resize(ResizeEvent { + split, + ratio, + })); + + event_status = event::Status::Captured; + } + } + } + } + } + _ => {} + } let picked_pane = action.picked_pane().map(|(pane, _)| pane); @@ -382,32 +536,61 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction( - tree.state.downcast_ref(), - self.contents.layout(), - layout, - cursor, - self.spacing, - self.on_resize.as_ref().map(|(leeway, _)| *leeway), - ) - .unwrap_or_else(|| { - self.contents - .iter() - .zip(&tree.children) - .zip(layout.children()) - .map(|(((_pane, content), tree), layout)| { - content.mouse_interaction( - tree, - layout, - cursor, - viewport, - renderer, - self.drag_enabled(), + let action = tree.state.downcast_ref::(); + + if action.picked_pane().is_some() { + return mouse::Interaction::Grabbing; + } + + let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); + let node = self.contents.layout(); + + let resize_axis = + action.picked_split().map(|(_, axis)| axis).or_else(|| { + resize_leeway.and_then(|leeway| { + let cursor_position = cursor.position()?; + let bounds = layout.bounds(); + + let splits = + node.split_regions(self.spacing, bounds.size()); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + hovered_split( + splits.iter(), + self.spacing + leeway, + relative_cursor, ) + .map(|(_, axis, _)| axis) }) - .max() - .unwrap_or_default() - }) + }); + + if let Some(resize_axis) = resize_axis { + return match resize_axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + }; + } + + self.contents + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|(((_pane, content), tree), layout)| { + content.mouse_interaction( + tree, + layout, + cursor, + viewport, + renderer, + self.drag_enabled(), + ) + }) + .max() + .unwrap_or_default() } fn draw( @@ -420,28 +603,210 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - draw( - tree.state.downcast_ref(), - self.contents.layout(), - layout, - cursor, - renderer, - theme, - style, - viewport, - self.spacing, - self.on_resize.as_ref().map(|(leeway, _)| *leeway), - &self.style, - self.contents - .iter() - .zip(&tree.children) - .map(|((pane, content), tree)| (pane, (content, tree))), - |(content, tree), renderer, style, layout, cursor, rectangle| { - content.draw( - tree, renderer, theme, style, layout, cursor, rectangle, + let action = tree.state.downcast_ref::(); + let node = self.contents.layout(); + let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); + + let contents = self + .contents + .iter() + .zip(&tree.children) + .map(|((pane, content), tree)| (pane, (content, tree))); + + let picked_pane = action.picked_pane().filter(|(_, origin)| { + cursor + .position() + .map(|position| position.distance(*origin)) + .unwrap_or_default() + > DRAG_DEADBAND_DISTANCE + }); + + let picked_split = action + .picked_split() + .and_then(|(split, axis)| { + let bounds = layout.bounds(); + + let splits = node.split_regions(self.spacing, bounds.size()); + + let (_axis, region, ratio) = splits.get(&split)?; + + let region = + axis.split_line_bounds(*region, *ratio, self.spacing); + + Some((axis, region + Vector::new(bounds.x, bounds.y), true)) + }) + .or_else(|| match resize_leeway { + Some(leeway) => { + let cursor_position = cursor.position()?; + let bounds = layout.bounds(); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = + node.split_regions(self.spacing, bounds.size()); + + let (_split, axis, region) = hovered_split( + splits.iter(), + self.spacing + leeway, + relative_cursor, + )?; + + Some(( + axis, + region + Vector::new(bounds.x, bounds.y), + false, + )) + } + None => None, + }); + + let pane_cursor = if picked_pane.is_some() { + mouse::Cursor::Unavailable + } else { + cursor + }; + + let mut render_picked_pane = None; + + let pane_in_edge = if picked_pane.is_some() { + cursor + .position() + .and_then(|cursor_position| in_edge(layout, cursor_position)) + } else { + None + }; + + let appearance = (self.style)(theme); + + for ((id, (content, tree)), pane_layout) in + contents.zip(layout.children()) + { + match picked_pane { + Some((dragging, origin)) if id == dragging => { + render_picked_pane = + Some(((content, tree), origin, pane_layout)); + } + Some((dragging, _)) if id != dragging => { + content.draw( + tree, + renderer, + theme, + style, + pane_layout, + pane_cursor, + viewport, + ); + + if picked_pane.is_some() && pane_in_edge.is_none() { + if let Some(region) = + cursor.position().and_then(|cursor_position| { + layout_region(pane_layout, cursor_position) + }) + { + let bounds = + layout_region_bounds(pane_layout, region); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.hovered_region.border, + ..renderer::Quad::default() + }, + appearance.hovered_region.background, + ); + } + } + } + _ => { + content.draw( + tree, + renderer, + theme, + style, + pane_layout, + pane_cursor, + viewport, + ); + } + } + } + + if let Some(edge) = pane_in_edge { + let bounds = edge_bounds(layout, edge); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.hovered_region.border, + ..renderer::Quad::default() + }, + appearance.hovered_region.background, + ); + } + + // Render picked pane last + if let Some(((content, tree), origin, layout)) = render_picked_pane { + if let Some(cursor_position) = cursor.position() { + let bounds = layout.bounds(); + + let translation = + cursor_position - Point::new(origin.x, origin.y); + + renderer.with_translation(translation, |renderer| { + renderer.with_layer(bounds, |renderer| { + content.draw( + tree, + renderer, + theme, + style, + layout, + pane_cursor, + viewport, + ); + }); + }); + } + } + + if picked_pane.is_none() { + if let Some((axis, split_region, is_picked)) = picked_split { + let highlight = if is_picked { + appearance.picked_split + } else { + appearance.hovered_split + }; + + renderer.fill_quad( + renderer::Quad { + bounds: match axis { + Axis::Horizontal => Rectangle { + x: split_region.x, + y: (split_region.y + + (split_region.height - highlight.width) + / 2.0) + .round(), + width: split_region.width, + height: highlight.width, + }, + Axis::Vertical => Rectangle { + x: (split_region.x + + (split_region.width - highlight.width) + / 2.0) + .round(), + y: split_region.y, + width: highlight.width, + height: split_region.height, + }, + }, + ..renderer::Quad::default() + }, + highlight.color, ); - }, - ); + } + } } fn overlay<'b>( @@ -469,7 +834,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: crate::core::Renderer + 'a, { fn from( @@ -479,219 +844,6 @@ where } } -/// Calculates the [`Layout`] of a [`PaneGrid`]. -pub fn layout( - tree: &mut Tree, - renderer: &Renderer, - limits: &layout::Limits, - node: &Node, - width: Length, - height: Length, - spacing: f32, - contents: impl Iterator, - layout_content: impl Fn( - T, - &mut Tree, - &Renderer, - &layout::Limits, - ) -> layout::Node, -) -> layout::Node { - let size = limits.resolve(width, height, Size::ZERO); - - let regions = node.pane_regions(spacing, size); - let children = contents - .zip(tree.children.iter_mut()) - .filter_map(|((pane, content), tree)| { - let region = regions.get(&pane)?; - let size = Size::new(region.width, region.height); - - let node = layout_content( - content, - tree, - renderer, - &layout::Limits::new(size, size), - ); - - Some(node.move_to(Point::new(region.x, region.y))) - }) - .collect(); - - layout::Node::with_children(size, children) -} - -/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`] -/// accordingly. -pub fn update<'a, Message, T: Draggable>( - action: &mut state::Action, - node: &Node, - event: &Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - spacing: f32, - contents: impl Iterator, - on_click: &Option Message + 'a>>, - on_drag: &Option Message + 'a>>, - on_resize: &Option<(f32, Box Message + 'a>)>, -) -> event::Status { - let mut event_status = event::Status::Ignored; - - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let bounds = layout.bounds(); - - if let Some(cursor_position) = cursor.position_over(bounds) { - event_status = event::Status::Captured; - - match on_resize { - Some((leeway, _)) => { - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = node.split_regions( - spacing, - Size::new(bounds.width, bounds.height), - ); - - let clicked_split = hovered_split( - splits.iter(), - spacing + leeway, - relative_cursor, - ); - - if let Some((split, axis, _)) = clicked_split { - if action.picked_pane().is_none() { - *action = - state::Action::Resizing { split, axis }; - } - } else { - click_pane( - action, - layout, - cursor_position, - shell, - contents, - on_click, - on_drag, - ); - } - } - None => { - click_pane( - action, - layout, - cursor_position, - shell, - contents, - on_click, - on_drag, - ); - } - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - if let Some((pane, origin)) = action.picked_pane() { - if let Some(on_drag) = on_drag { - if let Some(cursor_position) = cursor.position() { - if cursor_position.distance(origin) - > DRAG_DEADBAND_DISTANCE - { - let event = if let Some(edge) = - in_edge(layout, cursor_position) - { - DragEvent::Dropped { - pane, - target: Target::Edge(edge), - } - } else { - let dropped_region = contents - .zip(layout.children()) - .find_map(|(target, layout)| { - layout_region(layout, cursor_position) - .map(|region| (target, region)) - }); - - match dropped_region { - Some(((target, _), region)) - if pane != target => - { - DragEvent::Dropped { - pane, - target: Target::Pane( - target, region, - ), - } - } - _ => DragEvent::Canceled { pane }, - } - }; - - shell.publish(on_drag(event)); - } - } - } - - event_status = event::Status::Captured; - } else if action.picked_split().is_some() { - event_status = event::Status::Captured; - } - - *action = state::Action::Idle; - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some((_, on_resize)) = on_resize { - if let Some((split, _)) = action.picked_split() { - let bounds = layout.bounds(); - - let splits = node.split_regions( - spacing, - Size::new(bounds.width, bounds.height), - ); - - if let Some((axis, rectangle, _)) = splits.get(&split) { - if let Some(cursor_position) = cursor.position() { - let ratio = match axis { - Axis::Horizontal => { - let position = cursor_position.y - - bounds.y - - rectangle.y; - - (position / rectangle.height) - .clamp(0.1, 0.9) - } - Axis::Vertical => { - let position = cursor_position.x - - bounds.x - - rectangle.x; - - (position / rectangle.width).clamp(0.1, 0.9) - } - }; - - shell.publish(on_resize(ResizeEvent { - split, - ratio, - })); - - event_status = event::Status::Captured; - } - } - } - } - } - _ => {} - } - - event_status -} - fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option { let bounds = layout.bounds(); @@ -747,257 +899,6 @@ fn click_pane<'a, Message, T>( } } -/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. -pub fn mouse_interaction( - action: &state::Action, - node: &Node, - layout: Layout<'_>, - cursor: mouse::Cursor, - spacing: f32, - resize_leeway: Option, -) -> Option { - if action.picked_pane().is_some() { - return Some(mouse::Interaction::Grabbing); - } - - let resize_axis = - action.picked_split().map(|(_, axis)| axis).or_else(|| { - resize_leeway.and_then(|leeway| { - let cursor_position = cursor.position()?; - let bounds = layout.bounds(); - - let splits = node.split_regions(spacing, bounds.size()); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - hovered_split(splits.iter(), spacing + leeway, relative_cursor) - .map(|(_, axis, _)| axis) - }) - }); - - if let Some(resize_axis) = resize_axis { - return Some(match resize_axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - }); - } - - None -} - -/// Draws a [`PaneGrid`]. -pub fn draw( - action: &state::Action, - node: &Node, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &mut Renderer, - theme: &Theme, - default_style: &renderer::Style, - viewport: &Rectangle, - spacing: f32, - resize_leeway: Option, - style: &Theme::Style, - contents: impl Iterator, - draw_pane: impl Fn( - T, - &mut Renderer, - &renderer::Style, - Layout<'_>, - mouse::Cursor, - &Rectangle, - ), -) where - Theme: StyleSheet, - Renderer: crate::core::Renderer, -{ - let picked_pane = action.picked_pane().filter(|(_, origin)| { - cursor - .position() - .map(|position| position.distance(*origin)) - .unwrap_or_default() - > DRAG_DEADBAND_DISTANCE - }); - - let picked_split = action - .picked_split() - .and_then(|(split, axis)| { - let bounds = layout.bounds(); - - let splits = node.split_regions(spacing, bounds.size()); - - let (_axis, region, ratio) = splits.get(&split)?; - - let region = axis.split_line_bounds(*region, *ratio, spacing); - - Some((axis, region + Vector::new(bounds.x, bounds.y), true)) - }) - .or_else(|| match resize_leeway { - Some(leeway) => { - let cursor_position = cursor.position()?; - let bounds = layout.bounds(); - - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = node.split_regions(spacing, bounds.size()); - - let (_split, axis, region) = hovered_split( - splits.iter(), - spacing + leeway, - relative_cursor, - )?; - - Some((axis, region + Vector::new(bounds.x, bounds.y), false)) - } - None => None, - }); - - let pane_cursor = if picked_pane.is_some() { - mouse::Cursor::Unavailable - } else { - cursor - }; - - let mut render_picked_pane = None; - - let pane_in_edge = if picked_pane.is_some() { - cursor - .position() - .and_then(|cursor_position| in_edge(layout, cursor_position)) - } else { - None - }; - - for ((id, pane), pane_layout) in contents.zip(layout.children()) { - match picked_pane { - Some((dragging, origin)) if id == dragging => { - render_picked_pane = Some((pane, origin, pane_layout)); - } - Some((dragging, _)) if id != dragging => { - draw_pane( - pane, - renderer, - default_style, - pane_layout, - pane_cursor, - viewport, - ); - - if picked_pane.is_some() && pane_in_edge.is_none() { - if let Some(region) = - cursor.position().and_then(|cursor_position| { - layout_region(pane_layout, cursor_position) - }) - { - let bounds = layout_region_bounds(pane_layout, region); - let hovered_region_style = theme.hovered_region(style); - - renderer.fill_quad( - renderer::Quad { - bounds, - border: hovered_region_style.border, - ..renderer::Quad::default() - }, - theme.hovered_region(style).background, - ); - } - } - } - _ => { - draw_pane( - pane, - renderer, - default_style, - pane_layout, - pane_cursor, - viewport, - ); - } - } - } - - if let Some(edge) = pane_in_edge { - let hovered_region_style = theme.hovered_region(style); - let bounds = edge_bounds(layout, edge); - - renderer.fill_quad( - renderer::Quad { - bounds, - border: hovered_region_style.border, - ..renderer::Quad::default() - }, - theme.hovered_region(style).background, - ); - } - - // Render picked pane last - if let Some((pane, origin, layout)) = render_picked_pane { - if let Some(cursor_position) = cursor.position() { - let bounds = layout.bounds(); - - let translation = cursor_position - Point::new(origin.x, origin.y); - - renderer.with_translation(translation, |renderer| { - renderer.with_layer(bounds, |renderer| { - draw_pane( - pane, - renderer, - default_style, - layout, - pane_cursor, - viewport, - ); - }); - }); - } - } - - if picked_pane.is_none() { - if let Some((axis, split_region, is_picked)) = picked_split { - let highlight = if is_picked { - theme.picked_split(style) - } else { - theme.hovered_split(style) - }; - - if let Some(highlight) = highlight { - renderer.fill_quad( - renderer::Quad { - bounds: match axis { - Axis::Horizontal => Rectangle { - x: split_region.x, - y: (split_region.y - + (split_region.height - highlight.width) - / 2.0) - .round(), - width: split_region.width, - height: highlight.width, - }, - Axis::Vertical => Rectangle { - x: (split_region.x - + (split_region.width - highlight.width) - / 2.0) - .round(), - y: split_region.y, - width: highlight.width, - height: split_region.height, - }, - }, - ..renderer::Quad::default() - }, - highlight.color, - ); - } - } - } -} - fn in_edge(layout: Layout<'_>, cursor: Point) -> Option { let bounds = layout.bounds(); @@ -1214,3 +1115,73 @@ impl<'a, T> Contents<'a, T> { matches!(self, Self::Maximized(..)) } } + +/// The appearance of a [`PaneGrid`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { + /// The appearance of a hovered region highlight. + hovered_region: Highlight, + /// The appearance of a picked split. + picked_split: Line, + /// The appearance of a hovered split. + hovered_split: Line, +} + +/// The appearance of a highlight of the [`PaneGrid`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Highlight { + /// The [`Background`] of the pane region. + pub background: Background, + /// The [`Border`] of the pane region. + pub border: Border, +} + +/// A line. +/// +/// It is normally used to define the highlight of something, like a split. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Line { + /// The [`Color`] of the [`Line`]. + pub color: Color, + /// The width of the [`Line`]. + pub width: f32, +} + +/// The definiton of the default style of a [`PaneGrid`]. +pub trait Style { + /// Returns the default style of a [`PaneGrid`]. + fn style() -> fn(&Self) -> Appearance; +} + +impl Style for Theme { + fn style() -> fn(&Self) -> Appearance { + default + } +} + +/// The default style of a [`PaneGrid`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + hovered_region: Highlight { + background: Background::Color(Color { + a: 0.5, + ..palette.primary.base.color + }), + border: Border { + width: 2.0, + color: palette.primary.strong.color, + radius: 0.0.into(), + }, + }, + hovered_split: Line { + color: palette.primary.base.color, + width: 2.0, + }, + picked_split: Line { + color: palette.primary.strong.color, + width: 2.0, + }, + } +} -- cgit From 597a41cea73f078eda04eb3ff40cfda5d37d6135 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 17:08:28 +0100 Subject: Simplify theming for `PickList`, `ComboBox`, and `Menu` widgets --- widget/src/combo_box.rs | 89 +++++--- widget/src/container.rs | 27 ++- widget/src/helpers.rs | 11 +- widget/src/overlay/menu.rs | 143 +++++++++--- widget/src/pane_grid/content.rs | 2 +- widget/src/pane_grid/title_bar.rs | 2 +- widget/src/pick_list.rs | 449 +++++++++++++++++++------------------- widget/src/scrollable.rs | 3 +- widget/src/tooltip.rs | 2 +- 9 files changed, 421 insertions(+), 307 deletions(-) (limited to 'widget') diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 2ecf799d..533daab2 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -1,4 +1,5 @@ //! Display a dropdown list of searchable and selectable options. +use crate::container; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; @@ -13,8 +14,10 @@ use crate::core::{ Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Vector, }; use crate::overlay::menu; +use crate::scrollable; +use crate::style::Theme; use crate::text::LineHeight; -use crate::{container, scrollable, text_input, TextInput}; +use crate::text_input::{self, TextInput}; use std::cell::RefCell; use std::fmt::Display; @@ -32,7 +35,6 @@ pub struct ComboBox< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: text_input::Style + menu::StyleSheet, Renderer: text::Renderer, { state: &'a State, @@ -43,7 +45,7 @@ pub struct ComboBox< on_option_hovered: Option Message>>, on_close: Option, on_input: Option Message>>, - menu_style: ::Style, + menu_style: menu::Style, padding: Padding, size: Option, } @@ -51,7 +53,6 @@ pub struct ComboBox< impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer> where T: std::fmt::Display + Clone, - Theme: text_input::Style + menu::StyleSheet, Renderer: text::Renderer, { /// Creates a new [`ComboBox`] with the given list of options, a placeholder, @@ -62,9 +63,16 @@ where placeholder: &str, selection: Option<&T>, on_selected: impl Fn(T) -> Message + 'static, - ) -> Self { + ) -> Self + where + Theme: text_input::Style, + Style: Default, + { + let style = Style::::default(); + let text_input = TextInput::new(placeholder, &state.value()) - .on_input(TextInputEvent::TextChanged); + .on_input(TextInputEvent::TextChanged) + .style(style.text_input); let selection = selection.map(T::to_string).unwrap_or_default(); @@ -77,7 +85,7 @@ where on_option_hovered: None, on_input: None, on_close: None, - menu_style: Default::default(), + menu_style: style.menu, padding: text_input::DEFAULT_PADDING, size: None, } @@ -118,21 +126,11 @@ where } /// Sets the style of the [`ComboBox`]. - // TODO: Define its own `StyleSheet` trait - pub fn style(mut self, style: S) -> Self - where - S: Into<::Style>, - { - self.menu_style = style.into(); - self - } + pub fn style(mut self, style: impl Into>) -> Self { + let style = style.into(); - /// Sets the style of the [`TextInput`] of the [`ComboBox`]. - pub fn text_input_style( - mut self, - style: fn(&Theme, text_input::Status) -> text_input::Appearance, - ) -> Self { - self.text_input = self.text_input.style(style); + self.text_input = self.text_input.style(style.text_input); + self.menu_style = style.menu; self } @@ -296,10 +294,7 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Display + Clone + 'static, Message: Clone, - Theme: container::Style - + text_input::Style - + scrollable::Style - + menu::StyleSheet, + Theme: scrollable::Style + container::Style, Renderer: text::Renderer, { fn size(&self) -> Size { @@ -676,7 +671,7 @@ where self.state.sync_filtered_options(filtered_options); - let mut menu = menu::Menu::new( + let mut menu = menu::Menu::with_style( menu, &filtered_options.options, hovered_option, @@ -690,10 +685,10 @@ where (self.on_selected)(x) }, self.on_option_hovered.as_deref(), + self.menu_style, ) .width(bounds.width) - .padding(self.padding) - .style(self.menu_style.clone()); + .padding(self.padding); if let Some(font) = self.font { menu = menu.font(font); @@ -716,11 +711,7 @@ impl<'a, T, Message, Theme, Renderer> where T: Display + Clone + 'static, Message: Clone + 'a, - Theme: container::Style - + text_input::Style - + scrollable::Style - + menu::StyleSheet - + 'a, + Theme: scrollable::Style + container::Style + 'a, Renderer: text::Renderer + 'a, { fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self { @@ -772,3 +763,35 @@ where }) .collect() } + +/// The appearance of a [`ComboBox`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style { + /// The style of the [`TextInput`] of the [`ComboBox`]. + text_input: fn(&Theme, text_input::Status) -> text_input::Appearance, + + /// The style of the [`Menu`] of the [`ComboBox`]. + menu: menu::Style, +} + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + default() + } +} + +/// The default style of a [`ComboBox`]. +pub fn default() -> Style { + Style { + text_input: text_input::default, + menu: menu::Style::default(), + } +} diff --git a/widget/src/container.rs b/widget/src/container.rs index 66e80820..58a24339 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -61,7 +61,7 @@ where max_height: f32::INFINITY, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, - style: Theme::default(), + style: Theme::style(), clip: false, content, } @@ -540,24 +540,24 @@ pub enum Status { /// The style of a [`Container`] for a theme. pub trait Style { /// The default style of a [`Container`]. - fn default() -> fn(&Self, Status) -> Appearance; + fn style() -> fn(&Self, Status) -> Appearance; } impl Style for Theme { - fn default() -> fn(&Self, Status) -> Appearance { + fn style() -> fn(&Self, Status) -> Appearance { transparent } } impl Style for Appearance { - fn default() -> fn(&Self, Status) -> Appearance { + fn style() -> fn(&Self, Status) -> Appearance { |appearance, _status| *appearance } } /// A transparent [`Container`]. pub fn transparent(_theme: &Theme, _status: Status) -> Appearance { - ::default() + Appearance::default() } /// A rounded [`Container`] with a background. @@ -567,6 +567,21 @@ pub fn box_(theme: &Theme, _status: Status) -> Appearance { Appearance { background: Some(palette.background.weak.color.into()), border: Border::with_radius(2), - ..::default() + ..Appearance::default() + } +} + +/// A bordered [`Container`] with a background. +pub fn bordered_box(theme: &Theme, _status: Status) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + background: Some(palette.background.weak.color.into()), + border: Border { + width: 1.0, + radius: 0.0.into(), + color: palette.background.strong.color, + }, + ..Appearance::default() } } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 8dc2e60f..da9a5792 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -7,7 +7,6 @@ use crate::core; use crate::core::widget::operation; use crate::core::{Element, Length, Pixels}; use crate::keyed; -use crate::overlay; use crate::pick_list::{self, PickList}; use crate::progress_bar::{self, ProgressBar}; use crate::radio::{self, Radio}; @@ -276,12 +275,7 @@ where V: Borrow + 'a, Message: Clone, Renderer: core::text::Renderer, - Theme: pick_list::StyleSheet - + scrollable::Style - + overlay::menu::StyleSheet - + container::Style, - ::Style: - From<::Style>, + pick_list::Style: Default, { PickList::new(options, selected, on_selected) } @@ -297,8 +291,9 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>( ) -> ComboBox<'a, T, Message, Theme, Renderer> where T: std::fmt::Display + Clone, - Theme: text_input::Style + overlay::menu::StyleSheet, + Theme: text_input::Style, Renderer: core::text::Renderer, + combo_box::Style: Default, { ComboBox::new(state, placeholder, selection, on_selected) } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index d820592d..bb8ad0e0 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -10,12 +10,12 @@ use crate::core::text::{self, Text}; use crate::core::touch; use crate::core::widget::Tree; use crate::core::{ - Border, Clipboard, Length, Padding, Pixels, Point, Rectangle, Size, Vector, + Background, Border, Clipboard, Color, Length, Padding, Pixels, Point, + Rectangle, Size, Vector, }; use crate::core::{Element, Shell, Widget}; use crate::scrollable::{self, Scrollable}; - -pub use iced_style::menu::{Appearance, StyleSheet}; +use crate::style::Theme; /// A list of selectable options. #[allow(missing_debug_implementations)] @@ -26,7 +26,6 @@ pub struct Menu< Theme = crate::Theme, Renderer = crate::Renderer, > where - Theme: StyleSheet, Renderer: text::Renderer, { state: &'a mut State, @@ -40,14 +39,14 @@ pub struct Menu< text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, - style: Theme::Style, + style: Style, } impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer> where T: ToString + Clone, Message: 'a, - Theme: StyleSheet + container::Style + scrollable::Style + 'a, + Theme: container::Style + scrollable::Style + 'a, Renderer: text::Renderer + 'a, { /// Creates a new [`Menu`] with the given [`State`], a list of options, and @@ -58,6 +57,29 @@ where hovered_option: &'a mut Option, on_selected: impl FnMut(T) -> Message + 'a, on_option_hovered: Option<&'a dyn Fn(T) -> Message>, + ) -> Self + where + Style: Default, + { + Self::with_style( + state, + options, + hovered_option, + on_selected, + on_option_hovered, + Style::default(), + ) + } + + /// Creates a new [`Menu`] with the given [`State`], a list of options, + /// the message to produced when an option is selected, and its [`Style`]. + pub fn with_style( + state: &'a mut State, + options: &'a [T], + hovered_option: &'a mut Option, + on_selected: impl FnMut(T) -> Message + 'a, + on_option_hovered: Option<&'a dyn Fn(T) -> Message>, + style: Style, ) -> Self { Menu { state, @@ -71,7 +93,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - style: Default::default(), + style, } } @@ -115,10 +137,7 @@ where } /// Sets the style of the [`Menu`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { + pub fn style(mut self, style: impl Into>) -> Self { self.style = style.into(); self } @@ -165,7 +184,6 @@ impl Default for State { struct Overlay<'a, Message, Theme, Renderer> where - Theme: StyleSheet + container::Style, Renderer: crate::core::Renderer, { position: Point, @@ -173,13 +191,13 @@ where container: Container<'a, Message, Theme, Renderer>, width: f32, target_height: f32, - style: ::Style, + style: Style, } impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> where Message: 'a, - Theme: StyleSheet + container::Style + scrollable::Style + 'a, + Theme: container::Style + scrollable::Style + 'a, Renderer: text::Renderer + 'a, { pub fn new( @@ -205,18 +223,21 @@ where style, } = menu; - let container = Container::new(Scrollable::new(List { - options, - hovered_option, - on_selected, - on_option_hovered, - font, - text_size, - text_line_height, - text_shaping, - padding, - style: style.clone(), - })); + let container = Container::new( + Scrollable::new(List { + options, + hovered_option, + on_selected, + on_option_hovered, + font, + text_size, + text_line_height, + text_shaping, + padding, + style: style.menu, + }) + .style(style.scrollable), + ); state.tree.diff(&container as &dyn Widget<_, _, _>); @@ -235,7 +256,6 @@ impl<'a, Message, Theme, Renderer> crate::core::Overlay for Overlay<'a, Message, Theme, Renderer> where - Theme: StyleSheet + container::Style, Renderer: text::Renderer, { fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { @@ -302,9 +322,10 @@ where layout: Layout<'_>, cursor: mouse::Cursor, ) { - let appearance = StyleSheet::appearance(theme, &self.style); let bounds = layout.bounds(); + let appearance = (self.style.menu)(theme); + renderer.fill_quad( renderer::Quad { bounds, @@ -321,7 +342,6 @@ where struct List<'a, T, Message, Theme, Renderer> where - Theme: StyleSheet, Renderer: text::Renderer, { options: &'a [T], @@ -333,14 +353,13 @@ where text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, - style: Theme::Style, + style: fn(&Theme) -> Appearance, } impl<'a, T, Message, Theme, Renderer> Widget for List<'a, T, Message, Theme, Renderer> where T: Clone + ToString, - Theme: StyleSheet, Renderer: text::Renderer, { fn size(&self) -> Size { @@ -483,7 +502,7 @@ where _cursor: mouse::Cursor, viewport: &Rectangle, ) { - let appearance = theme.appearance(&self.style); + let appearance = (self.style)(theme); let bounds = layout.bounds(); let text_size = @@ -553,10 +572,68 @@ impl<'a, T, Message, Theme, Renderer> where T: ToString + Clone, Message: 'a, - Theme: StyleSheet + 'a, + Theme: 'a, Renderer: 'a + text::Renderer, { fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self { Element::new(list) } } + +/// The appearance of a [`Menu`]. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The [`Background`] of the menu. + pub background: Background, + /// The [`Border`] of the menu. + pub border: Border, + /// The text [`Color`] of the menu. + pub text_color: Color, + /// The text [`Color`] of a selected option in the menu. + pub selected_text_color: Color, + /// The background [`Color`] of a selected option in the menu. + pub selected_background: Background, +} + +/// The definiton of the default style of a [`Menu`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style { + /// The style of the [`Menu`]. + menu: fn(&Theme) -> Appearance, + /// The style of the [`Scrollable`] of the [`Menu`]. + scrollable: fn(&Theme, scrollable::Status) -> scrollable::Appearance, +} + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Self { + menu: default, + scrollable: scrollable::default, + } + } +} + +/// The default style of a [`Menu`]. +pub fn default(theme: &Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + background: palette.background.weak.color.into(), + border: Border { + width: 1.0, + radius: 0.0.into(), + color: palette.background.strong.color, + }, + text_color: palette.background.weak.text, + selected_text_color: palette.primary.strong.text, + selected_background: palette.primary.strong.color.into(), + } +} diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 78a4f347..25b64e17 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -39,7 +39,7 @@ where Self { title_bar: None, body: body.into(), - style: Theme::default(), + style: Theme::style(), } } diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 6d786f96..787510cc 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -43,7 +43,7 @@ where controls: None, padding: Padding::ZERO, always_show_controls: false, - style: Theme::default(), + style: Theme::style(), } } diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index aeb0f246..546bf294 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -11,16 +11,15 @@ use crate::core::text::{self, Paragraph as _, Text}; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, - Shell, Size, Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Padding, + Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; use crate::scrollable; +use crate::style::Theme; use std::borrow::Borrow; -pub use crate::style::pick_list::{Appearance, StyleSheet}; - /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] pub struct PickList< @@ -35,7 +34,6 @@ pub struct PickList< T: ToString + PartialEq + Clone, L: Borrow<[T]> + 'a, V: Borrow + 'a, - Theme: StyleSheet, Renderer: text::Renderer, { on_select: Box Message + 'a>, @@ -51,7 +49,7 @@ pub struct PickList< text_shaping: text::Shaping, font: Option, handle: Handle, - style: Theme::Style, + style: Style, } impl<'a, T, L, V, Message, Theme, Renderer> @@ -61,8 +59,6 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone, - Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, - ::Style: From<::Style>, Renderer: text::Renderer, { /// The default padding of a [`PickList`]. @@ -74,7 +70,10 @@ where options: L, selected: Option, on_select: impl Fn(T) -> Message + 'a, - ) -> Self { + ) -> Self + where + Style: Default, + { Self { on_select: Box::new(on_select), on_open: None, @@ -89,7 +88,7 @@ where text_shaping: text::Shaping::Basic, font: None, handle: Handle::default(), - style: Default::default(), + style: Style::default(), } } @@ -157,10 +156,7 @@ where } /// Sets the style of the [`PickList`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { + pub fn style(mut self, style: impl Into>) -> Self { self.style = style.into(); self } @@ -173,8 +169,7 @@ where L: Borrow<[T]>, V: Borrow, Message: Clone + 'a, - Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, - ::Style: From<::Style>, + Theme: scrollable::Style + container::Style, Renderer: text::Renderer + 'a, { fn tag(&self) -> tree::Tag { @@ -260,23 +255,124 @@ where viewport: &Rectangle, ) { let font = self.font.unwrap_or_else(|| renderer.default_font()); - draw( - renderer, - theme, - layout, - cursor, - self.padding, - self.text_size, - self.text_line_height, - self.text_shaping, - font, - self.placeholder.as_deref(), - self.selected.as_ref().map(Borrow::borrow), - &self.handle, - &self.style, - || tree.state.downcast_ref::>(), - viewport, + let selected = self.selected.as_ref().map(Borrow::borrow); + let state = tree.state.downcast_ref::>(); + + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + let is_selected = selected.is_some(); + + let status = if state.is_open { + Status::Opened + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }; + + let appearance = (self.style.pick_list)(theme, status); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.border, + ..renderer::Quad::default() + }, + appearance.background, ); + + let handle = match &self.handle { + Handle::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON, + *size, + text::LineHeight::default(), + text::Shaping::Basic, + )), + Handle::Static(Icon { + font, + code_point, + size, + line_height, + shaping, + }) => Some((*font, *code_point, *size, *line_height, *shaping)), + Handle::Dynamic { open, closed } => { + if state.is_open { + Some(( + open.font, + open.code_point, + open.size, + open.line_height, + open.shaping, + )) + } else { + Some(( + closed.font, + closed.code_point, + closed.size, + closed.line_height, + closed.shaping, + )) + } + } + Handle::None => None, + }; + + if let Some((font, code_point, size, line_height, shaping)) = handle { + let size = size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text( + Text { + content: &code_point.to_string(), + size, + line_height, + font, + bounds: Size::new( + bounds.width, + f32::from(line_height.to_absolute(size)), + ), + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Center, + shaping, + }, + Point::new( + bounds.x + bounds.width - self.padding.horizontal(), + bounds.center_y(), + ), + appearance.handle_color, + *viewport, + ); + } + + let label = selected.map(ToString::to_string); + + if let Some(label) = label.as_deref().or(self.placeholder.as_deref()) { + let text_size = + self.text_size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text( + Text { + content: label, + size: text_size, + line_height: self.text_line_height, + font, + bounds: Size::new( + bounds.width - self.padding.horizontal(), + f32::from(self.text_line_height.to_absolute(text_size)), + ), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, + }, + Point::new(bounds.x + self.padding.left, bounds.center_y()), + if is_selected { + appearance.text_color + } else { + appearance.placeholder_color + }, + *viewport, + ); + } } fn overlay<'b>( @@ -287,19 +383,38 @@ where translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::>(); + let font = self.font.unwrap_or_else(|| renderer.default_font()); - overlay( - layout, - translation, - state, - self.padding, - self.text_size, - self.text_shaping, - self.font.unwrap_or_else(|| renderer.default_font()), - self.options.borrow(), - &self.on_select, - self.style.clone(), - ) + if state.is_open { + let bounds = layout.bounds(); + + let on_select = &self.on_select; + + let mut menu = Menu::with_style( + &mut state.menu, + self.options.borrow(), + &mut state.hovered_option, + |option| { + state.is_open = false; + + (on_select)(option) + }, + None, + self.style.menu, + ) + .width(bounds.width) + .padding(self.padding) + .font(font) + .text_shaping(self.text_shaping); + + if let Some(text_size) = self.text_size { + menu = menu.text_size(text_size); + } + + Some(menu.overlay(layout.position() + translation, bounds.height)) + } else { + None + } } } @@ -311,12 +426,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone + 'a, - Theme: StyleSheet - + scrollable::Style - + menu::StyleSheet - + container::Style - + 'a, - ::Style: From<::Style>, + Theme: scrollable::Style + container::Style + 'a, Renderer: text::Renderer + 'a, { fn from( @@ -605,190 +715,83 @@ pub fn mouse_interaction( } } -/// Returns the current overlay of a [`PickList`]. -pub fn overlay<'a, T, Message, Theme, Renderer>( - layout: Layout<'_>, - translation: Vector, - state: &'a mut State, - padding: Padding, - text_size: Option, - text_shaping: text::Shaping, - font: Renderer::Font, - options: &'a [T], - on_selected: &'a dyn Fn(T) -> Message, - style: ::Style, -) -> Option> -where - T: Clone + ToString, - Message: 'a, - Theme: StyleSheet - + scrollable::Style - + menu::StyleSheet - + container::Style - + 'a, - ::Style: From<::Style>, - Renderer: text::Renderer + 'a, -{ - if state.is_open { - let bounds = layout.bounds(); +/// The possible status of a [`PickList`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`PickList`] can be interacted with. + Active, + /// The [`PickList`] is being hovered. + Hovered, + /// The [`PickList`] is open. + Opened, +} - let mut menu = Menu::new( - &mut state.menu, - options, - &mut state.hovered_option, - |option| { - state.is_open = false; +/// The appearance of a pick list. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The text [`Color`] of the pick list. + pub text_color: Color, + /// The placeholder [`Color`] of the pick list. + pub placeholder_color: Color, + /// The handle [`Color`] of the pick list. + pub handle_color: Color, + /// The [`Background`] of the pick list. + pub background: Background, + /// The [`Border`] of the pick list. + pub border: Border, +} - (on_selected)(option) - }, - None, - ) - .width(bounds.width) - .padding(padding) - .font(font) - .text_shaping(text_shaping) - .style(style); - - if let Some(text_size) = text_size { - menu = menu.text_size(text_size); - } +/// The different styles of a [`PickList`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style { + /// The style of the [`PickList`] itself. + pub pick_list: fn(&Theme, Status) -> Appearance, - Some(menu.overlay(layout.position() + translation, bounds.height)) - } else { - None - } + /// The style of the [`Menu`] of the pick list. + pub menu: menu::Style, } -/// Draws a [`PickList`]. -pub fn draw<'a, T, Theme, Renderer>( - renderer: &mut Renderer, - theme: &Theme, - layout: Layout<'_>, - cursor: mouse::Cursor, - padding: Padding, - text_size: Option, - text_line_height: text::LineHeight, - text_shaping: text::Shaping, - font: Renderer::Font, - placeholder: Option<&str>, - selected: Option<&T>, - handle: &Handle, - style: &Theme::Style, - state: impl FnOnce() -> &'a State, - viewport: &Rectangle, -) where - Renderer: text::Renderer, - Theme: StyleSheet, - T: ToString + 'a, -{ - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - let is_selected = selected.is_some(); +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} - let style = if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; +impl Copy for Style {} - renderer.fill_quad( - renderer::Quad { - bounds, - border: style.border, - ..renderer::Quad::default() - }, - style.background, - ); - - let handle = match handle { - Handle::Arrow { size } => Some(( - Renderer::ICON_FONT, - Renderer::ARROW_DOWN_ICON, - *size, - text::LineHeight::default(), - text::Shaping::Basic, - )), - Handle::Static(Icon { - font, - code_point, - size, - line_height, - shaping, - }) => Some((*font, *code_point, *size, *line_height, *shaping)), - Handle::Dynamic { open, closed } => { - if state().is_open { - Some(( - open.font, - open.code_point, - open.size, - open.line_height, - open.shaping, - )) - } else { - Some(( - closed.font, - closed.code_point, - closed.size, - closed.line_height, - closed.shaping, - )) - } +impl Default for Style { + fn default() -> Self { + Self { + pick_list: default, + menu: menu::Style::default(), } - Handle::None => None, - }; - - if let Some((font, code_point, size, line_height, shaping)) = handle { - let size = size.unwrap_or_else(|| renderer.default_size()); - - renderer.fill_text( - Text { - content: &code_point.to_string(), - size, - line_height, - font, - bounds: Size::new( - bounds.width, - f32::from(line_height.to_absolute(size)), - ), - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - shaping, - }, - Point::new( - bounds.x + bounds.width - padding.horizontal(), - bounds.center_y(), - ), - style.handle_color, - *viewport, - ); } +} - let label = selected.map(ToString::to_string); - - if let Some(label) = label.as_deref().or(placeholder) { - let text_size = text_size.unwrap_or_else(|| renderer.default_size()); +/// The default style of a [`PickList`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let active = Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + handle_color: palette.background.weak.text, + border: Border { + radius: 2.0.into(), + width: 1.0, + color: palette.background.strong.color, + }, + }; - renderer.fill_text( - Text { - content: label, - size: text_size, - line_height: text_line_height, - font, - bounds: Size::new( - bounds.width - padding.horizontal(), - f32::from(text_line_height.to_absolute(text_size)), - ), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text_shaping, - }, - Point::new(bounds.x + padding.left, bounds.center_y()), - if is_selected { - style.text_color - } else { - style.placeholder_color + match status { + Status::Active => active, + Status::Hovered | Status::Opened => Appearance { + border: Border { + color: palette.primary.strong.color, + ..active.border }, - *viewport, - ); + ..active + }, } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 864fbec8..9772855e 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1665,7 +1665,8 @@ impl Style for Theme { } } -fn default(theme: &Theme, status: Status) -> Appearance { +/// The default style of a [`Scrollable`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); let scrollbar = Scrollbar { diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 11df391e..956383da 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -56,7 +56,7 @@ where gap: 0.0, padding: Self::DEFAULT_PADDING, snap_within_viewport: true, - style: Theme::default(), + style: Theme::style(), } } -- cgit From 8a63774b24488f71147a728123551ae72c080d14 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 17:26:07 +0100 Subject: Try `Style` newtype instead of trait for `Svg` widget --- widget/src/helpers.rs | 2 +- widget/src/svg.rs | 36 ++++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'widget') diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index da9a5792..f63306c4 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -366,7 +366,7 @@ pub fn image(handle: impl Into) -> crate::Image { #[cfg(feature = "svg")] pub fn svg(handle: impl Into) -> crate::Svg where - Theme: crate::svg::Style, + crate::svg::Style: Default, { crate::Svg::new(handle) } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index a0402288..8ac5a1cf 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -25,21 +25,21 @@ pub struct Svg { width: Length, height: Length, content_fit: ContentFit, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl Svg { /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into) -> Self where - Theme: Style, + Style: Default, { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, - style: Theme::style(), + style: Style::default(), } } @@ -48,7 +48,7 @@ impl Svg { #[must_use] pub fn from_path(path: impl Into) -> Self where - Theme: Style, + Style: Default, { Self::new(Handle::from_path(path)) } @@ -163,7 +163,7 @@ where Status::Idle }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); renderer.draw( self.handle.clone(), @@ -213,14 +213,26 @@ pub struct Appearance { pub color: Option, } -/// The definiton of the default style of an [`Svg`]. -pub trait Style { - /// Returns the default style of an [`Svg`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of an [`Svg`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(|_, _| Appearance::default()) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - |_, _| Appearance::default() +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } -- cgit From 34e7c6593a9e0f56cee5db18b7258717cf6bc11b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 20:30:58 +0100 Subject: Use `Style` struct pattern instead of trait for all widgets --- widget/src/button.rs | 44 ++++++++++++++++--------- widget/src/checkbox.rs | 40 +++++++++++++++-------- widget/src/combo_box.rs | 15 ++++----- widget/src/container.rs | 67 ++++++++++++++++++++++++++++----------- widget/src/helpers.rs | 51 +++++++++++++++-------------- widget/src/overlay/menu.rs | 36 +++++++++++---------- widget/src/pane_grid.rs | 36 ++++++++++++++------- widget/src/pane_grid/content.rs | 12 +++---- widget/src/pane_grid/title_bar.rs | 15 +++++---- widget/src/pick_list.rs | 5 +-- widget/src/progress_bar.rs | 36 ++++++++++++++------- widget/src/qr_code.rs | 36 ++++++++++++++------- widget/src/radio.rs | 36 ++++++++++++++------- widget/src/rule.rs | 40 +++++++++++++++-------- widget/src/scrollable.rs | 47 +++++++++++++++++++-------- widget/src/slider.rs | 36 ++++++++++++++------- widget/src/svg.rs | 2 +- widget/src/text_editor.rs | 36 ++++++++++++++------- widget/src/text_input.rs | 53 +++++++++++++++++++++---------- widget/src/toggler.rs | 34 +++++++++++++------- widget/src/tooltip.rs | 12 +++---- widget/src/vertical_slider.rs | 13 +++----- 22 files changed, 449 insertions(+), 253 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 798a8206..b5786baa 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -53,7 +53,6 @@ use crate::style::Theme; #[allow(missing_debug_implementations)] pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> where - Theme: Style, Renderer: crate::core::Renderer, { content: Element<'a, Message, Theme, Renderer>, @@ -62,18 +61,20 @@ where height: Length, padding: Padding, clip: bool, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> where - Theme: Style, Renderer: crate::core::Renderer, { /// Creates a new [`Button`] with the given content. pub fn new( content: impl Into>, - ) -> Self { + ) -> Self + where + Style: Default, + { let content = content.into(); let size = content.as_widget().size_hint(); @@ -84,7 +85,7 @@ where height: size.height.fluid(), padding: Padding::new(5.0), clip: false, - style: Theme::DEFAULT, + style: Style::default(), } } @@ -125,7 +126,7 @@ where /// Sets the style variant of this [`Button`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style.into(); + self.style = Style(style); self } @@ -146,7 +147,6 @@ impl<'a, Message, Theme, Renderer> Widget for Button<'a, Message, Theme, Renderer> where Message: 'a + Clone, - Theme: Style, Renderer: 'a + crate::core::Renderer, { fn tag(&self) -> tree::Tag { @@ -306,7 +306,7 @@ where Status::Active }; - let styling = (self.style)(theme, status); + let styling = (self.style.0)(theme, status); if styling.background.is_some() || styling.border.width > 0.0 @@ -380,7 +380,7 @@ impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: Clone + 'a, - Theme: Style + 'a, + Theme: 'a, Renderer: crate::core::Renderer + 'a, { fn from(button: Button<'a, Message, Theme, Renderer>) -> Self { @@ -428,14 +428,28 @@ impl std::default::Default for Appearance { } } -/// The default style of a [`Button`] for a given theme. -pub trait Style { - /// The default style. - const DEFAULT: fn(&Self, Status) -> Appearance; +/// The style of a [`Button`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(primary) + } } -impl Style for Theme { - const DEFAULT: fn(&Self, Status) -> Appearance = primary; +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) + } } /// A primary button; denoting a main action. diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 91838291..e4fc2232 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -53,13 +53,12 @@ pub struct Checkbox< text_shaping: text::Shaping, font: Option, icon: Icon, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> where Renderer: text::Renderer, - Theme: Style, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 15.0; @@ -72,7 +71,10 @@ where /// It expects: /// * the label of the [`Checkbox`] /// * a boolean describing whether the [`Checkbox`] is checked or not - pub fn new(label: impl Into, is_checked: bool) -> Self { + pub fn new(label: impl Into, is_checked: bool) -> Self + where + Style: Default, + { Checkbox { is_checked, on_toggle: None, @@ -91,7 +93,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Theme::style(), + style: Style::default(), } } @@ -175,7 +177,7 @@ where /// Sets the style of the [`Checkbox`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style.into(); + self.style = Style(style); self } } @@ -301,7 +303,7 @@ where Status::Active { is_checked } }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); { let layout = children.next().unwrap(); @@ -423,15 +425,27 @@ pub struct Appearance { pub text_color: Option, } -/// A set of rules that dictate the style of a checkbox. -pub trait Style { - /// The supported style of the [`StyleSheet`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`Checkbox`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(primary) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - primary +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 533daab2..1140f1c6 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -1,5 +1,4 @@ //! Display a dropdown list of searchable and selectable options. -use crate::container; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; @@ -14,7 +13,6 @@ use crate::core::{ Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Vector, }; use crate::overlay::menu; -use crate::scrollable; use crate::style::Theme; use crate::text::LineHeight; use crate::text_input::{self, TextInput}; @@ -65,14 +63,16 @@ where on_selected: impl Fn(T) -> Message + 'static, ) -> Self where - Theme: text_input::Style, Style: Default, { let style = Style::::default(); - let text_input = TextInput::new(placeholder, &state.value()) - .on_input(TextInputEvent::TextChanged) - .style(style.text_input); + let text_input = TextInput::with_style( + placeholder, + &state.value(), + style.text_input, + ) + .on_input(TextInputEvent::TextChanged); let selection = selection.map(T::to_string).unwrap_or_default(); @@ -294,7 +294,6 @@ impl<'a, T, Message, Theme, Renderer> Widget where T: Display + Clone + 'static, Message: Clone, - Theme: scrollable::Style + container::Style, Renderer: text::Renderer, { fn size(&self) -> Size { @@ -711,7 +710,7 @@ impl<'a, T, Message, Theme, Renderer> where T: Display + Clone + 'static, Message: Clone + 'a, - Theme: scrollable::Style + container::Style + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self { diff --git a/widget/src/container.rs b/widget/src/container.rs index 58a24339..97b481a2 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -34,21 +34,30 @@ pub struct Container< max_height: f32, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - style: fn(&Theme, Status) -> Appearance, clip: bool, content: Element<'a, Message, Theme, Renderer>, + style: Style, } impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { - /// Creates an empty [`Container`]. - pub fn new(content: T) -> Self + /// Creates a [`Container`] with the given content. + pub fn new( + content: impl Into>, + ) -> Self where - Theme: Style, - T: Into>, + Style: Default, { + Self::with_style(content, Style::default().0) + } + + /// Creates a [`Container`] with the given content and style. + pub fn with_style( + content: impl Into>, + style: fn(&Theme, Status) -> Appearance, + ) -> Self { let content = content.into(); let size = content.as_widget().size_hint(); @@ -61,9 +70,9 @@ where max_height: f32::INFINITY, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, - style: Theme::style(), clip: false, content, + style: Style(style), } } @@ -129,7 +138,7 @@ where /// Sets the style of the [`Container`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style; + self.style = Style(style); self } @@ -267,7 +276,7 @@ where Status::Idle }; - let style = (self.style)(theme, status); + let style = (self.style.0)(theme, status); if let Some(clipped_viewport) = bounds.intersection(viewport) { draw_background(renderer, &style, bounds); @@ -537,26 +546,46 @@ pub enum Status { Hovered, } -/// The style of a [`Container`] for a theme. -pub trait Style { - /// The default style of a [`Container`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`Container`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Style { + /// Resolves the [`Style`] with the given `Theme` and [`Status`] to + /// produce an [`Appearance`]. + pub fn resolve(self, theme: &Theme, status: Status) -> Appearance { + (self.0)(theme, status) + } +} + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(transparent) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - transparent +impl Default for Style { + fn default() -> Self { + Style(|appearance, _status| *appearance) } } -impl Style for Appearance { - fn style() -> fn(&Self, Status) -> Appearance { - |appearance, _status| *appearance +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } /// A transparent [`Container`]. -pub fn transparent(_theme: &Theme, _status: Status) -> Appearance { +pub fn transparent(_theme: &Theme, _status: Status) -> Appearance { Appearance::default() } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index f63306c4..fdc9462d 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -14,7 +14,7 @@ use crate::rule::{self, Rule}; use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; -use crate::text::{self, Text}; +use crate::text::Text; use crate::text_editor::{self, TextEditor}; use crate::text_input::{self, TextInput}; use crate::toggler::{self, Toggler}; @@ -58,8 +58,8 @@ pub fn container<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Container<'a, Message, Theme, Renderer> where - Theme: container::Style, Renderer: core::Renderer, + container::Style: Default, { Container::new(content) } @@ -104,8 +104,8 @@ pub fn scrollable<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Scrollable<'a, Message, Theme, Renderer> where - Theme: scrollable::Style, Renderer: core::Renderer, + scrollable::Style: Default, { Scrollable::new(content) } @@ -118,7 +118,7 @@ pub fn button<'a, Message, Theme, Renderer>( ) -> Button<'a, Message, Theme, Renderer> where Renderer: core::Renderer, - Theme: button::Style, + button::Style: Default, { Button::new(content) } @@ -134,8 +134,8 @@ pub fn tooltip<'a, Message, Theme, Renderer>( position: tooltip::Position, ) -> crate::Tooltip<'a, Message, Theme, Renderer> where - Theme: container::Style + text::StyleSheet, Renderer: core::text::Renderer, + container::Style: Default, { Tooltip::new(content, tooltip, position) } @@ -147,7 +147,6 @@ pub fn text<'a, Theme, Renderer>( text: impl ToString, ) -> Text<'a, Theme, Renderer> where - Theme: text::StyleSheet, Renderer: core::text::Renderer, { Text::new(text.to_string()) @@ -161,8 +160,8 @@ pub fn checkbox<'a, Message, Theme, Renderer>( is_checked: bool, ) -> Checkbox<'a, Message, Theme, Renderer> where - Theme: checkbox::Style, Renderer: core::text::Renderer, + checkbox::Style: Default, { Checkbox::new(label, is_checked) } @@ -178,9 +177,9 @@ pub fn radio( ) -> Radio where Message: Clone, - Theme: radio::Style, Renderer: core::text::Renderer, V: Copy + Eq, + radio::Style: Default, { Radio::new(label, value, selected, on_click) } @@ -195,7 +194,7 @@ pub fn toggler<'a, Message, Theme, Renderer>( ) -> Toggler<'a, Message, Theme, Renderer> where Renderer: core::text::Renderer, - Theme: toggler::Style, + toggler::Style: Default, { Toggler::new(label, is_checked, f) } @@ -209,8 +208,8 @@ pub fn text_input<'a, Message, Theme, Renderer>( ) -> TextInput<'a, Message, Theme, Renderer> where Message: Clone, - Theme: text_input::Style, Renderer: core::text::Renderer, + text_input::Style: Default, { TextInput::new(placeholder, value) } @@ -223,8 +222,8 @@ pub fn text_editor( ) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Theme, Renderer> where Message: Clone, - Theme: text_editor::Style, Renderer: core::text::Renderer, + text_editor::Style: Default, { TextEditor::new(content) } @@ -240,7 +239,7 @@ pub fn slider<'a, T, Message, Theme>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Theme: slider::Style, + slider::Style: Default, { Slider::new(range, value, on_change) } @@ -256,7 +255,7 @@ pub fn vertical_slider<'a, T, Message, Theme>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Theme: vertical_slider::Style, + vertical_slider::Style: Default, { VerticalSlider::new(range, value, on_change) } @@ -291,7 +290,6 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>( ) -> ComboBox<'a, T, Message, Theme, Renderer> where T: std::fmt::Display + Clone, - Theme: text_input::Style, Renderer: core::text::Renderer, combo_box::Style: Default, { @@ -319,7 +317,7 @@ pub fn vertical_space() -> Space { /// [`Rule`]: crate::Rule pub fn horizontal_rule(height: impl Into) -> Rule where - Theme: rule::Style, + rule::Style: Default, { Rule::horizontal(height) } @@ -329,7 +327,7 @@ where /// [`Rule`]: crate::Rule pub fn vertical_rule(width: impl Into) -> Rule where - Theme: rule::Style, + rule::Style: Default, { Rule::vertical(width) } @@ -346,7 +344,7 @@ pub fn progress_bar( value: f32, ) -> ProgressBar where - Theme: progress_bar::Style, + progress_bar::Style: Default, { ProgressBar::new(range, value) } @@ -392,7 +390,7 @@ where #[cfg(feature = "qr_code")] pub fn qr_code(data: &crate::qr_code::Data) -> crate::QRCode<'_, Theme> where - Theme: crate::qr_code::Style, + crate::qr_code::Style: Default, { crate::QRCode::new(data) } @@ -435,13 +433,20 @@ where } /// A widget that applies any `Theme` to its contents. -pub fn themer<'a, Message, OldTheme, NewTheme, F, Renderer>( - to_theme: F, +pub fn themer<'a, Message, OldTheme, NewTheme, Renderer>( + new_theme: NewTheme, content: impl Into>, -) -> Themer<'a, Message, OldTheme, NewTheme, F, Renderer> +) -> Themer< + 'a, + Message, + OldTheme, + NewTheme, + impl Fn(&OldTheme) -> NewTheme, + Renderer, +> where - F: Fn(&OldTheme) -> NewTheme, Renderer: core::Renderer, + NewTheme: Clone, { - Themer::new(to_theme, content) + Themer::new(move |_| new_theme.clone(), content) } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index bb8ad0e0..e0887e59 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -46,7 +46,7 @@ impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer> where T: ToString + Clone, Message: 'a, - Theme: container::Style + scrollable::Style + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { /// Creates a new [`Menu`] with the given [`State`], a list of options, and @@ -197,7 +197,7 @@ where impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> where Message: 'a, - Theme: container::Style + scrollable::Style + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { pub fn new( @@ -223,20 +223,24 @@ where style, } = menu; - let container = Container::new( - Scrollable::new(List { - options, - hovered_option, - on_selected, - on_option_hovered, - font, - text_size, - text_line_height, - text_shaping, - padding, - style: style.menu, - }) - .style(style.scrollable), + let container = Container::with_style( + Scrollable::with_direction_and_style( + List { + options, + hovered_option, + on_selected, + on_option_hovered, + font, + text_size, + text_line_height, + text_shaping, + padding, + style: style.menu, + }, + scrollable::Direction::default(), + style.scrollable, + ), + container::transparent, ); state.tree.diff(&container as &dyn Widget<_, _, _>); diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 62067e66..ae9cd825 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -112,7 +112,7 @@ pub struct PaneGrid< on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(f32, Box Message + 'a>)>, - style: fn(&Theme) -> Appearance, + style: Style, } impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> @@ -128,7 +128,7 @@ where view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, ) -> Self where - Theme: Style, + Style: Default, { let contents = if let Some((pane, pane_state)) = state.maximized.and_then(|pane| { @@ -160,7 +160,7 @@ where on_click: None, on_drag: None, on_resize: None, - style: Theme::style(), + style: Style::default(), } } @@ -221,7 +221,7 @@ where /// Sets the style of the [`PaneGrid`]. pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { - self.style = style; + self.style = Style(style); self } @@ -679,7 +679,7 @@ where None }; - let appearance = (self.style)(theme); + let appearance = (self.style.0)(theme); for ((id, (content, tree)), pane_layout) in contents.zip(layout.children()) @@ -1147,15 +1147,27 @@ pub struct Line { pub width: f32, } -/// The definiton of the default style of a [`PaneGrid`]. -pub trait Style { - /// Returns the default style of a [`PaneGrid`]. - fn style() -> fn(&Self) -> Appearance; +/// The style of a [`PaneGrid`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 25b64e17..ce29e8d0 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -24,7 +24,7 @@ pub struct Content< { title_bar: Option>, body: Element<'a, Message, Theme, Renderer>, - style: fn(&Theme, container::Status) -> container::Appearance, + style: container::Style, } impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer> @@ -34,12 +34,12 @@ where /// Creates a new [`Content`] with the provided body. pub fn new(body: impl Into>) -> Self where - Theme: container::Style, + container::Style: Default, { Self { title_bar: None, body: body.into(), - style: Theme::style(), + style: container::Style::default(), } } @@ -57,7 +57,7 @@ where mut self, style: fn(&Theme, container::Status) -> container::Appearance, ) -> Self { - self.style = style; + self.style = style.into(); self } } @@ -114,7 +114,7 @@ where container::Status::Idle }; - (self.style)(theme, status) + self.style.resolve(theme, status) }; container::draw_background(renderer, &style, bounds); @@ -403,8 +403,8 @@ impl<'a, T, Message, Theme, Renderer> From for Content<'a, Message, Theme, Renderer> where T: Into>, - Theme: container::Style, Renderer: crate::core::Renderer, + container::Style: Default, { fn from(element: T) -> Self { Self::new(element) diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 787510cc..b1cdcde3 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -25,7 +25,7 @@ pub struct TitleBar< controls: Option>, padding: Padding, always_show_controls: bool, - style: fn(&Theme, container::Status) -> container::Appearance, + style: container::Style, } impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> @@ -33,17 +33,18 @@ where Renderer: crate::core::Renderer, { /// Creates a new [`TitleBar`] with the given content. - pub fn new(content: E) -> Self + pub fn new( + content: impl Into>, + ) -> Self where - Theme: container::Style, - E: Into>, + container::Style: Default, { Self { content: content.into(), controls: None, padding: Padding::ZERO, always_show_controls: false, - style: Theme::style(), + style: container::Style::default(), } } @@ -67,7 +68,7 @@ where mut self, style: fn(&Theme, container::Status) -> container::Appearance, ) -> Self { - self.style = style; + self.style = style.into(); self } @@ -137,7 +138,7 @@ where container::Status::Idle }; - (self.style)(theme, status) + self.style.resolve(theme, status) }; let inherited_style = renderer::Style { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 546bf294..49daa89c 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -1,5 +1,4 @@ //! Display a dropdown list of selectable values. -use crate::container; use crate::core::alignment; use crate::core::event::{self, Event}; use crate::core::keyboard; @@ -15,7 +14,6 @@ use crate::core::{ Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; -use crate::scrollable; use crate::style::Theme; use std::borrow::Borrow; @@ -169,7 +167,6 @@ where L: Borrow<[T]>, V: Borrow, Message: Clone + 'a, - Theme: scrollable::Style + container::Style, Renderer: text::Renderer + 'a, { fn tag(&self) -> tree::Tag { @@ -426,7 +423,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone + 'a, - Theme: scrollable::Style + container::Style + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { fn from( diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 889c5558..62d319f4 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -28,7 +28,7 @@ pub struct ProgressBar { value: f32, width: Length, height: Option, - style: fn(&Theme) -> Appearance, + style: Style, } impl ProgressBar { @@ -42,14 +42,14 @@ impl ProgressBar { /// * the current value of the [`ProgressBar`] pub fn new(range: RangeInclusive, value: f32) -> Self where - Theme: Style, + Style: Default, { ProgressBar { value: value.clamp(*range.start(), *range.end()), range, width: Length::Fill, height: None, - style: Theme::style(), + style: Style::default(), } } @@ -67,7 +67,7 @@ impl ProgressBar { /// Sets the style of the [`ProgressBar`]. pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { - self.style = style; + self.style = style.into(); self } } @@ -117,7 +117,7 @@ where / (range_end - range_start) }; - let appearance = (self.style)(theme); + let appearance = (self.style.0)(theme); renderer.fill_quad( renderer::Quad { @@ -169,15 +169,27 @@ pub struct Appearance { pub border: Border, } -/// The definiton of the default style of a [`ProgressBar`]. -pub trait Style { - /// Returns the default style of a [`ProgressBar`]. - fn style() -> fn(&Self) -> Appearance; +/// The style of a [`ProgressBar`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(primary) + } } -impl Style for Theme { - fn style() -> fn(&Self) -> Appearance { - primary +impl From Appearance> for Style { + fn from(f: fn(&Theme) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index f13c9102..b94e95f6 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -23,19 +23,19 @@ const QUIET_ZONE: usize = 2; pub struct QRCode<'a, Theme = crate::Theme> { data: &'a Data, cell_size: u16, - style: fn(&Theme) -> Appearance, + style: Style, } impl<'a, Theme> QRCode<'a, Theme> { /// Creates a new [`QRCode`] with the provided [`Data`]. pub fn new(data: &'a Data) -> Self where - Theme: Style, + Style: Default, { Self { data, cell_size: DEFAULT_CELL_SIZE, - style: Theme::style(), + style: Style::default(), } } @@ -47,7 +47,7 @@ impl<'a, Theme> QRCode<'a, Theme> { /// Sets the style of the [`QRCode`]. pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { - self.style = style; + self.style = style.into(); self } } @@ -97,7 +97,7 @@ impl<'a, Message, Theme> Widget let bounds = layout.bounds(); let side_length = self.data.width + 2 * QUIET_ZONE; - let appearance = (self.style)(theme); + let appearance = (self.style.0)(theme); let mut last_appearance = state.last_appearance.borrow_mut(); if Some(appearance) != *last_appearance { @@ -335,15 +335,27 @@ pub struct Appearance { pub background: Color, } -/// The definiton of the default style of a [`QRCode`]. -pub trait Style { - /// Returns the default style of a [`QRCode`]. - fn style() -> fn(&Self) -> Appearance; +/// The style of a [`QRCode`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 90a10a0b..83d17f01 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -82,7 +82,7 @@ where text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl Radio @@ -111,7 +111,7 @@ where f: F, ) -> Self where - Theme: Style, + Style: Default, V: Eq + Copy, F: FnOnce(V) -> Message, { @@ -126,7 +126,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - style: Theme::style(), + style: Style::default(), } } @@ -177,7 +177,7 @@ where /// Sets the style of the [`Radio`] button. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style.into(); + self.style = Style(style); self } } @@ -298,7 +298,7 @@ where Status::Active { is_selected } }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); { let layout = children.next().unwrap(); @@ -398,15 +398,27 @@ pub struct Appearance { pub text_color: Option, } -/// The definiton of the default style of a [`Radio`] button. -pub trait Style { - /// Returns the default style of a [`Radio`] button. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`Radio`] button. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 1a1ba106..53a077aa 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -15,39 +15,39 @@ pub struct Rule { width: Length, height: Length, is_horizontal: bool, - style: fn(&Theme) -> Appearance, + style: Style, } impl Rule { /// Creates a horizontal [`Rule`] with the given height. pub fn horizontal(height: impl Into) -> Self where - Theme: Style, + Style: Default, { Rule { width: Length::Fill, height: Length::Fixed(height.into().0), is_horizontal: true, - style: Theme::style(), + style: Style::default(), } } /// Creates a vertical [`Rule`] with the given width. pub fn vertical(width: impl Into) -> Self where - Theme: Style, + Style: Default, { Rule { width: Length::Fixed(width.into().0), height: Length::Fill, is_horizontal: false, - style: Theme::style(), + style: Style::default(), } } /// Sets the style of the [`Rule`]. pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { - self.style = style; + self.style = Style(style); self } } @@ -83,7 +83,7 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let appearance = (self.style)(theme); + let appearance = (self.style.0)(theme); let bounds = if self.is_horizontal { let line_y = (bounds.y + (bounds.height / 2.0) @@ -216,15 +216,27 @@ impl FillMode { } } -/// The definiton of the default style of a [`Rule`]. -pub trait Style { - /// Returns the default style of a [`Rule`]. - fn style() -> fn(&Self) -> Appearance; +/// The style of a [`Rule`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 9772855e..19a80ee2 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -37,7 +37,7 @@ pub struct Scrollable< direction: Direction, content: Element<'a, Message, Theme, Renderer>, on_scroll: Option Message + 'a>>, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer> @@ -49,7 +49,7 @@ where content: impl Into>, ) -> Self where - Theme: Style, + Style: Default, { Self::with_direction(content, Direction::default()) } @@ -60,8 +60,17 @@ where direction: Direction, ) -> Self where - Theme: Style, + Style: Default, { + Self::with_direction_and_style(content, direction, Style::default().0) + } + + /// Creates a new [`Scrollable`] with the given [`Direction`] and style. + pub fn with_direction_and_style( + content: impl Into>, + direction: Direction, + style: fn(&Theme, Status) -> Appearance, + ) -> Self { let content = content.into(); debug_assert!( @@ -83,7 +92,7 @@ where direction, content, on_scroll: None, - style: Theme::style(), + style: style.into(), } } @@ -115,7 +124,7 @@ where /// Sets the style of the [`Scrollable`] . pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style; + self.style = style.into(); self } } @@ -399,7 +408,7 @@ where Status::Active }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); container::draw_background( renderer, @@ -1653,15 +1662,27 @@ pub struct Scroller { pub border: Border, } -/// The definition of the default style of a [`Scrollable`]. -pub trait Style { - /// Returns the default style of a [`Scrollable`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`Scrollable`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/slider.rs b/widget/src/slider.rs index a40f8792..c48fe143 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -53,7 +53,7 @@ pub struct Slider<'a, T, Message, Theme = crate::Theme> { on_release: Option, width: Length, height: f32, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> @@ -74,7 +74,7 @@ where /// `Message`. pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self where - Theme: Style, + Style: Default, F: 'a + Fn(T) -> Message, { let value = if value >= *range.start() { @@ -99,7 +99,7 @@ where on_release: None, width: Length::Fill, height: Self::DEFAULT_HEIGHT, - style: Theme::style(), + style: Style::default(), } } @@ -136,7 +136,7 @@ where /// Sets the style of the [`Slider`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style; + self.style = style.into(); self } @@ -350,7 +350,7 @@ where let bounds = layout.bounds(); let is_mouse_over = cursor.is_over(bounds); - let style = (self.style)( + let style = (self.style.0)( theme, if state.is_dragging { Status::Dragged @@ -550,15 +550,27 @@ pub enum HandleShape { }, } -/// The definiton of the default style of a [`TextInput`]. -pub trait Style { - /// Returns the default style of a [`TextInput`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`Slider`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(pub(crate) fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 8ac5a1cf..c80fa6b1 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -81,7 +81,7 @@ impl Svg { /// Sets the style variant of this [`Svg`]. #[must_use] pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style.into(); + self.style = Style(style); self } } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 91670228..73b006fa 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -42,7 +42,7 @@ pub struct TextEditor< width: Length, height: Length, padding: Padding, - style: fn(&Theme, Status) -> Appearance, + style: Style, on_edit: Option Message + 'a>>, highlighter_settings: Highlighter::Settings, highlighter_format: fn( @@ -59,7 +59,7 @@ where /// Creates new [`TextEditor`] with the given [`Content`]. pub fn new(content: &'a Content) -> Self where - Theme: Style, + Style: Default, { Self { content, @@ -69,7 +69,7 @@ where width: Length::Fill, height: Length::Shrink, padding: Padding::new(5.0), - style: Theme::style(), + style: Style::default(), on_edit: None, highlighter_settings: (), highlighter_format: |_highlight, _theme| { @@ -144,7 +144,7 @@ where /// Sets the style of the [`TextEditor`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style; + self.style = style.into(); self } } @@ -506,7 +506,7 @@ where Status::Active }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); renderer.fill_quad( renderer::Quad { @@ -809,15 +809,27 @@ pub struct Appearance { pub selection: Color, } -/// The definiton of the default style of a [`TextInput`]. -pub trait Style { - /// Returns the default style of a [`TextInput`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`TextEditor`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 11b0a5d5..bae84db7 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -77,7 +77,7 @@ pub struct TextInput< on_paste: Option Message + 'a>>, on_submit: Option, icon: Option>, - style: fn(&Theme, Status) -> Appearance, + style: Style, } /// The default [`Padding`] of a [`TextInput`]. @@ -88,15 +88,22 @@ where Message: Clone, Renderer: text::Renderer, { - /// Creates a new [`TextInput`]. - /// - /// It expects: - /// - a placeholder, - /// - the current value + /// Creates a new [`TextInput`] with the given placeholder and + /// its current value. pub fn new(placeholder: &str, value: &str) -> Self where - Theme: Style, + Style: Default, { + Self::with_style(placeholder, value, Style::default().0) + } + + /// Creates a new [`TextInput`] with the given placeholder, + /// its current value, and its style. + pub fn with_style( + placeholder: &str, + value: &str, + style: fn(&Theme, Status) -> Appearance, + ) -> Self { TextInput { id: None, placeholder: String::from(placeholder), @@ -111,7 +118,7 @@ where on_paste: None, on_submit: None, icon: None, - style: Theme::style(), + style: style.into(), } } @@ -199,7 +206,7 @@ where /// Sets the style of the [`TextInput`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = style; + self.style = style.into(); self } @@ -337,7 +344,7 @@ where Status::Active }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); renderer.fill_quad( renderer::Quad { @@ -1406,15 +1413,27 @@ pub struct Appearance { pub selection: Color, } -/// The definiton of the default style of a [`TextInput`]. -pub trait Style { - /// Returns the default style of a [`TextInput`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`TextInput`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 1f19212d..cecd7b6c 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -50,7 +50,7 @@ pub struct Toggler< text_shaping: text::Shaping, spacing: f32, font: Option, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer> @@ -74,7 +74,7 @@ where f: F, ) -> Self where - Theme: Style, + Style: Default, F: 'a + Fn(bool) -> Message, { Toggler { @@ -89,7 +89,7 @@ where text_shaping: text::Shaping::Basic, spacing: Self::DEFAULT_SIZE / 2.0, font: None, - style: Theme::style(), + style: Style::default(), } } @@ -301,7 +301,7 @@ where } }; - let appearance = (self.style)(theme, status); + let appearance = (self.style.0)(theme, status); let border_radius = bounds.height / BORDER_RADIUS_RATIO; let space = SPACE_RATIO * bounds.height; @@ -399,15 +399,27 @@ pub struct Appearance { pub foreground_border_color: Color, } -/// The definiton of the default style of a [`Toggler`]. -pub trait Style { - /// Returns the default style of a [`Toggler`]. - fn style() -> fn(&Self, Status) -> Appearance; +/// The style of a [`Toggler`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style(fn(&Theme, Status) -> Appearance); + +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Style {} + +impl Default for Style { + fn default() -> Self { + Style(default) + } } -impl Style for Theme { - fn style() -> fn(&Self, Status) -> Appearance { - default +impl From Appearance> for Style { + fn from(f: fn(&Theme, Status) -> Appearance) -> Self { + Style(f) } } diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 956383da..4e026e8f 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -28,7 +28,7 @@ pub struct Tooltip< gap: f32, padding: f32, snap_within_viewport: bool, - style: fn(&Theme, container::Status) -> container::Appearance, + style: container::Style, } impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer> @@ -47,7 +47,7 @@ where position: Position, ) -> Self where - Theme: container::Style, + container::Style: Default, { Tooltip { content: content.into(), @@ -56,7 +56,7 @@ where gap: 0.0, padding: Self::DEFAULT_PADDING, snap_within_viewport: true, - style: Theme::style(), + style: container::Style::default(), } } @@ -83,7 +83,7 @@ where mut self, style: fn(&Theme, container::Status) -> container::Appearance, ) -> Self { - self.style = style; + self.style = style.into(); self } } @@ -309,7 +309,7 @@ where positioning: Position, gap: f32, padding: f32, - style: fn(&Theme, container::Status) -> container::Appearance, + style: container::Style, } impl<'a, 'b, Message, Theme, Renderer> @@ -424,7 +424,7 @@ where layout: Layout<'_>, cursor_position: mouse::Cursor, ) { - let style = (self.style)(theme, container::Status::Idle); + let style = self.style.resolve(theme, container::Status::Idle); container::draw_background(renderer, &style, layout.bounds()); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index b51aa2bf..47c400c7 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -54,7 +54,7 @@ pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> { on_release: Option, width: f32, height: Length, - style: fn(&Theme, Status) -> Appearance, + style: Style, } impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme> @@ -75,7 +75,7 @@ where /// `Message`. pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self where - Theme: Style, + Style: Default, F: 'a + Fn(T) -> Message, { let value = if value >= *range.start() { @@ -100,7 +100,7 @@ where on_release: None, width: Self::DEFAULT_WIDTH, height: Length::Fill, - style: Theme::style(), + style: Style::default(), } } @@ -136,10 +136,7 @@ where } /// Sets the style of the [`VerticalSlider`]. - pub fn style( - mut self, - style: impl Into Appearance>, - ) -> Self { + pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { self.style = style.into(); self } @@ -357,7 +354,7 @@ where let bounds = layout.bounds(); let is_mouse_over = cursor.is_over(bounds); - let style = (self.style)( + let style = (self.style.0)( theme, if state.is_dragging { Status::Dragged -- cgit From 905f2160e6eb7504f52d9bd62c7bfa42c8ec2902 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 00:14:41 +0100 Subject: Move `Theme` type to `iced_core` --- widget/Cargo.toml | 1 - widget/src/button.rs | 11 ++++------- widget/src/checkbox.rs | 8 +++----- widget/src/combo_box.rs | 3 +-- widget/src/container.rs | 3 +-- widget/src/lib.rs | 3 +-- widget/src/overlay/menu.rs | 3 +-- widget/src/pane_grid.rs | 6 ++---- widget/src/pick_list.rs | 3 +-- widget/src/progress_bar.rs | 5 ++--- widget/src/qr_code.rs | 4 ++-- widget/src/radio.rs | 5 ++--- widget/src/rule.rs | 3 +-- widget/src/scrollable.rs | 3 +-- widget/src/slider.rs | 6 ++---- widget/src/svg.rs | 4 ++-- widget/src/text_editor.rs | 3 +-- widget/src/text_input.rs | 6 ++---- widget/src/toggler.rs | 6 ++---- widget/src/vertical_slider.rs | 3 +-- 20 files changed, 32 insertions(+), 57 deletions(-) (limited to 'widget') diff --git a/widget/Cargo.toml b/widget/Cargo.toml index e8e363c4..3c9ffddb 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -25,7 +25,6 @@ wgpu = ["iced_renderer/wgpu"] [dependencies] iced_renderer.workspace = true iced_runtime.workspace = true -iced_style.workspace = true num-traits.workspace = true thiserror.workspace = true diff --git a/widget/src/button.rs b/widget/src/button.rs index b5786baa..d0d3cb4a 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -6,21 +6,19 @@ use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; +use crate::core::theme::palette; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Padding, - Rectangle, Shadow, Shell, Size, Vector, Widget, + Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, }; -use crate::style::theme::palette; -use crate::style::Theme; /// A generic widget that produces a message when pressed. /// /// ```no_run -/// # type Button<'a, Message> = -/// # iced_widget::Button<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>; +/// # type Button<'a, Message> = iced_widget::Button<'a, Message>; /// # /// #[derive(Clone)] /// enum Message { @@ -34,8 +32,7 @@ use crate::style::Theme; /// be disabled: /// /// ``` -/// # type Button<'a, Message> = -/// # iced_widget::Button<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>; +/// # type Button<'a, Message> = iced_widget::Button<'a, Message>; /// # /// #[derive(Clone)] /// enum Message { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index e4fc2232..a297627b 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -5,23 +5,21 @@ use crate::core::layout; use crate::core::mouse; use crate::core::renderer; use crate::core::text; +use crate::core::theme::palette; use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, - Rectangle, Shell, Size, Widget, + Rectangle, Shell, Size, Theme, Widget, }; -use crate::style::theme::palette; -use crate::style::Theme; /// A box that can be checked. /// /// # Example /// /// ```no_run -/// # type Checkbox<'a, Message> = -/// # iced_widget::Checkbox<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>; +/// # type Checkbox<'a, Message> = iced_widget::Checkbox<'a, Message>; /// # /// pub enum Message { /// CheckboxToggled(bool), diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 1140f1c6..62c19137 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -10,10 +10,9 @@ use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; use crate::core::{ - Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Vector, + Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Theme, Vector, }; use crate::overlay::menu; -use crate::style::Theme; use crate::text::LineHeight; use crate::text_input::{self, TextInput}; diff --git a/widget/src/container.rs b/widget/src/container.rs index 97b481a2..99d877fe 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -9,10 +9,9 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Operation}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Padding, - Pixels, Point, Rectangle, Shadow, Shell, Size, Vector, Widget, + Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::Command; -use crate::style::Theme; /// An element decorating some content. /// diff --git a/widget/src/lib.rs b/widget/src/lib.rs index cefafdbe..72596efc 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -14,7 +14,6 @@ pub use iced_renderer as renderer; pub use iced_renderer::graphics; pub use iced_runtime as runtime; pub use iced_runtime::core; -pub use iced_style as style; mod column; mod mouse_area; @@ -135,5 +134,5 @@ pub mod qr_code; #[doc(no_inline)] pub use qr_code::QRCode; +pub use crate::core::theme::{self, Theme}; pub use renderer::Renderer; -pub use style::theme::{self, Theme}; diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index e0887e59..2b9e0d03 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -11,11 +11,10 @@ use crate::core::touch; use crate::core::widget::Tree; use crate::core::{ Background, Border, Clipboard, Color, Length, Padding, Pixels, Point, - Rectangle, Size, Vector, + Rectangle, Size, Theme, Vector, }; use crate::core::{Element, Shell, Widget}; use crate::scrollable::{self, Scrollable}; -use crate::style::Theme; /// A list of selectable options. #[allow(missing_debug_implementations)] diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index ae9cd825..5403b2f5 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -40,9 +40,8 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, + Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; -use crate::style::Theme; const DRAG_DEADBAND_DISTANCE: f32 = 10.0; const THICKNESS_RATIO: f32 = 25.0; @@ -71,8 +70,7 @@ const THICKNESS_RATIO: f32 = 25.0; /// ```no_run /// # use iced_widget::{pane_grid, text}; /// # -/// # type PaneGrid<'a, Message> = -/// # iced_widget::PaneGrid<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>; +/// # type PaneGrid<'a, Message> = iced_widget::PaneGrid<'a, Message>; /// # /// enum PaneState { /// SomePane, diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 49daa89c..649daafe 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -11,10 +11,9 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Padding, - Pixels, Point, Rectangle, Shell, Size, Vector, Widget, + Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; -use crate::style::Theme; use std::borrow::Borrow; diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 62d319f4..b667b506 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -4,9 +4,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::Tree; use crate::core::{ - Background, Border, Element, Layout, Length, Rectangle, Size, Widget, + Background, Border, Element, Layout, Length, Rectangle, Size, Theme, Widget, }; -use crate::style::Theme; use std::ops::RangeInclusive; @@ -14,7 +13,7 @@ use std::ops::RangeInclusive; /// /// # Example /// ```no_run -/// # type ProgressBar = iced_widget::ProgressBar; +/// # type ProgressBar = iced_widget::ProgressBar; /// # /// let value = 50.0; /// diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index b94e95f6..66513775 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -5,10 +5,10 @@ use crate::core::mouse; use crate::core::renderer::{self, Renderer as _}; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, + Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector, + Widget, }; use crate::graphics::geometry::Renderer as _; -use crate::style::Theme; use crate::Renderer; use std::cell::RefCell; diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 83d17f01..556d8ac9 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -10,16 +10,15 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, - Rectangle, Shell, Size, Widget, + Rectangle, Shell, Size, Theme, Widget, }; -use crate::style::Theme; /// A circular button representing a choice. /// /// # Example /// ```no_run /// # type Radio = -/// # iced_widget::Radio; +/// # iced_widget::Radio; /// # /// # use iced_widget::column; /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 53a077aa..19ad43f6 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -5,9 +5,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::Tree; use crate::core::{ - Color, Element, Layout, Length, Pixels, Rectangle, Size, Widget, + Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget, }; -use crate::style::Theme; /// Display a horizontal or vertical rule for dividing content. #[allow(missing_debug_implementations)] diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 19a80ee2..861f1bfb 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -13,10 +13,9 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, + Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::Command; -use crate::style::Theme; pub use operation::scrollable::{AbsoluteOffset, RelativeOffset}; diff --git a/widget/src/slider.rs b/widget/src/slider.rs index c48fe143..463a4f04 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -12,9 +12,8 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Border, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Size, Widget, + Rectangle, Shell, Size, Theme, Widget, }; -use crate::style::Theme; use std::ops::RangeInclusive; @@ -28,8 +27,7 @@ use std::ops::RangeInclusive; /// /// # Example /// ```no_run -/// # type Slider<'a, T, Message> = -/// # iced_widget::Slider<'a, Message, T, iced_widget::style::Theme>; +/// # type Slider<'a, T, Message> = iced_widget::Slider<'a, Message, T>; /// # /// #[derive(Clone)] /// pub enum Message { diff --git a/widget/src/svg.rs b/widget/src/svg.rs index c80fa6b1..34fd9a7b 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -5,9 +5,9 @@ use crate::core::renderer; use crate::core::svg; use crate::core::widget::Tree; use crate::core::{ - Color, ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget, + Color, ContentFit, Element, Layout, Length, Rectangle, Size, Theme, Vector, + Widget, }; -use crate::style::Theme; use std::path::PathBuf; diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 73b006fa..fabcb744 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -12,9 +12,8 @@ use crate::core::text::{self, LineHeight}; use crate::core::widget::{self, Widget}; use crate::core::{ Background, Border, Color, Element, Length, Padding, Pixels, Rectangle, - Shell, Size, Vector, + Shell, Size, Theme, Vector, }; -use crate::style::Theme; use std::cell::RefCell; use std::fmt; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index bae84db7..6bad0afe 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -28,17 +28,15 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, + Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::Command; -use crate::style::Theme; /// A field that can be filled with text. /// /// # Example /// ```no_run -/// # pub type TextInput<'a, Message> = -/// # iced_widget::TextInput<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>; +/// # pub type TextInput<'a, Message> = iced_widget::TextInput<'a, Message>; /// # /// #[derive(Debug, Clone)] /// enum Message { diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index cecd7b6c..adc82f73 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -10,17 +10,15 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Border, Clipboard, Color, Element, Event, Layout, Length, Pixels, - Rectangle, Shell, Size, Widget, + Rectangle, Shell, Size, Theme, Widget, }; -use crate::style::Theme; /// A toggler widget. /// /// # Example /// /// ```no_run -/// # type Toggler<'a, Message> = -/// # iced_widget::Toggler<'a, Message, iced_widget::style::Theme, iced_widget::renderer::Renderer>; +/// # type Toggler<'a, Message> = iced_widget::Toggler<'a, Message>; /// # /// pub enum Message { /// TogglerToggled(bool), diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 47c400c7..721a59fb 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -31,8 +31,7 @@ use crate::core::{ /// /// # Example /// ```no_run -/// # type VerticalSlider<'a, T, Message> = -/// # iced_widget::VerticalSlider<'a, T, Message, iced_widget::style::Theme>; +/// # type VerticalSlider<'a, T, Message> = iced_widget::VerticalSlider<'a, T, Message>; /// # /// #[derive(Clone)] /// pub enum Message { -- cgit From 6785a452eea5f6b6f69bac123789245dacbc936e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 00:19:24 +0100 Subject: Fix broken links in documentation --- widget/src/button.rs | 2 -- widget/src/radio.rs | 2 +- widget/src/slider.rs | 2 -- widget/src/text_editor.rs | 12 ++++++------ widget/src/vertical_slider.rs | 2 -- 5 files changed, 7 insertions(+), 13 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index d0d3cb4a..22dbaec5 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -1,6 +1,4 @@ //! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 556d8ac9..6bb72650 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -367,7 +367,7 @@ where } } -/// The possible status of a [`TextInput`]. +/// The possible status of a [`Radio`] button. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { /// The [`Radio`] button can be interacted with. diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 463a4f04..79850f63 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -1,6 +1,4 @@ //! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. use crate::core::border; use crate::core::event::{self, Event}; use crate::core::keyboard; diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index fabcb744..0212a7a0 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -778,16 +778,16 @@ mod platform { } } -/// The possible status of a [`TextInput`]. +/// The possible status of a [`TextEditor`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { - /// The [`TextInput`] can be interacted with. + /// The [`TextEditor`] can be interacted with. Active, - /// The [`TextInput`] is being hovered. + /// The [`TextEditor`] is being hovered. Hovered, - /// The [`TextInput`] is focused. + /// The [`TextEditor`] is focused. Focused, - /// The [`TextInput`] cannot be interacted with. + /// The [`TextEditor`] cannot be interacted with. Disabled, } @@ -832,7 +832,7 @@ impl From Appearance> for Style { } } -/// The default style of a [`TextInput`]. +/// The default style of a [`TextEditor`]. pub fn default(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 721a59fb..93abe02a 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -1,6 +1,4 @@ //! Display an interactive selector of a single value from a range of values. -//! -//! A [`VerticalSlider`] has some local [`State`]. use std::ops::RangeInclusive; pub use crate::slider::{ -- cgit From 44f002f64a9d53040f09affe69bd92675e302e16 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 15:21:42 +0100 Subject: Rename `positive` and `destructive` to `success` and `danger` in `button` --- widget/src/button.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 22dbaec5..5fa62280 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -477,8 +477,8 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance { } } -/// A positive button; denoting a good outcome. -pub fn positive(theme: &Theme, status: Status) -> Appearance { +/// A success button; denoting a good outcome. +pub fn success(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); let base = styled(palette.success.base); @@ -492,8 +492,8 @@ pub fn positive(theme: &Theme, status: Status) -> Appearance { } } -/// A destructive button; denoting a dangerous action. -pub fn destructive(theme: &Theme, status: Status) -> Appearance { +/// A danger button; denoting a destructive action. +pub fn danger(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); let base = styled(palette.danger.base); -- cgit From 833538ee7f3a60a839304762dfc29b0881d19094 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 20:11:32 +0100 Subject: Leverage `DefaultStyle` traits instead of `Default` --- widget/src/button.rs | 33 ++++++++++++-------------- widget/src/checkbox.rs | 33 ++++++++++++-------------- widget/src/combo_box.rs | 30 ++++++++++++++---------- widget/src/container.rs | 49 +++++++++++++-------------------------- widget/src/helpers.rs | 36 ++++++++++++++-------------- widget/src/overlay/menu.rs | 39 ++++++++++++++++++++----------- widget/src/pane_grid.rs | 33 ++++++++++++-------------- widget/src/pane_grid/content.rs | 8 +++---- widget/src/pane_grid/title_bar.rs | 6 ++--- widget/src/pick_list.rs | 35 ++++++++++++++++++---------- widget/src/progress_bar.rs | 31 +++++++++++-------------- widget/src/qr_code.rs | 31 +++++++++++-------------- widget/src/radio.rs | 33 ++++++++++++-------------- widget/src/rule.rs | 37 ++++++++++++++--------------- widget/src/scrollable.rs | 37 +++++++++++++++-------------- widget/src/slider.rs | 31 +++++++++++-------------- widget/src/svg.rs | 35 +++++++++++++--------------- widget/src/text_editor.rs | 31 +++++++++++-------------- widget/src/text_input.rs | 31 +++++++++++-------------- widget/src/toggler.rs | 31 +++++++++++-------------- widget/src/tooltip.rs | 6 ++--- widget/src/vertical_slider.rs | 8 +++---- 22 files changed, 310 insertions(+), 334 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 5fa62280..f9859353 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -68,7 +68,7 @@ where content: impl Into>, ) -> Self where - Style: Default, + Theme: DefaultStyle, { let content = content.into(); let size = content.as_widget().size_hint(); @@ -80,7 +80,7 @@ where height: size.height.fluid(), padding: Padding::new(5.0), clip: false, - style: Style::default(), + style: Theme::default_style(), } } @@ -121,7 +121,7 @@ where /// Sets the style variant of this [`Button`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } @@ -301,7 +301,7 @@ where Status::Active }; - let styling = (self.style.0)(theme, status); + let styling = (self.style)(theme, status); if styling.background.is_some() || styling.border.width > 0.0 @@ -424,26 +424,23 @@ impl std::default::Default for Appearance { } /// The style of a [`Button`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Button`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Button`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(primary) +impl DefaultStyle for Theme { + fn default_style() -> Style { + primary } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index a297627b..c837ab3f 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -71,7 +71,7 @@ where /// * a boolean describing whether the [`Checkbox`] is checked or not pub fn new(label: impl Into, is_checked: bool) -> Self where - Style: Default, + Theme: DefaultStyle, { Checkbox { is_checked, @@ -91,7 +91,7 @@ where line_height: text::LineHeight::default(), shaping: text::Shaping::Basic, }, - style: Style::default(), + style: Theme::default_style(), } } @@ -175,7 +175,7 @@ where /// Sets the style of the [`Checkbox`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } } @@ -301,7 +301,7 @@ where Status::Active { is_checked } }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); { let layout = children.next().unwrap(); @@ -424,26 +424,23 @@ pub struct Appearance { } /// The style of a [`Checkbox`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Checkbox`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Checkbox`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(primary) +impl DefaultStyle for Theme { + fn default_style() -> Style { + primary } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 62c19137..44830d8a 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -62,9 +62,9 @@ where on_selected: impl Fn(T) -> Message + 'static, ) -> Self where - Style: Default, + Theme: DefaultStyle, { - let style = Style::::default(); + let style = Theme::default_style(); let text_input = TextInput::with_style( placeholder, @@ -762,7 +762,7 @@ where .collect() } -/// The appearance of a [`ComboBox`]. +/// The style of a [`ComboBox`]. #[derive(Debug, PartialEq, Eq)] pub struct Style { /// The style of the [`TextInput`] of the [`ComboBox`]. @@ -772,6 +772,14 @@ pub struct Style { menu: menu::Style, } +impl Style { + /// The default style of a [`ComboBox`]. + pub const DEFAULT: Self = Self { + text_input: text_input::default, + menu: menu::Style::::DEFAULT, + }; +} + impl Clone for Style { fn clone(&self) -> Self { *self @@ -780,16 +788,14 @@ impl Clone for Style { impl Copy for Style {} -impl Default for Style { - fn default() -> Self { - default() - } +/// The default style of a [`ComboBox`]. +pub trait DefaultStyle: Sized { + /// Returns the default style of a [`ComboBox`]. + fn default_style() -> Style; } -/// The default style of a [`ComboBox`]. -pub fn default() -> Style { - Style { - text_input: text_input::default, - menu: menu::Style::default(), +impl DefaultStyle for Theme { + fn default_style() -> Style { + Style::::DEFAULT } } diff --git a/widget/src/container.rs b/widget/src/container.rs index 99d877fe..5e16312c 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -47,9 +47,9 @@ where content: impl Into>, ) -> Self where - Style: Default, + Theme: DefaultStyle, { - Self::with_style(content, Style::default().0) + Self::with_style(content, Theme::default_style()) } /// Creates a [`Container`] with the given content and style. @@ -71,7 +71,7 @@ where vertical_alignment: alignment::Vertical::Top, clip: false, content, - style: Style(style), + style, } } @@ -137,7 +137,7 @@ where /// Sets the style of the [`Container`]. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } @@ -275,7 +275,7 @@ where Status::Idle }; - let style = (self.style.0)(theme, status); + let style = (self.style)(theme, status); if let Some(clipped_viewport) = bounds.intersection(viewport) { draw_background(renderer, &style, bounds); @@ -546,40 +546,23 @@ pub enum Status { } /// The style of a [`Container`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Style { - /// Resolves the [`Style`] with the given `Theme` and [`Status`] to - /// produce an [`Appearance`]. - pub fn resolve(self, theme: &Theme, status: Status) -> Appearance { - (self.0)(theme, status) - } -} - -impl Clone for Style { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(transparent) - } +/// The default style of a [`Container`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Container`]. + fn default_style() -> Style; } -impl Default for Style { - fn default() -> Self { - Style(|appearance, _status| *appearance) +impl DefaultStyle for Theme { + fn default_style() -> Style { + transparent } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index fdc9462d..75072d2e 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -58,8 +58,8 @@ pub fn container<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Container<'a, Message, Theme, Renderer> where + Theme: container::DefaultStyle, Renderer: core::Renderer, - container::Style: Default, { Container::new(content) } @@ -104,8 +104,8 @@ pub fn scrollable<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Scrollable<'a, Message, Theme, Renderer> where + Theme: scrollable::DefaultStyle, Renderer: core::Renderer, - scrollable::Style: Default, { Scrollable::new(content) } @@ -117,8 +117,8 @@ pub fn button<'a, Message, Theme, Renderer>( content: impl Into>, ) -> Button<'a, Message, Theme, Renderer> where + Theme: button::DefaultStyle, Renderer: core::Renderer, - button::Style: Default, { Button::new(content) } @@ -134,8 +134,8 @@ pub fn tooltip<'a, Message, Theme, Renderer>( position: tooltip::Position, ) -> crate::Tooltip<'a, Message, Theme, Renderer> where + Theme: container::DefaultStyle, Renderer: core::text::Renderer, - container::Style: Default, { Tooltip::new(content, tooltip, position) } @@ -160,8 +160,8 @@ pub fn checkbox<'a, Message, Theme, Renderer>( is_checked: bool, ) -> Checkbox<'a, Message, Theme, Renderer> where + Theme: checkbox::DefaultStyle, Renderer: core::text::Renderer, - checkbox::Style: Default, { Checkbox::new(label, is_checked) } @@ -177,9 +177,9 @@ pub fn radio( ) -> Radio where Message: Clone, + Theme: radio::DefaultStyle, Renderer: core::text::Renderer, V: Copy + Eq, - radio::Style: Default, { Radio::new(label, value, selected, on_click) } @@ -193,8 +193,8 @@ pub fn toggler<'a, Message, Theme, Renderer>( f: impl Fn(bool) -> Message + 'a, ) -> Toggler<'a, Message, Theme, Renderer> where + Theme: toggler::DefaultStyle, Renderer: core::text::Renderer, - toggler::Style: Default, { Toggler::new(label, is_checked, f) } @@ -208,8 +208,8 @@ pub fn text_input<'a, Message, Theme, Renderer>( ) -> TextInput<'a, Message, Theme, Renderer> where Message: Clone, + Theme: text_input::DefaultStyle, Renderer: core::text::Renderer, - text_input::Style: Default, { TextInput::new(placeholder, value) } @@ -222,8 +222,8 @@ pub fn text_editor( ) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Theme, Renderer> where Message: Clone, + Theme: text_editor::DefaultStyle, Renderer: core::text::Renderer, - text_editor::Style: Default, { TextEditor::new(content) } @@ -239,7 +239,7 @@ pub fn slider<'a, T, Message, Theme>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - slider::Style: Default, + Theme: slider::DefaultStyle, { Slider::new(range, value, on_change) } @@ -255,7 +255,7 @@ pub fn vertical_slider<'a, T, Message, Theme>( where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - vertical_slider::Style: Default, + Theme: vertical_slider::DefaultStyle, { VerticalSlider::new(range, value, on_change) } @@ -273,8 +273,8 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone, + Theme: pick_list::DefaultStyle, Renderer: core::text::Renderer, - pick_list::Style: Default, { PickList::new(options, selected, on_selected) } @@ -290,8 +290,8 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>( ) -> ComboBox<'a, T, Message, Theme, Renderer> where T: std::fmt::Display + Clone, + Theme: combo_box::DefaultStyle, Renderer: core::text::Renderer, - combo_box::Style: Default, { ComboBox::new(state, placeholder, selection, on_selected) } @@ -317,7 +317,7 @@ pub fn vertical_space() -> Space { /// [`Rule`]: crate::Rule pub fn horizontal_rule(height: impl Into) -> Rule where - rule::Style: Default, + Theme: rule::DefaultStyle, { Rule::horizontal(height) } @@ -327,7 +327,7 @@ where /// [`Rule`]: crate::Rule pub fn vertical_rule(width: impl Into) -> Rule where - rule::Style: Default, + Theme: rule::DefaultStyle, { Rule::vertical(width) } @@ -344,7 +344,7 @@ pub fn progress_bar( value: f32, ) -> ProgressBar where - progress_bar::Style: Default, + Theme: progress_bar::DefaultStyle, { ProgressBar::new(range, value) } @@ -364,7 +364,7 @@ pub fn image(handle: impl Into) -> crate::Image { #[cfg(feature = "svg")] pub fn svg(handle: impl Into) -> crate::Svg where - crate::svg::Style: Default, + Theme: crate::svg::DefaultStyle, { crate::Svg::new(handle) } @@ -390,7 +390,7 @@ where #[cfg(feature = "qr_code")] pub fn qr_code(data: &crate::qr_code::Data) -> crate::QRCode<'_, Theme> where - crate::qr_code::Style: Default, + Theme: crate::qr_code::DefaultStyle, { crate::QRCode::new(data) } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 2b9e0d03..3ed26b7d 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -58,7 +58,7 @@ where on_option_hovered: Option<&'a dyn Fn(T) -> Message>, ) -> Self where - Style: Default, + Theme: DefaultStyle, { Self::with_style( state, @@ -66,7 +66,7 @@ where hovered_option, on_selected, on_option_hovered, - Style::default(), + Theme::default_style(), ) } @@ -234,7 +234,7 @@ where text_line_height, text_shaping, padding, - style: style.menu, + style: style.list, }, scrollable::Direction::default(), style.scrollable, @@ -327,7 +327,7 @@ where ) { let bounds = layout.bounds(); - let appearance = (self.style.menu)(theme); + let appearance = (self.style.list)(theme); renderer.fill_quad( renderer::Quad { @@ -598,15 +598,23 @@ pub struct Appearance { pub selected_background: Background, } -/// The definiton of the default style of a [`Menu`]. +/// The style of the different parts of a [`Menu`]. #[derive(Debug, PartialEq, Eq)] pub struct Style { - /// The style of the [`Menu`]. - menu: fn(&Theme) -> Appearance, + /// The style of the list of the [`Menu`]. + list: fn(&Theme) -> Appearance, /// The style of the [`Scrollable`] of the [`Menu`]. scrollable: fn(&Theme, scrollable::Status) -> scrollable::Appearance, } +impl Style { + /// The default style of a [`Menu`] with the built-in [`Theme`]. + pub const DEFAULT: Self = Self { + list: default, + scrollable: scrollable::default, + }; +} + impl Clone for Style { fn clone(&self) -> Self { *self @@ -615,16 +623,19 @@ impl Clone for Style { impl Copy for Style {} -impl Default for Style { - fn default() -> Self { - Self { - menu: default, - scrollable: scrollable::default, - } +/// The default style of a [`Menu`]. +pub trait DefaultStyle: Sized { + /// Returns the default style of a [`Menu`]. + fn default_style() -> Style; +} + +impl DefaultStyle for Theme { + fn default_style() -> Style { + Style::::DEFAULT } } -/// The default style of a [`Menu`]. +/// The default style of the list of a [`Menu`]. pub fn default(theme: &Theme) -> Appearance { let palette = theme.extended_palette(); diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 5403b2f5..d60d5e3b 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -126,7 +126,7 @@ where view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, ) -> Self where - Style: Default, + Theme: DefaultStyle, { let contents = if let Some((pane, pane_state)) = state.maximized.and_then(|pane| { @@ -158,7 +158,7 @@ where on_click: None, on_drag: None, on_resize: None, - style: Style::default(), + style: Theme::default_style(), } } @@ -219,7 +219,7 @@ where /// Sets the style of the [`PaneGrid`]. pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } @@ -677,7 +677,7 @@ where None }; - let appearance = (self.style.0)(theme); + let appearance = (self.style)(theme); for ((id, (content, tree)), pane_layout) in contents.zip(layout.children()) @@ -1146,26 +1146,23 @@ pub struct Line { } /// The style of a [`PaneGrid`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme) -> Appearance); +pub type Style = fn(&Theme) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`PaneGrid`]. +pub trait DefaultStyle { + /// Returns the default style of a [`PaneGrid`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance| *appearance } } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index ce29e8d0..aecec777 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -34,12 +34,12 @@ where /// Creates a new [`Content`] with the provided body. pub fn new(body: impl Into>) -> Self where - container::Style: Default, + Theme: container::DefaultStyle, { Self { title_bar: None, body: body.into(), - style: container::Style::default(), + style: Theme::default_style(), } } @@ -114,7 +114,7 @@ where container::Status::Idle }; - self.style.resolve(theme, status) + (self.style)(theme, status) }; container::draw_background(renderer, &style, bounds); @@ -403,8 +403,8 @@ impl<'a, T, Message, Theme, Renderer> From for Content<'a, Message, Theme, Renderer> where T: Into>, + Theme: container::DefaultStyle, Renderer: crate::core::Renderer, - container::Style: Default, { fn from(element: T) -> Self { Self::new(element) diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index b1cdcde3..37f0f160 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -37,14 +37,14 @@ where content: impl Into>, ) -> Self where - container::Style: Default, + Theme: container::DefaultStyle, { Self { content: content.into(), controls: None, padding: Padding::ZERO, always_show_controls: false, - style: container::Style::default(), + style: Theme::default_style(), } } @@ -138,7 +138,7 @@ where container::Status::Idle }; - self.style.resolve(theme, status) + (self.style)(theme, status) }; let inherited_style = renderer::Style { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 649daafe..cfeabbb7 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -69,7 +69,7 @@ where on_select: impl Fn(T) -> Message + 'a, ) -> Self where - Style: Default, + Theme: DefaultStyle, { Self { on_select: Box::new(on_select), @@ -85,7 +85,7 @@ where text_shaping: text::Shaping::Basic, font: None, handle: Handle::default(), - style: Style::default(), + style: Theme::default_style(), } } @@ -266,7 +266,7 @@ where Status::Active }; - let appearance = (self.style.pick_list)(theme, status); + let appearance = (self.style.field)(theme, status); renderer.fill_quad( renderer::Quad { @@ -737,16 +737,24 @@ pub struct Appearance { pub border: Border, } -/// The different styles of a [`PickList`]. +/// The styles of the different parts of a [`PickList`]. #[derive(Debug, PartialEq, Eq)] pub struct Style { /// The style of the [`PickList`] itself. - pub pick_list: fn(&Theme, Status) -> Appearance, + pub field: fn(&Theme, Status) -> Appearance, /// The style of the [`Menu`] of the pick list. pub menu: menu::Style, } +impl Style { + /// The default style of a [`PickList`] with the built-in [`Theme`]. + pub const DEFAULT: Self = Self { + field: default, + menu: menu::Style::::DEFAULT, + }; +} + impl Clone for Style { fn clone(&self) -> Self { *self @@ -755,16 +763,19 @@ impl Clone for Style { impl Copy for Style {} -impl Default for Style { - fn default() -> Self { - Self { - pick_list: default, - menu: menu::Style::default(), - } +/// The default style of a [`PickList`]. +pub trait DefaultStyle: Sized { + /// Returns the default style of a [`PickList`]. + fn default_style() -> Style; +} + +impl DefaultStyle for Theme { + fn default_style() -> Style { + Style::::DEFAULT } } -/// The default style of a [`PickList`]. +/// The default style of the field of a [`PickList`]. pub fn default(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index b667b506..f945a7b5 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -41,14 +41,14 @@ impl ProgressBar { /// * the current value of the [`ProgressBar`] pub fn new(range: RangeInclusive, value: f32) -> Self where - Style: Default, + Theme: DefaultStyle, { ProgressBar { value: value.clamp(*range.start(), *range.end()), range, width: Length::Fill, height: None, - style: Style::default(), + style: Theme::default_style(), } } @@ -116,7 +116,7 @@ where / (range_end - range_start) }; - let appearance = (self.style.0)(theme); + let appearance = (self.style)(theme); renderer.fill_quad( renderer::Quad { @@ -169,26 +169,23 @@ pub struct Appearance { } /// The style of a [`ProgressBar`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme) -> Appearance); +pub type Style = fn(&Theme) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`ProgressBar`]. +pub trait DefaultStyle { + /// Returns the default style of a [`ProgressBar`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(primary) +impl DefaultStyle for Theme { + fn default_style() -> Style { + primary } } -impl From Appearance> for Style { - fn from(f: fn(&Theme) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance| *appearance } } diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 66513775..41bcb83e 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -30,12 +30,12 @@ impl<'a, Theme> QRCode<'a, Theme> { /// Creates a new [`QRCode`] with the provided [`Data`]. pub fn new(data: &'a Data) -> Self where - Style: Default, + Theme: DefaultStyle, { Self { data, cell_size: DEFAULT_CELL_SIZE, - style: Style::default(), + style: Theme::default_style(), } } @@ -97,7 +97,7 @@ impl<'a, Message, Theme> Widget let bounds = layout.bounds(); let side_length = self.data.width + 2 * QUIET_ZONE; - let appearance = (self.style.0)(theme); + let appearance = (self.style)(theme); let mut last_appearance = state.last_appearance.borrow_mut(); if Some(appearance) != *last_appearance { @@ -336,26 +336,23 @@ pub struct Appearance { } /// The style of a [`QRCode`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme) -> Appearance); +pub type Style = fn(&Theme) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`QRCode`]. +pub trait DefaultStyle { + /// Returns the default style of a [`QRCode`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance| *appearance } } diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 6bb72650..e8f1eb1f 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -110,7 +110,7 @@ where f: F, ) -> Self where - Style: Default, + Theme: DefaultStyle, V: Eq + Copy, F: FnOnce(V) -> Message, { @@ -125,7 +125,7 @@ where text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, - style: Style::default(), + style: Theme::default_style(), } } @@ -176,7 +176,7 @@ where /// Sets the style of the [`Radio`] button. pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } } @@ -297,7 +297,7 @@ where Status::Active { is_selected } }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); { let layout = children.next().unwrap(); @@ -398,26 +398,23 @@ pub struct Appearance { } /// The style of a [`Radio`] button. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Radio`] button. +pub trait DefaultStyle { + /// Returns the default style of a [`Radio`] button. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 19ad43f6..384baed4 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -21,32 +21,32 @@ impl Rule { /// Creates a horizontal [`Rule`] with the given height. pub fn horizontal(height: impl Into) -> Self where - Style: Default, + Theme: DefaultStyle, { Rule { width: Length::Fill, height: Length::Fixed(height.into().0), is_horizontal: true, - style: Style::default(), + style: Theme::default_style(), } } /// Creates a vertical [`Rule`] with the given width. pub fn vertical(width: impl Into) -> Self where - Style: Default, + Theme: DefaultStyle, { Rule { width: Length::Fixed(width.into().0), height: Length::Fill, is_horizontal: false, - style: Style::default(), + style: Theme::default_style(), } } /// Sets the style of the [`Rule`]. pub fn style(mut self, style: fn(&Theme) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } } @@ -82,7 +82,7 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let appearance = (self.style.0)(theme); + let appearance = (self.style)(theme); let bounds = if self.is_horizontal { let line_y = (bounds.y + (bounds.height / 2.0) @@ -216,26 +216,23 @@ impl FillMode { } /// The style of a [`Rule`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme) -> Appearance); +pub type Style = fn(&Theme) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Rule`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Rule`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance| *appearance } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 861f1bfb..8d2b2057 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -48,7 +48,7 @@ where content: impl Into>, ) -> Self where - Style: Default, + Theme: DefaultStyle, { Self::with_direction(content, Direction::default()) } @@ -59,9 +59,13 @@ where direction: Direction, ) -> Self where - Style: Default, + Theme: DefaultStyle, { - Self::with_direction_and_style(content, direction, Style::default().0) + Self::with_direction_and_style( + content, + direction, + Theme::default_style(), + ) } /// Creates a new [`Scrollable`] with the given [`Direction`] and style. @@ -407,7 +411,7 @@ where Status::Active }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); container::draw_background( renderer, @@ -1662,26 +1666,23 @@ pub struct Scroller { } /// The style of a [`Scrollable`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Scrollable`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Scrollable`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 79850f63..6449b18e 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -70,7 +70,7 @@ where /// `Message`. pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self where - Style: Default, + Theme: DefaultStyle, F: 'a + Fn(T) -> Message, { let value = if value >= *range.start() { @@ -95,7 +95,7 @@ where on_release: None, width: Length::Fill, height: Self::DEFAULT_HEIGHT, - style: Style::default(), + style: Theme::default_style(), } } @@ -346,7 +346,7 @@ where let bounds = layout.bounds(); let is_mouse_over = cursor.is_over(bounds); - let style = (self.style.0)( + let style = (self.style)( theme, if state.is_dragging { Status::Dragged @@ -547,26 +547,23 @@ pub enum HandleShape { } /// The style of a [`Slider`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(pub(crate) fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Slider`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Slider`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 34fd9a7b..6e61d27a 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -32,14 +32,14 @@ impl Svg { /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into) -> Self where - Style: Default, + Theme: DefaultStyle, { Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, content_fit: ContentFit::Contain, - style: Style::default(), + style: Theme::default_style(), } } @@ -48,7 +48,7 @@ impl Svg { #[must_use] pub fn from_path(path: impl Into) -> Self where - Style: Default, + Theme: DefaultStyle, { Self::new(Handle::from_path(path)) } @@ -81,7 +81,7 @@ impl Svg { /// Sets the style variant of this [`Svg`]. #[must_use] pub fn style(mut self, style: fn(&Theme, Status) -> Appearance) -> Self { - self.style = Style(style); + self.style = style; self } } @@ -163,7 +163,7 @@ where Status::Idle }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); renderer.draw( self.handle.clone(), @@ -214,25 +214,22 @@ pub struct Appearance { } /// The style of an [`Svg`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of an [`Svg`]. +pub trait DefaultStyle { + /// Returns the default style of an [`Svg`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(|_, _| Appearance::default()) +impl DefaultStyle for Theme { + fn default_style() -> Style { + |_theme, _status| Appearance::default() } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 0212a7a0..018ffd9c 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -58,7 +58,7 @@ where /// Creates new [`TextEditor`] with the given [`Content`]. pub fn new(content: &'a Content) -> Self where - Style: Default, + Theme: DefaultStyle, { Self { content, @@ -68,7 +68,7 @@ where width: Length::Fill, height: Length::Shrink, padding: Padding::new(5.0), - style: Style::default(), + style: Theme::default_style(), on_edit: None, highlighter_settings: (), highlighter_format: |_highlight, _theme| { @@ -505,7 +505,7 @@ where Status::Active }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); renderer.fill_quad( renderer::Quad { @@ -809,26 +809,23 @@ pub struct Appearance { } /// The style of a [`TextEditor`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`TextEditor`]. +pub trait DefaultStyle { + /// Returns the default style of a [`TextEditor`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 6bad0afe..449524fc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -90,9 +90,9 @@ where /// its current value. pub fn new(placeholder: &str, value: &str) -> Self where - Style: Default, + Theme: DefaultStyle, { - Self::with_style(placeholder, value, Style::default().0) + Self::with_style(placeholder, value, Theme::default_style()) } /// Creates a new [`TextInput`] with the given placeholder, @@ -342,7 +342,7 @@ where Status::Active }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); renderer.fill_quad( renderer::Quad { @@ -1412,26 +1412,23 @@ pub struct Appearance { } /// The style of a [`TextInput`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`TextInput`]. +pub trait DefaultStyle { + /// Returns the default style of a [`TextInput`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index adc82f73..6b540f1c 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -72,7 +72,7 @@ where f: F, ) -> Self where - Style: Default, + Theme: DefaultStyle, F: 'a + Fn(bool) -> Message, { Toggler { @@ -87,7 +87,7 @@ where text_shaping: text::Shaping::Basic, spacing: Self::DEFAULT_SIZE / 2.0, font: None, - style: Style::default(), + style: Theme::default_style(), } } @@ -299,7 +299,7 @@ where } }; - let appearance = (self.style.0)(theme, status); + let appearance = (self.style)(theme, status); let border_radius = bounds.height / BORDER_RADIUS_RATIO; let space = SPACE_RATIO * bounds.height; @@ -398,26 +398,23 @@ pub struct Appearance { } /// The style of a [`Toggler`]. -#[derive(Debug, PartialEq, Eq)] -pub struct Style(fn(&Theme, Status) -> Appearance); +pub type Style = fn(&Theme, Status) -> Appearance; -impl Clone for Style { - fn clone(&self) -> Self { - *self - } +/// The default style of a [`Toggler`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Toggler`]. + fn default_style() -> Style; } -impl Copy for Style {} - -impl Default for Style { - fn default() -> Self { - Style(default) +impl DefaultStyle for Theme { + fn default_style() -> Style { + default } } -impl From Appearance> for Style { - fn from(f: fn(&Theme, Status) -> Appearance) -> Self { - Style(f) +impl DefaultStyle for Appearance { + fn default_style() -> Style { + |appearance, _status| *appearance } } diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 4e026e8f..8c8ee983 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -47,7 +47,7 @@ where position: Position, ) -> Self where - container::Style: Default, + Theme: container::DefaultStyle, { Tooltip { content: content.into(), @@ -56,7 +56,7 @@ where gap: 0.0, padding: Self::DEFAULT_PADDING, snap_within_viewport: true, - style: container::Style::default(), + style: Theme::default_style(), } } @@ -424,7 +424,7 @@ where layout: Layout<'_>, cursor_position: mouse::Cursor, ) { - let style = self.style.resolve(theme, container::Status::Idle); + let style = (self.style)(theme, container::Status::Idle); container::draw_background(renderer, &style, layout.bounds()); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 93abe02a..103c3b7d 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -2,7 +2,7 @@ use std::ops::RangeInclusive; pub use crate::slider::{ - default, Appearance, Handle, HandleShape, Status, Style, + default, Appearance, DefaultStyle, Handle, HandleShape, Status, Style, }; use crate::core; @@ -72,7 +72,7 @@ where /// `Message`. pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self where - Style: Default, + Theme: DefaultStyle, F: 'a + Fn(T) -> Message, { let value = if value >= *range.start() { @@ -97,7 +97,7 @@ where on_release: None, width: Self::DEFAULT_WIDTH, height: Length::Fill, - style: Style::default(), + style: Theme::default_style(), } } @@ -351,7 +351,7 @@ where let bounds = layout.bounds(); let is_mouse_over = cursor.is_over(bounds); - let style = (self.style.0)( + let style = (self.style)( theme, if state.is_dragging { Status::Dragged -- cgit From e11776055dd6c79298c17e00a86ba43aba917bba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 20:13:29 +0100 Subject: Make fields of `Style` structs public --- widget/src/combo_box.rs | 4 ++-- widget/src/overlay/menu.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'widget') diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 44830d8a..933a0fac 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -766,10 +766,10 @@ where #[derive(Debug, PartialEq, Eq)] pub struct Style { /// The style of the [`TextInput`] of the [`ComboBox`]. - text_input: fn(&Theme, text_input::Status) -> text_input::Appearance, + pub text_input: fn(&Theme, text_input::Status) -> text_input::Appearance, /// The style of the [`Menu`] of the [`ComboBox`]. - menu: menu::Style, + pub menu: menu::Style, } impl Style { diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 3ed26b7d..e855fc14 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -602,9 +602,9 @@ pub struct Appearance { #[derive(Debug, PartialEq, Eq)] pub struct Style { /// The style of the list of the [`Menu`]. - list: fn(&Theme) -> Appearance, + pub list: fn(&Theme) -> Appearance, /// The style of the [`Scrollable`] of the [`Menu`]. - scrollable: fn(&Theme, scrollable::Status) -> scrollable::Appearance, + pub scrollable: fn(&Theme, scrollable::Status) -> scrollable::Appearance, } impl Style { -- cgit From 34ca5386b52ae56d7373ce1af7933cf9aa104b08 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 20:15:49 +0100 Subject: Implement `with_background` for `button::Appearance` --- widget/src/button.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index f9859353..bfda8fe3 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -411,6 +411,16 @@ pub struct Appearance { pub shadow: Shadow, } +impl Appearance { + /// Creates an [`Appearance`] with the given [`Background`]. + pub fn with_background(background: impl Into) -> Self { + Self { + background: Some(background.into()), + ..Self::default() + } + } +} + impl std::default::Default for Appearance { fn default() -> Self { Self { -- cgit From b8f05eb8dd0394e308385796c229cfc5bc4f3a73 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 20:16:07 +0100 Subject: Implement `button::DefaultStyle` for `Color` --- widget/src/button.rs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index bfda8fe3..12716acd 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -454,6 +454,12 @@ impl DefaultStyle for Appearance { } } +impl DefaultStyle for Color { + fn default_style() -> Style { + |color, _status| Appearance::with_background(*color) + } +} + /// A primary button; denoting a main action. pub fn primary(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); -- cgit From 7ece5eea509f3595432babfc7729701f2e063b21 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 21:02:17 +0100 Subject: Implement additional helpers for `Border` and `container::Appearance` --- widget/src/button.rs | 2 +- widget/src/container.rs | 26 ++++++++++++++++++++++---- widget/src/overlay/menu.rs | 2 +- widget/src/progress_bar.rs | 4 ++-- widget/src/radio.rs | 2 +- widget/src/rule.rs | 2 +- widget/src/scrollable.rs | 4 ++-- widget/src/slider.rs | 4 ++-- widget/src/vertical_slider.rs | 4 ++-- 9 files changed, 34 insertions(+), 16 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 12716acd..9ce856bb 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -543,7 +543,7 @@ fn styled(pair: palette::Pair) -> Appearance { Appearance { background: Some(Background::Color(pair.color)), text_color: pair.text, - border: Border::with_radius(2), + border: Border::rounded(2), ..Appearance::default() } } diff --git a/widget/src/container.rs b/widget/src/container.rs index 5e16312c..81b9a29e 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -1,6 +1,7 @@ //! Decorate content and apply alignment. use crate::core::alignment::{self, Alignment}; use crate::core::event::{self, Event}; +use crate::core::gradient::{self, Gradient}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; @@ -510,8 +511,7 @@ pub struct Appearance { } impl Appearance { - /// Derives a new [`Appearance`] with a border of the given [`Color`] and - /// `width`. + /// Updates the border of the [`Appearance`] with the given [`Color`] and `width`. pub fn with_border( self, color: impl Into, @@ -527,7 +527,7 @@ impl Appearance { } } - /// Derives a new [`Appearance`] with the given [`Background`]. + /// Updates the background of the [`Appearance`]. pub fn with_background(self, background: impl Into) -> Self { Self { background: Some(background.into()), @@ -566,6 +566,24 @@ impl DefaultStyle for Appearance { } } +impl DefaultStyle for Color { + fn default_style() -> Style { + |color, _status| Appearance::default().with_background(*color) + } +} + +impl DefaultStyle for Gradient { + fn default_style() -> Style { + |gradient, _status| Appearance::default().with_background(*gradient) + } +} + +impl DefaultStyle for gradient::Linear { + fn default_style() -> Style { + |gradient, _status| Appearance::default().with_background(*gradient) + } +} + /// A transparent [`Container`]. pub fn transparent(_theme: &Theme, _status: Status) -> Appearance { Appearance::default() @@ -577,7 +595,7 @@ pub fn box_(theme: &Theme, _status: Status) -> Appearance { Appearance { background: Some(palette.background.weak.color.into()), - border: Border::with_radius(2), + border: Border::rounded(2), ..Appearance::default() } } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index e855fc14..746407c6 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -539,7 +539,7 @@ where width: bounds.width - appearance.border.width * 2.0, ..bounds }, - border: Border::with_radius(appearance.border.radius), + border: Border::rounded(appearance.border.radius), ..renderer::Quad::default() }, appearance.selected_background, diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index f945a7b5..7b0ea63f 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -134,7 +134,7 @@ where width: active_progress_width, ..bounds }, - border: Border::with_radius(appearance.border.radius), + border: Border::rounded(appearance.border.radius), ..renderer::Quad::default() }, appearance.bar, @@ -230,6 +230,6 @@ fn styled( Appearance { background: background.into(), bar: bar.into(), - border: Border::with_radius(2), + border: Border::rounded(2), } } diff --git a/widget/src/radio.rs b/widget/src/radio.rs index e8f1eb1f..34c3b3a0 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -328,7 +328,7 @@ where width: bounds.width - dot_size, height: bounds.height - dot_size, }, - border: Border::with_radius(dot_size / 2.0), + border: Border::rounded(dot_size / 2.0), ..renderer::Quad::default() }, appearance.dot_color, diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 384baed4..8580d4c7 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -118,7 +118,7 @@ where renderer.fill_quad( renderer::Quad { bounds, - border: Border::with_radius(appearance.radius), + border: Border::rounded(appearance.radius), ..renderer::Quad::default() }, appearance.color, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 8d2b2057..6cd2048f 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1692,10 +1692,10 @@ pub fn default(theme: &Theme, status: Status) -> Appearance { let scrollbar = Scrollbar { background: Some(palette.background.weak.color.into()), - border: Border::with_radius(2), + border: Border::rounded(2), scroller: Scroller { color: palette.background.strong.color, - border: Border::with_radius(2), + border: Border::rounded(2), }, }; diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 6449b18e..c37607a8 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -392,7 +392,7 @@ where width: offset + handle_width / 2.0, height: style.rail.width, }, - border: Border::with_radius(style.rail.border_radius), + border: Border::rounded(style.rail.border_radius), ..renderer::Quad::default() }, style.rail.colors.0, @@ -406,7 +406,7 @@ where width: bounds.width - offset - handle_width / 2.0, height: style.rail.width, }, - border: Border::with_radius(style.rail.border_radius), + border: Border::rounded(style.rail.border_radius), ..renderer::Quad::default() }, style.rail.colors.1, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 103c3b7d..67a9f4e6 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -397,7 +397,7 @@ where width: style.rail.width, height: offset + handle_width / 2.0, }, - border: Border::with_radius(style.rail.border_radius), + border: Border::rounded(style.rail.border_radius), ..renderer::Quad::default() }, style.rail.colors.1, @@ -411,7 +411,7 @@ where width: style.rail.width, height: bounds.height - offset - handle_width / 2.0, }, - border: Border::with_radius(style.rail.border_radius), + border: Border::rounded(style.rail.border_radius), ..renderer::Quad::default() }, style.rail.colors.0, -- cgit From 1f46fd871b04ba738e3817d03131bf5ce46f5e46 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 21:13:23 +0100 Subject: Fix consistency of `with_background` for `button::Appearance` --- widget/src/button.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 9ce856bb..4563f3f4 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -397,7 +397,7 @@ pub enum Status { } /// The appearance of a button. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Appearance { /// The amount of offset to apply to the shadow of the button. pub shadow_offset: Vector, @@ -412,11 +412,11 @@ pub struct Appearance { } impl Appearance { - /// Creates an [`Appearance`] with the given [`Background`]. - pub fn with_background(background: impl Into) -> Self { + /// Updates the [`Appearance`] with the given [`Background`]. + pub fn with_background(self, background: impl Into) -> Self { Self { background: Some(background.into()), - ..Self::default() + ..self } } } @@ -456,7 +456,7 @@ impl DefaultStyle for Appearance { impl DefaultStyle for Color { fn default_style() -> Style { - |color, _status| Appearance::with_background(*color) + |color, _status| Appearance::default().with_background(*color) } } -- cgit From 8fe7f9e4354fb46144b6dc459f1205fc87616259 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 23:39:19 +0100 Subject: Remove obsolete `shadow_offset` field from `button::Appearance` --- widget/src/button.rs | 3 --- 1 file changed, 3 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 4563f3f4..579ad434 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -399,8 +399,6 @@ pub enum Status { /// The appearance of a button. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Appearance { - /// The amount of offset to apply to the shadow of the button. - pub shadow_offset: Vector, /// The [`Background`] of the button. pub background: Option, /// The text [`Color`] of the button. @@ -424,7 +422,6 @@ impl Appearance { impl std::default::Default for Appearance { fn default() -> Self { Self { - shadow_offset: Vector::default(), background: None, text_color: Color::BLACK, border: Border::default(), -- cgit From 00e7035622fc2cd0abcdb3670c4d1a898213fe42 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:01:51 +0100 Subject: Reduce default `Rail::width` of `Slider` widget --- widget/src/slider.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'widget') diff --git a/widget/src/slider.rs b/widget/src/slider.rs index c37607a8..5c965af8 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -580,8 +580,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance { Appearance { rail: Rail { colors: (color, palette.secondary.base.color), - width: 6.0, - border_radius: 3.0.into(), + width: 4.0, + border_radius: 2.0.into(), }, handle: Handle { shape: HandleShape::Circle { radius: 7.0 }, -- cgit From 1b96868e4836b0532ad5c2c0bdb85d1d6fd0698f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:24:49 +0100 Subject: Improve default padding of `Button` widget --- widget/src/button.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 579ad434..a23a7156 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -63,6 +63,14 @@ impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { + /// The default [`Padding`] of a [`Button`]. + pub const DEFAULT_PADDING: Padding = Padding { + top: 5.0, + bottom: 5.0, + right: 10.0, + left: 10.0, + }; + /// Creates a new [`Button`] with the given content. pub fn new( content: impl Into>, @@ -78,7 +86,7 @@ where on_press: None, width: size.width.fluid(), height: size.height.fluid(), - padding: Padding::new(5.0), + padding: Self::DEFAULT_PADDING, clip: false, style: Theme::default_style(), } -- cgit From c99e5996478ee74e5328ef5aaa1d350fcc06933b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:25:42 +0100 Subject: Make `Checkbox`, `Radio`, and `Toggler` default sizes consistent --- widget/src/checkbox.rs | 4 ++-- widget/src/radio.rs | 4 ++-- widget/src/toggler.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'widget') diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index c837ab3f..f0c7357b 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -59,10 +59,10 @@ where Renderer: text::Renderer, { /// The default size of a [`Checkbox`]. - const DEFAULT_SIZE: f32 = 15.0; + const DEFAULT_SIZE: f32 = 16.0; /// The default spacing of a [`Checkbox`]. - const DEFAULT_SPACING: f32 = 10.0; + const DEFAULT_SPACING: f32 = 8.0; /// Creates a new [`Checkbox`]. /// diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 34c3b3a0..5e4a3c1f 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -90,10 +90,10 @@ where Renderer: text::Renderer, { /// The default size of a [`Radio`] button. - pub const DEFAULT_SIZE: f32 = 15.0; + pub const DEFAULT_SIZE: f32 = 16.0; /// The default spacing of a [`Radio`] button. - pub const DEFAULT_SPACING: f32 = 10.0; + pub const DEFAULT_SPACING: f32 = 8.0; /// Creates a new [`Radio`] button. /// diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 6b540f1c..9e81ba33 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -56,7 +56,7 @@ where Renderer: text::Renderer, { /// The default size of a [`Toggler`]. - pub const DEFAULT_SIZE: f32 = 20.0; + pub const DEFAULT_SIZE: f32 = 16.0; /// Creates a new [`Toggler`]. /// -- cgit From 1db823b4c528441627dd075d989fca2fa0a44346 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:36:37 +0100 Subject: Make `PickList` padding consistent with `Button` --- widget/src/button.rs | 18 +++++++++--------- widget/src/pick_list.rs | 5 +---- 2 files changed, 10 insertions(+), 13 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index a23a7156..243eaf04 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -63,14 +63,6 @@ impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { - /// The default [`Padding`] of a [`Button`]. - pub const DEFAULT_PADDING: Padding = Padding { - top: 5.0, - bottom: 5.0, - right: 10.0, - left: 10.0, - }; - /// Creates a new [`Button`] with the given content. pub fn new( content: impl Into>, @@ -86,7 +78,7 @@ where on_press: None, width: size.width.fluid(), height: size.height.fluid(), - padding: Self::DEFAULT_PADDING, + padding: DEFAULT_PADDING, clip: false, style: Theme::default_style(), } @@ -391,6 +383,14 @@ where } } +/// The default [`Padding`] of a [`Button`]. +pub(crate) const DEFAULT_PADDING: Padding = Padding { + top: 5.0, + bottom: 5.0, + right: 10.0, + left: 10.0, +}; + /// The possible status of a [`Button`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index cfeabbb7..d98909fa 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -58,9 +58,6 @@ where Message: Clone, Renderer: text::Renderer, { - /// The default padding of a [`PickList`]. - pub const DEFAULT_PADDING: Padding = Padding::new(5.0); - /// Creates a new [`PickList`] with the given list of options, the current /// selected value, and the message to produce when an option is selected. pub fn new( @@ -79,7 +76,7 @@ where placeholder: None, selected, width: Length::Shrink, - padding: Self::DEFAULT_PADDING, + padding: crate::button::DEFAULT_PADDING, text_size: None, text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, -- cgit From f316755cdcaf632d91a9adbc80e93fda744fd16e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:37:10 +0100 Subject: Fix handle of `PickList` being rendered too much to the left --- widget/src/pick_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'widget') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index d98909fa..b8fc6079 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -329,7 +329,7 @@ where shaping, }, Point::new( - bounds.x + bounds.width - self.padding.horizontal(), + bounds.x + bounds.width - self.padding.right, bounds.center_y(), ), appearance.handle_color, -- cgit From 8a98d1e7974a7709fc2dfc59f08b05a05e537c1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:39:42 +0100 Subject: Make default height of `Slider` consistent with `Checkbox` --- widget/src/slider.rs | 2 +- widget/src/vertical_slider.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'widget') diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 5c965af8..f3ea9bfd 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -58,7 +58,7 @@ where Message: Clone, { /// The default height of a [`Slider`]. - pub const DEFAULT_HEIGHT: f32 = 15.0; + pub const DEFAULT_HEIGHT: f32 = 16.0; /// Creates a new [`Slider`]. /// diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 67a9f4e6..f7030584 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -60,7 +60,7 @@ where Message: Clone, { /// The default width of a [`VerticalSlider`]. - pub const DEFAULT_WIDTH: f32 = 22.0; + pub const DEFAULT_WIDTH: f32 = 16.0; /// Creates a new [`VerticalSlider`]. /// -- cgit From 288025f5143f4e3f8bc5af36e86f7afa7f07a4c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 13:34:36 +0100 Subject: Inline helper functions in `widget` modules --- widget/src/combo_box.rs | 8 +- widget/src/lib.rs | 2 +- widget/src/pick_list.rs | 405 ++++++++++-------------- widget/src/scrollable.rs | 807 ++++++++++++++++++++++------------------------- 4 files changed, 553 insertions(+), 669 deletions(-) (limited to 'widget') diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 933a0fac..bddf2789 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -717,8 +717,7 @@ where } } -/// Search list of options for a given query. -pub fn search<'a, T, A>( +fn search<'a, T, A>( options: impl IntoIterator + 'a, option_matchers: impl IntoIterator + 'a, query: &'a str, @@ -745,8 +744,7 @@ where }) } -/// Build matchers from given list of options. -pub fn build_matchers<'a, T>( +fn build_matchers<'a, T>( options: impl IntoIterator + 'a, ) -> Vec where @@ -769,6 +767,8 @@ pub struct Style { pub text_input: fn(&Theme, text_input::Status) -> text_input::Appearance, /// The style of the [`Menu`] of the [`ComboBox`]. + /// + /// [`Menu`]: menu::Menu pub menu: menu::Style, } diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 72596efc..209dfad9 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -18,6 +18,7 @@ pub use iced_runtime::core; mod column; mod mouse_area; mod row; +mod space; mod themer; pub mod button; @@ -33,7 +34,6 @@ pub mod radio; pub mod rule; pub mod scrollable; pub mod slider; -pub mod space; pub mod text; pub mod text_editor; pub mod text_input; diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index b8fc6079..beb4e0c1 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -16,6 +16,7 @@ use crate::core::{ use crate::overlay::menu::{self, Menu}; use std::borrow::Borrow; +use std::f32; /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] @@ -186,19 +187,77 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - tree.state.downcast_mut::>(), - renderer, - limits, - self.width, - self.padding, - self.text_size, - self.text_line_height, - self.text_shaping, - self.font, - self.placeholder.as_deref(), - self.options.borrow(), - ) + let state = tree.state.downcast_mut::>(); + + let font = self.font.unwrap_or_else(|| renderer.default_font()); + let text_size = + self.text_size.unwrap_or_else(|| renderer.default_size()); + let options = self.options.borrow(); + + state.options.resize_with(options.len(), Default::default); + + let option_text = Text { + content: "", + bounds: Size::new( + f32::INFINITY, + self.text_line_height.to_absolute(text_size).into(), + ), + size: text_size, + line_height: self.text_line_height, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, + }; + + for (option, paragraph) in options.iter().zip(state.options.iter_mut()) + { + let label = option.to_string(); + + paragraph.update(Text { + content: &label, + ..option_text + }); + } + + if let Some(placeholder) = &self.placeholder { + state.placeholder.update(Text { + content: placeholder, + ..option_text + }); + } + + let max_width = match self.width { + Length::Shrink => { + let labels_width = + state.options.iter().fold(0.0, |width, paragraph| { + f32::max(width, paragraph.min_width()) + }); + + labels_width.max( + self.placeholder + .as_ref() + .map(|_| state.placeholder.min_width()) + .unwrap_or(0.0), + ) + } + _ => 0.0, + }; + + let size = { + let intrinsic = Size::new( + max_width + text_size.0 + self.padding.left, + f32::from(self.text_line_height.to_absolute(text_size)), + ); + + limits + .width(self.width) + .shrink(self.padding) + .resolve(self.width, Length::Shrink, intrinsic) + .expand(self.padding) + }; + + layout::Node::new(size) } fn on_event( @@ -212,18 +271,98 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - event, - layout, - cursor, - shell, - self.on_select.as_ref(), - self.on_open.as_ref(), - self.on_close.as_ref(), - self.selected.as_ref().map(Borrow::borrow), - self.options.borrow(), - || tree.state.downcast_mut::>(), - ) + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let state = + tree.state.downcast_mut::>(); + + if state.is_open { + // Event wasn't processed by overlay, so cursor was clicked either outside its + // bounds or on the drop-down, either way we close the overlay. + state.is_open = false; + + if let Some(on_close) = &self.on_close { + shell.publish(on_close.clone()); + } + + event::Status::Captured + } else if cursor.is_over(layout.bounds()) { + let selected = self.selected.as_ref().map(Borrow::borrow); + + state.is_open = true; + state.hovered_option = self + .options + .borrow() + .iter() + .position(|option| Some(option) == selected); + + if let Some(on_open) = &self.on_open { + shell.publish(on_open.clone()); + } + + event::Status::Captured + } else { + event::Status::Ignored + } + } + Event::Mouse(mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Lines { y, .. }, + }) => { + let state = + tree.state.downcast_mut::>(); + + if state.keyboard_modifiers.command() + && cursor.is_over(layout.bounds()) + && !state.is_open + { + fn find_next<'a, T: PartialEq>( + selected: &'a T, + mut options: impl Iterator, + ) -> Option<&'a T> { + let _ = options.find(|&option| option == selected); + + options.next() + } + + let options = self.options.borrow(); + let selected = self.selected.as_ref().map(Borrow::borrow); + + let next_option = if y < 0.0 { + if let Some(selected) = selected { + find_next(selected, options.iter()) + } else { + options.first() + } + } else if y > 0.0 { + if let Some(selected) = selected { + find_next(selected, options.iter().rev()) + } else { + options.last() + } + } else { + None + }; + + if let Some(next_option) = next_option { + shell.publish((self.on_select)(next_option.clone())); + } + + event::Status::Captured + } else { + event::Status::Ignored + } + } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + let state = + tree.state.downcast_mut::>(); + + state.keyboard_modifiers = modifiers; + + event::Status::Ignored + } + _ => event::Status::Ignored, + } } fn mouse_interaction( @@ -234,7 +373,14 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction(layout, cursor) + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } } fn draw( @@ -429,9 +575,8 @@ where } } -/// The state of a [`PickList`]. #[derive(Debug)] -pub struct State { +struct State { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: bool, @@ -504,210 +649,6 @@ pub struct Icon { pub shaping: text::Shaping, } -/// Computes the layout of a [`PickList`]. -pub fn layout( - state: &mut State, - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - padding: Padding, - text_size: Option, - text_line_height: text::LineHeight, - text_shaping: text::Shaping, - font: Option, - placeholder: Option<&str>, - options: &[T], -) -> layout::Node -where - Renderer: text::Renderer, - T: ToString, -{ - use std::f32; - - let font = font.unwrap_or_else(|| renderer.default_font()); - let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - - state.options.resize_with(options.len(), Default::default); - - let option_text = Text { - content: "", - bounds: Size::new( - f32::INFINITY, - text_line_height.to_absolute(text_size).into(), - ), - size: text_size, - line_height: text_line_height, - font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text_shaping, - }; - - for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { - let label = option.to_string(); - - paragraph.update(Text { - content: &label, - ..option_text - }); - } - - if let Some(placeholder) = placeholder { - state.placeholder.update(Text { - content: placeholder, - ..option_text - }); - } - - let max_width = match width { - Length::Shrink => { - let labels_width = - state.options.iter().fold(0.0, |width, paragraph| { - f32::max(width, paragraph.min_width()) - }); - - labels_width.max( - placeholder - .map(|_| state.placeholder.min_width()) - .unwrap_or(0.0), - ) - } - _ => 0.0, - }; - - let size = { - let intrinsic = Size::new( - max_width + text_size.0 + padding.left, - f32::from(text_line_height.to_absolute(text_size)), - ); - - limits - .width(width) - .shrink(padding) - .resolve(width, Length::Shrink, intrinsic) - .expand(padding) - }; - - layout::Node::new(size) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`PickList`] -/// accordingly. -pub fn update<'a, T, P, Message>( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - on_select: &dyn Fn(T) -> Message, - on_open: Option<&Message>, - on_close: Option<&Message>, - selected: Option<&T>, - options: &[T], - state: impl FnOnce() -> &'a mut State

, -) -> event::Status -where - T: PartialEq + Clone + 'a, - P: text::Paragraph + 'a, - Message: Clone, -{ - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); - - if state.is_open { - // Event wasn't processed by overlay, so cursor was clicked either outside it's - // bounds or on the drop-down, either way we close the overlay. - state.is_open = false; - - if let Some(on_close) = on_close { - shell.publish(on_close.clone()); - } - - event::Status::Captured - } else if cursor.is_over(layout.bounds()) { - state.is_open = true; - state.hovered_option = - options.iter().position(|option| Some(option) == selected); - - if let Some(on_open) = on_open { - shell.publish(on_open.clone()); - } - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Mouse(mouse::Event::WheelScrolled { - delta: mouse::ScrollDelta::Lines { y, .. }, - }) => { - let state = state(); - - if state.keyboard_modifiers.command() - && cursor.is_over(layout.bounds()) - && !state.is_open - { - fn find_next<'a, T: PartialEq>( - selected: &'a T, - mut options: impl Iterator, - ) -> Option<&'a T> { - let _ = options.find(|&option| option == selected); - - options.next() - } - - let next_option = if y < 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter()) - } else { - options.first() - } - } else if y > 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter().rev()) - } else { - options.last() - } - } else { - None - }; - - if let Some(next_option) = next_option { - shell.publish((on_select)(next_option.clone())); - } - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - - state.keyboard_modifiers = modifiers; - - event::Status::Ignored - } - _ => event::Status::Ignored, - } -} - -/// Returns the current [`mouse::Interaction`] of a [`PickList`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } -} - /// The possible status of a [`PickList`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 6cd2048f..9770ce57 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -269,20 +269,29 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.height, - &self.direction, - |renderer, limits| { - self.content.as_widget().layout( - &mut tree.children[0], - renderer, - limits, - ) - }, - ) + layout::contained(limits, self.width, self.height, |limits| { + let child_limits = layout::Limits::new( + Size::new(limits.min().width, limits.min().height), + Size::new( + if self.direction.horizontal().is_some() { + f32::INFINITY + } else { + limits.max().width + }, + if self.direction.vertical().is_some() { + f32::MAX + } else { + limits.max().height + }, + ), + ); + + self.content.as_widget().layout( + &mut tree.children[0], + renderer, + &child_limits, + ) + }) } fn operate( @@ -332,28 +341,316 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - tree.state.downcast_mut::(), - event, - layout, - cursor, - clipboard, - shell, - self.direction, - &self.on_scroll, - |event, layout, cursor, clipboard, shell, viewport| { - self.content.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout, - cursor, - renderer, - clipboard, + let state = tree.state.downcast_mut::(); + let bounds = layout.bounds(); + let cursor_over_scrollable = cursor.position_over(bounds); + + let content = layout.children().next().unwrap(); + let content_bounds = content.bounds(); + + let scrollbars = + Scrollbars::new(state, self.direction, bounds, content_bounds); + + let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = + scrollbars.is_mouse_over(cursor); + + let mut event_status = { + let cursor = match cursor_over_scrollable { + Some(cursor_position) + if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => + { + mouse::Cursor::Available( + cursor_position + + state.translation( + self.direction, + bounds, + content_bounds, + ), + ) + } + _ => mouse::Cursor::Unavailable, + }; + + let translation = + state.translation(self.direction, bounds, content_bounds); + + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + &Rectangle { + y: bounds.y + translation.y, + x: bounds.x + translation.x, + ..bounds + }, + ) + }; + + if let event::Status::Captured = event_status { + return event::Status::Captured; + } + + if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = + event + { + state.keyboard_modifiers = modifiers; + + return event::Status::Ignored; + } + + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + if cursor_over_scrollable.is_none() { + return event::Status::Ignored; + } + + let delta = match delta { + mouse::ScrollDelta::Lines { x, y } => { + // TODO: Configurable speed/friction (?) + let movement = if state.keyboard_modifiers.shift() { + Vector::new(y, x) + } else { + Vector::new(x, y) + }; + + movement * 60.0 + } + mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), + }; + + state.scroll(delta, self.direction, bounds, content_bounds); + + notify_on_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, shell, - viewport, - ) - }, - ) + ); + + event_status = event::Status::Captured; + } + Event::Touch(event) + if state.scroll_area_touched_at.is_some() + || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar => + { + match event { + touch::Event::FingerPressed { .. } => { + let Some(cursor_position) = cursor.position() else { + return event::Status::Ignored; + }; + + state.scroll_area_touched_at = Some(cursor_position); + } + touch::Event::FingerMoved { .. } => { + if let Some(scroll_box_touched_at) = + state.scroll_area_touched_at + { + let Some(cursor_position) = cursor.position() + else { + return event::Status::Ignored; + }; + + let delta = Vector::new( + cursor_position.x - scroll_box_touched_at.x, + cursor_position.y - scroll_box_touched_at.y, + ); + + state.scroll( + delta, + self.direction, + bounds, + content_bounds, + ); + + state.scroll_area_touched_at = + Some(cursor_position); + + notify_on_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } + } + touch::Event::FingerLifted { .. } + | touch::Event::FingerLost { .. } => { + state.scroll_area_touched_at = None; + } + } + + event_status = event::Status::Captured; + } + _ => {} + } + + if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { + match event { + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + state.y_scroller_grabbed_at = None; + + event_status = event::Status::Captured; + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + if let Some(scrollbar) = scrollbars.y { + let Some(cursor_position) = cursor.position() else { + return event::Status::Ignored; + }; + + state.scroll_y_to( + scrollbar.scroll_percentage_y( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); + + notify_on_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + + event_status = event::Status::Captured; + } + } + _ => {} + } + } else if mouse_over_y_scrollbar { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let Some(cursor_position) = cursor.position() else { + return event::Status::Ignored; + }; + + if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( + scrollbars.grab_y_scroller(cursor_position), + scrollbars.y, + ) { + state.scroll_y_to( + scrollbar.scroll_percentage_y( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); + + state.y_scroller_grabbed_at = Some(scroller_grabbed_at); + + notify_on_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } + + event_status = event::Status::Captured; + } + _ => {} + } + } + + if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { + match event { + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) + | Event::Touch(touch::Event::FingerLost { .. }) => { + state.x_scroller_grabbed_at = None; + + event_status = event::Status::Captured; + } + Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) => { + let Some(cursor_position) = cursor.position() else { + return event::Status::Ignored; + }; + + if let Some(scrollbar) = scrollbars.x { + state.scroll_x_to( + scrollbar.scroll_percentage_x( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); + + notify_on_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + } + + event_status = event::Status::Captured; + } + _ => {} + } + } else if mouse_over_x_scrollbar { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let Some(cursor_position) = cursor.position() else { + return event::Status::Ignored; + }; + + if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( + scrollbars.grab_x_scroller(cursor_position), + scrollbars.x, + ) { + state.scroll_x_to( + scrollbar.scroll_percentage_x( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); + + state.x_scroller_grabbed_at = Some(scroller_grabbed_at); + + notify_on_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + + event_status = event::Status::Captured; + } + } + _ => {} + } + } + + event_status } fn draw( @@ -551,21 +848,48 @@ where _viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction( - tree.state.downcast_ref::(), - layout, - cursor, - self.direction, - |layout, cursor, viewport| { - self.content.as_widget().mouse_interaction( - &tree.children[0], - layout, - cursor, - viewport, - renderer, - ) - }, - ) + let state = tree.state.downcast_ref::(); + let bounds = layout.bounds(); + let cursor_over_scrollable = cursor.position_over(bounds); + + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + + let scrollbars = + Scrollbars::new(state, self.direction, bounds, content_bounds); + + let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = + scrollbars.is_mouse_over(cursor); + + if (mouse_over_x_scrollbar || mouse_over_y_scrollbar) + || state.scrollers_grabbed() + { + mouse::Interaction::Idle + } else { + let translation = + state.translation(self.direction, bounds, content_bounds); + + let cursor = match cursor_over_scrollable { + Some(cursor_position) + if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => + { + mouse::Cursor::Available(cursor_position + translation) + } + _ => mouse::Cursor::Unavailable, + }; + + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor, + &Rectangle { + y: bounds.y + translation.y, + x: bounds.x + translation.x, + ..bounds + }, + renderer, + ) + } } fn overlay<'b>( @@ -651,386 +975,6 @@ pub fn scroll_to( Command::widget(operation::scrollable::scroll_to(id.0, offset)) } -/// Computes the layout of a [`Scrollable`]. -pub fn layout( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - height: Length, - direction: &Direction, - layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { - layout::contained(limits, width, height, |limits| { - let child_limits = layout::Limits::new( - Size::new(limits.min().width, limits.min().height), - Size::new( - if direction.horizontal().is_some() { - f32::INFINITY - } else { - limits.max().width - }, - if direction.vertical().is_some() { - f32::MAX - } else { - limits.max().height - }, - ), - ); - - layout_content(renderer, &child_limits) - }) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`] -/// accordingly. -pub fn update( - state: &mut State, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - direction: Direction, - on_scroll: &Option Message + '_>>, - update_content: impl FnOnce( - Event, - Layout<'_>, - mouse::Cursor, - &mut dyn Clipboard, - &mut Shell<'_, Message>, - &Rectangle, - ) -> event::Status, -) -> event::Status { - let bounds = layout.bounds(); - let cursor_over_scrollable = cursor.position_over(bounds); - - let content = layout.children().next().unwrap(); - let content_bounds = content.bounds(); - - let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); - - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = - scrollbars.is_mouse_over(cursor); - - let mut event_status = { - let cursor = match cursor_over_scrollable { - Some(cursor_position) - if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => - { - mouse::Cursor::Available( - cursor_position - + state.translation(direction, bounds, content_bounds), - ) - } - _ => mouse::Cursor::Unavailable, - }; - - let translation = state.translation(direction, bounds, content_bounds); - - update_content( - event.clone(), - content, - cursor, - clipboard, - shell, - &Rectangle { - y: bounds.y + translation.y, - x: bounds.x + translation.x, - ..bounds - }, - ) - }; - - if let event::Status::Captured = event_status { - return event::Status::Captured; - } - - if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event - { - state.keyboard_modifiers = modifiers; - - return event::Status::Ignored; - } - - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - if cursor_over_scrollable.is_none() { - return event::Status::Ignored; - } - - let delta = match delta { - mouse::ScrollDelta::Lines { x, y } => { - // TODO: Configurable speed/friction (?) - let movement = if state.keyboard_modifiers.shift() { - Vector::new(y, x) - } else { - Vector::new(x, y) - }; - - movement * 60.0 - } - mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), - }; - - state.scroll(delta, direction, bounds, content_bounds); - - notify_on_scroll(state, on_scroll, bounds, content_bounds, shell); - - event_status = event::Status::Captured; - } - Event::Touch(event) - if state.scroll_area_touched_at.is_some() - || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar => - { - match event { - touch::Event::FingerPressed { .. } => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; - - state.scroll_area_touched_at = Some(cursor_position); - } - touch::Event::FingerMoved { .. } => { - if let Some(scroll_box_touched_at) = - state.scroll_area_touched_at - { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; - - let delta = Vector::new( - cursor_position.x - scroll_box_touched_at.x, - cursor_position.y - scroll_box_touched_at.y, - ); - - state.scroll(delta, direction, bounds, content_bounds); - - state.scroll_area_touched_at = Some(cursor_position); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - } - } - touch::Event::FingerLifted { .. } - | touch::Event::FingerLost { .. } => { - state.scroll_area_touched_at = None; - } - } - - event_status = event::Status::Captured; - } - _ => {} - } - - if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { - match event { - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - state.y_scroller_grabbed_at = None; - - event_status = event::Status::Captured; - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some(scrollbar) = scrollbars.y { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; - - state.scroll_y_to( - scrollbar.scroll_percentage_y( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - - event_status = event::Status::Captured; - } - } - _ => {} - } - } else if mouse_over_y_scrollbar { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; - - if let (Some(scroller_grabbed_at), Some(scrollbar)) = - (scrollbars.grab_y_scroller(cursor_position), scrollbars.y) - { - state.scroll_y_to( - scrollbar.scroll_percentage_y( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - state.y_scroller_grabbed_at = Some(scroller_grabbed_at); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - } - - event_status = event::Status::Captured; - } - _ => {} - } - } - - if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { - match event { - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) - | Event::Touch(touch::Event::FingerLost { .. }) => { - state.x_scroller_grabbed_at = None; - - event_status = event::Status::Captured; - } - Event::Mouse(mouse::Event::CursorMoved { .. }) - | Event::Touch(touch::Event::FingerMoved { .. }) => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; - - if let Some(scrollbar) = scrollbars.x { - state.scroll_x_to( - scrollbar.scroll_percentage_x( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - } - - event_status = event::Status::Captured; - } - _ => {} - } - } else if mouse_over_x_scrollbar { - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; - }; - - if let (Some(scroller_grabbed_at), Some(scrollbar)) = - (scrollbars.grab_x_scroller(cursor_position), scrollbars.x) - { - state.scroll_x_to( - scrollbar.scroll_percentage_x( - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - state.x_scroller_grabbed_at = Some(scroller_grabbed_at); - - notify_on_scroll( - state, - on_scroll, - bounds, - content_bounds, - shell, - ); - - event_status = event::Status::Captured; - } - } - _ => {} - } - } - - event_status -} - -/// Computes the current [`mouse::Interaction`] of a [`Scrollable`]. -pub fn mouse_interaction( - state: &State, - layout: Layout<'_>, - cursor: mouse::Cursor, - direction: Direction, - content_interaction: impl FnOnce( - Layout<'_>, - mouse::Cursor, - &Rectangle, - ) -> mouse::Interaction, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let cursor_over_scrollable = cursor.position_over(bounds); - - let content_layout = layout.children().next().unwrap(); - let content_bounds = content_layout.bounds(); - - let scrollbars = Scrollbars::new(state, direction, bounds, content_bounds); - - let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = - scrollbars.is_mouse_over(cursor); - - if (mouse_over_x_scrollbar || mouse_over_y_scrollbar) - || state.scrollers_grabbed() - { - mouse::Interaction::Idle - } else { - let translation = state.translation(direction, bounds, content_bounds); - - let cursor = match cursor_over_scrollable { - Some(cursor_position) - if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => - { - mouse::Cursor::Available(cursor_position + translation) - } - _ => mouse::Cursor::Unavailable, - }; - - content_interaction( - content_layout, - cursor, - &Rectangle { - y: bounds.y + translation.y, - x: bounds.x + translation.x, - ..bounds - }, - ) - } -} - fn notify_on_scroll( state: &mut State, on_scroll: &Option Message + '_>>, @@ -1078,9 +1022,8 @@ fn notify_on_scroll( } } -/// The local state of a [`Scrollable`]. #[derive(Debug, Clone, Copy)] -pub struct State { +struct State { scroll_area_touched_at: Option, offset_y: Offset, y_scroller_grabbed_at: Option, -- cgit From 3e99f39a8680bb48d5c15b043c399a3337765ae5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 13:40:10 +0100 Subject: Rename `transparentize` to `scale_alpha` --- widget/src/button.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'widget') diff --git a/widget/src/button.rs b/widget/src/button.rs index 243eaf04..e265aa1f 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -537,7 +537,7 @@ pub fn text(theme: &Theme, status: Status) -> Appearance { match status { Status::Active | Status::Pressed => base, Status::Hovered => Appearance { - text_color: palette.background.base.text.transparentize(0.8), + text_color: palette.background.base.text.scale_alpha(0.8), ..base }, Status::Disabled => disabled(base), @@ -557,8 +557,8 @@ fn disabled(appearance: Appearance) -> Appearance { Appearance { background: appearance .background - .map(|background| background.transparentize(0.5)), - text_color: appearance.text_color.transparentize(0.5), + .map(|background| background.scale_alpha(0.5)), + text_color: appearance.text_color.scale_alpha(0.5), ..appearance } } -- cgit