summaryrefslogtreecommitdiffstats
path: root/native/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/widget')
-rw-r--r--native/src/widget/button.rs293
-rw-r--r--native/src/widget/checkbox.rs4
-rw-r--r--native/src/widget/container.rs58
-rw-r--r--native/src/widget/image.rs74
-rw-r--r--native/src/widget/pick_list.rs551
-rw-r--r--native/src/widget/radio.rs2
-rw-r--r--native/src/widget/scrollable.rs889
-rw-r--r--native/src/widget/slider.rs384
-rw-r--r--native/src/widget/text_input.rs901
-rw-r--r--native/src/widget/toggler.rs4
10 files changed, 1789 insertions, 1371 deletions
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 57fdd7d4..b03d9f27 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -61,8 +61,6 @@ pub struct Button<'a, Message, Renderer> {
on_press: Option<Message>,
width: Length,
height: Length,
- min_width: u32,
- min_height: u32,
padding: Padding,
style_sheet: Box<dyn StyleSheet + 'a>,
}
@@ -84,8 +82,6 @@ where
on_press: None,
width: Length::Shrink,
height: Length::Shrink,
- min_width: 0,
- min_height: 0,
padding: Padding::new(5),
style_sheet: Default::default(),
}
@@ -103,18 +99,6 @@ where
self
}
- /// Sets the minimum width of the [`Button`].
- pub fn min_width(mut self, min_width: u32) -> Self {
- self.min_width = min_width;
- self
- }
-
- /// Sets the minimum height of the [`Button`].
- pub fn min_height(mut self, min_height: u32) -> Self {
- self.min_height = min_height;
- self
- }
-
/// Sets the [`Padding`] of the [`Button`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
@@ -151,6 +135,153 @@ impl State {
}
}
+/// Processes the given [`Event`] and updates the [`State`] of a [`Button`]
+/// accordingly.
+pub fn update<'a, Message: Clone>(
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ shell: &mut Shell<'_, Message>,
+ on_press: &Option<Message>,
+ state: impl FnOnce() -> &'a mut State,
+) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if on_press.is_some() {
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ let state = state();
+
+ state.is_pressed = true;
+
+ return event::Status::Captured;
+ }
+ }
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) => {
+ if let Some(on_press) = on_press.clone() {
+ let state = state();
+
+ if state.is_pressed {
+ state.is_pressed = false;
+
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ shell.publish(on_press);
+ }
+
+ return event::Status::Captured;
+ }
+ }
+ }
+ Event::Touch(touch::Event::FingerLost { .. }) => {
+ let state = state();
+
+ state.is_pressed = false;
+ }
+ _ => {}
+ }
+
+ event::Status::Ignored
+}
+
+/// Draws a [`Button`].
+pub fn draw<'a, Renderer: crate::Renderer>(
+ renderer: &mut Renderer,
+ bounds: Rectangle,
+ cursor_position: Point,
+ is_enabled: bool,
+ style_sheet: &dyn StyleSheet,
+ state: impl FnOnce() -> &'a State,
+) -> Style {
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let styling = if !is_enabled {
+ style_sheet.disabled()
+ } else if is_mouse_over {
+ let state = state();
+
+ if state.is_pressed {
+ style_sheet.pressed()
+ } else {
+ style_sheet.hovered()
+ }
+ } else {
+ style_sheet.active()
+ };
+
+ if styling.background.is_some() || styling.border_width > 0.0 {
+ if styling.shadow_offset != Vector::default() {
+ // TODO: Implement proper shadow support
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + styling.shadow_offset.x,
+ y: bounds.y + styling.shadow_offset.y,
+ ..bounds
+ },
+ border_radius: styling.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color([0.0, 0.0, 0.0, 0.5].into()),
+ );
+ }
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: styling.border_radius,
+ border_width: styling.border_width,
+ border_color: styling.border_color,
+ },
+ styling
+ .background
+ .unwrap_or(Background::Color(Color::TRANSPARENT)),
+ );
+ }
+
+ styling
+}
+
+/// Computes the layout of a [`Button`].
+pub fn layout<Renderer>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ height: Length,
+ padding: Padding,
+ layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
+) -> layout::Node {
+ let limits = limits.width(width).height(height).pad(padding);
+
+ let mut content = layout_content(renderer, &limits);
+ content.move_to(Point::new(padding.left.into(), padding.top.into()));
+
+ let size = limits.resolve(content.size()).pad(padding);
+
+ layout::Node::with_children(size, vec![content])
+}
+
+/// Returns the [`mouse::Interaction`] of a [`Button`].
+pub fn mouse_interaction(
+ layout: Layout<'_>,
+ cursor_position: Point,
+ is_enabled: bool,
+) -> mouse::Interaction {
+ let is_mouse_over = layout.bounds().contains(cursor_position);
+
+ if is_mouse_over && is_enabled {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+}
+
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
@@ -170,22 +301,14 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits
- .min_width(self.min_width)
- .min_height(self.min_height)
- .width(self.width)
- .height(self.height)
- .pad(self.padding);
-
- let mut content = self.content.layout(renderer, &limits);
- content.move_to(Point::new(
- self.padding.left.into(),
- self.padding.top.into(),
- ));
-
- let size = limits.resolve(content.size()).pad(self.padding);
-
- layout::Node::with_children(size, vec![content])
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.height,
+ self.padding,
+ |renderer, limits| self.content.layout(renderer, limits),
+ )
}
fn on_event(
@@ -208,42 +331,14 @@ where
return event::Status::Captured;
}
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- if self.on_press.is_some() {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
- self.state.is_pressed = true;
-
- return event::Status::Captured;
- }
- }
- }
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerLifted { .. }) => {
- if let Some(on_press) = self.on_press.clone() {
- let bounds = layout.bounds();
-
- if self.state.is_pressed {
- self.state.is_pressed = false;
-
- if bounds.contains(cursor_position) {
- shell.publish(on_press);
- }
-
- return event::Status::Captured;
- }
- }
- }
- Event::Touch(touch::Event::FingerLost { .. }) => {
- self.state.is_pressed = false;
- }
- _ => {}
- }
-
- event::Status::Ignored
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ &self.on_press,
+ || &mut self.state,
+ )
}
fn mouse_interaction(
@@ -253,14 +348,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- let is_mouse_over = layout.bounds().contains(cursor_position);
- let is_disabled = self.on_press.is_none();
-
- if is_mouse_over && !is_disabled {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- }
+ mouse_interaction(layout, cursor_position, self.on_press.is_some())
}
fn draw(
@@ -274,51 +362,14 @@ where
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
- let is_mouse_over = bounds.contains(cursor_position);
- let is_disabled = self.on_press.is_none();
-
- let styling = if is_disabled {
- self.style_sheet.disabled()
- } else if is_mouse_over {
- if self.state.is_pressed {
- self.style_sheet.pressed()
- } else {
- self.style_sheet.hovered()
- }
- } else {
- self.style_sheet.active()
- };
-
- if styling.background.is_some() || styling.border_width > 0.0 {
- if styling.shadow_offset != Vector::default() {
- // TODO: Implement proper shadow support
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: bounds.x + styling.shadow_offset.x,
- y: bounds.y + styling.shadow_offset.y,
- ..bounds
- },
- border_radius: styling.border_radius,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- Background::Color([0.0, 0.0, 0.0, 0.5].into()),
- );
- }
-
- renderer.fill_quad(
- renderer::Quad {
- bounds,
- border_radius: styling.border_radius,
- border_width: styling.border_width,
- border_color: styling.border_color,
- },
- styling
- .background
- .unwrap_or(Background::Color(Color::TRANSPARENT)),
- );
- }
+ let styling = draw(
+ renderer,
+ bounds,
+ cursor_position,
+ self.on_press.is_some(),
+ self.style_sheet.as_ref(),
+ || &self.state,
+ );
self.content.draw(
renderer,
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 15cbf93a..122c5e52 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -34,7 +34,7 @@ pub use iced_style::checkbox::{Style, StyleSheet};
#[allow(missing_debug_implementations)]
pub struct Checkbox<'a, Message, Renderer: text::Renderer> {
is_checked: bool,
- on_toggle: Box<dyn Fn(bool) -> Message>,
+ on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
label: String,
width: Length,
size: u16,
@@ -61,7 +61,7 @@ impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> {
/// `Message`.
pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self
where
- F: 'static + Fn(bool) -> Message,
+ F: 'a + Fn(bool) -> Message,
{
Checkbox {
is_checked,
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index ca85a425..0e7c301e 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -116,6 +116,32 @@ where
}
}
+/// Computes the layout of a [`Container`].
+pub fn layout<Renderer>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ height: Length,
+ padding: Padding,
+ horizontal_alignment: alignment::Horizontal,
+ vertical_alignment: alignment::Vertical,
+ layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
+) -> layout::Node {
+ let limits = limits.loose().width(width).height(height).pad(padding);
+
+ let mut content = layout_content(renderer, &limits.loose());
+ let size = limits.resolve(content.size());
+
+ content.move_to(Point::new(padding.left.into(), padding.top.into()));
+ content.align(
+ Alignment::from(horizontal_alignment),
+ Alignment::from(vertical_alignment),
+ size,
+ );
+
+ layout::Node::with_children(size.pad(padding), vec![content])
+}
+
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>
where
@@ -134,28 +160,16 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits
- .loose()
- .max_width(self.max_width)
- .max_height(self.max_height)
- .width(self.width)
- .height(self.height)
- .pad(self.padding);
-
- let mut content = self.content.layout(renderer, &limits.loose());
- let size = limits.resolve(content.size());
-
- content.move_to(Point::new(
- self.padding.left.into(),
- self.padding.top.into(),
- ));
- content.align(
- Alignment::from(self.horizontal_alignment),
- Alignment::from(self.vertical_alignment),
- size,
- );
-
- layout::Node::with_children(size.pad(self.padding), vec![content])
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.height,
+ self.padding,
+ self.horizontal_alignment,
+ self.vertical_alignment,
+ |renderer, limits| self.content.layout(renderer, limits),
+ )
}
fn on_event(
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index de0ffbc0..8e7a28e5 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -65,6 +65,46 @@ impl<Handle> Image<Handle> {
}
}
+/// Computes the layout of an [`Image`].
+pub fn layout<Renderer, Handle>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ handle: &Handle,
+ width: Length,
+ height: Length,
+ content_fit: ContentFit,
+) -> layout::Node
+where
+ Renderer: image::Renderer<Handle = Handle>,
+{
+ // The raw w/h of the underlying image
+ let image_size = {
+ let (width, height) = renderer.dimensions(handle);
+
+ Size::new(width as f32, height as f32)
+ };
+
+ // The size to be available to the widget prior to `Shrink`ing
+ let raw_size = limits.width(width).height(height).resolve(image_size);
+
+ // The uncropped size of the image when fit to the bounds above
+ let full_size = content_fit.fit(image_size, raw_size);
+
+ // Shrink the widget to fit the resized image, if requested
+ let final_size = Size {
+ width: match width {
+ Length::Shrink => f32::min(raw_size.width, full_size.width),
+ _ => raw_size.width,
+ },
+ height: match height {
+ Length::Shrink => f32::min(raw_size.height, full_size.height),
+ _ => raw_size.height,
+ },
+ };
+
+ layout::Node::new(final_size)
+}
+
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle>
where
Renderer: image::Renderer<Handle = Handle>,
@@ -83,32 +123,14 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- // The raw w/h of the underlying image
- let (width, height) = renderer.dimensions(&self.handle);
- let image_size = Size::new(width as f32, height as f32);
-
- // The size to be available to the widget prior to `Shrink`ing
- let raw_size = limits
- .width(self.width)
- .height(self.height)
- .resolve(image_size);
-
- // The uncropped size of the image when fit to the bounds above
- let full_size = self.content_fit.fit(image_size, raw_size);
-
- // Shrink the widget to fit the resized image, if requested
- let final_size = Size {
- width: match self.width {
- Length::Shrink => f32::min(raw_size.width, full_size.width),
- _ => raw_size.width,
- },
- height: match self.height {
- Length::Shrink => f32::min(raw_size.height, full_size.height),
- _ => raw_size.height,
- },
- };
-
- layout::Node::new(final_size)
+ layout(
+ renderer,
+ limits,
+ &self.handle,
+ self.width,
+ self.height,
+ self.content_fit,
+ )
}
fn draw(
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index 3be6c20c..e050ada5 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -23,11 +23,7 @@ pub struct PickList<'a, T, Message, Renderer: text::Renderer>
where
[T]: ToOwned<Owned = Vec<T>>,
{
- menu: &'a mut menu::State,
- keyboard_modifiers: &'a mut keyboard::Modifiers,
- is_open: &'a mut bool,
- hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ state: &'a mut State<T>,
on_selected: Box<dyn Fn(T) -> Message>,
options: Cow<'a, [T]>,
placeholder: Option<String>,
@@ -49,8 +45,9 @@ pub struct State<T> {
last_selection: Option<T>,
}
-impl<T> Default for State<T> {
- fn default() -> Self {
+impl<T> State<T> {
+ /// Creates a new [`State`] for a [`PickList`].
+ pub fn new() -> Self {
Self {
menu: menu::State::default(),
keyboard_modifiers: keyboard::Modifiers::default(),
@@ -61,6 +58,12 @@ impl<T> Default for State<T> {
}
}
+impl<T> Default for State<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl<'a, T: 'a, Message, Renderer: text::Renderer>
PickList<'a, T, Message, Renderer>
where
@@ -79,20 +82,8 @@ where
selected: Option<T>,
on_selected: impl Fn(T) -> Message + 'static,
) -> Self {
- let State {
- menu,
- keyboard_modifiers,
- is_open,
- hovered_option,
- last_selection,
- } = state;
-
Self {
- menu,
- keyboard_modifiers,
- is_open,
- hovered_option,
- last_selection,
+ state,
on_selected: Box::new(on_selected),
options: options.into(),
placeholder: None,
@@ -145,128 +136,118 @@ where
}
}
-impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
- for PickList<'a, T, Message, Renderer>
+/// Computes the layout of a [`PickList`].
+pub fn layout<Renderer, T>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ padding: Padding,
+ text_size: Option<u16>,
+ font: &Renderer::Font,
+ placeholder: Option<&str>,
+ options: &[T],
+) -> layout::Node
where
- T: Clone + ToString + Eq,
- [T]: ToOwned<Owned = Vec<T>>,
- Message: 'static,
- Renderer: text::Renderer + 'a,
+ Renderer: text::Renderer,
+ T: ToString,
{
- fn width(&self) -> Length {
- self.width
- }
+ use std::f32;
- fn height(&self) -> Length {
- Length::Shrink
- }
+ let limits = limits.width(width).height(Length::Shrink).pad(padding);
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- use std::f32;
-
- let limits = limits
- .width(self.width)
- .height(Length::Shrink)
- .pad(self.padding);
-
- let text_size = self.text_size.unwrap_or(renderer.default_size());
- let font = self.font.clone();
-
- let max_width = match self.width {
- Length::Shrink => {
- let measure = |label: &str| -> u32 {
- let (width, _) = renderer.measure(
- label,
- text_size,
- font.clone(),
- Size::new(f32::INFINITY, f32::INFINITY),
- );
-
- width.round() as u32
- };
+ let text_size = text_size.unwrap_or(renderer.default_size());
- let labels = self.options.iter().map(ToString::to_string);
+ let max_width = match width {
+ Length::Shrink => {
+ let measure = |label: &str| -> u32 {
+ let (width, _) = renderer.measure(
+ label,
+ text_size,
+ font.clone(),
+ Size::new(f32::INFINITY, f32::INFINITY),
+ );
- let labels_width =
- labels.map(|label| measure(&label)).max().unwrap_or(100);
+ width.round() as u32
+ };
- let placeholder_width = self
- .placeholder
- .as_ref()
- .map(String::as_str)
- .map(measure)
- .unwrap_or(100);
+ let labels = options.iter().map(ToString::to_string);
- labels_width.max(placeholder_width)
- }
- _ => 0,
- };
+ let labels_width =
+ labels.map(|label| measure(&label)).max().unwrap_or(100);
- let size = {
- let intrinsic = Size::new(
- max_width as f32
- + f32::from(text_size)
- + f32::from(self.padding.left),
- f32::from(text_size),
- );
+ let placeholder_width = placeholder.map(measure).unwrap_or(100);
- limits.resolve(intrinsic).pad(self.padding)
- };
+ labels_width.max(placeholder_width)
+ }
+ _ => 0,
+ };
- layout::Node::new(size)
- }
+ let size = {
+ let intrinsic = Size::new(
+ max_width as f32 + f32::from(text_size) + f32::from(padding.left),
+ f32::from(text_size),
+ );
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- _renderer: &Renderer,
- _clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let event_status = if *self.is_open {
- // TODO: Encode cursor availability in the type system
- *self.is_open =
- cursor_position.x < 0.0 || cursor_position.y < 0.0;
-
- event::Status::Captured
- } else if layout.bounds().contains(cursor_position) {
- let selected = self.selected.as_ref();
-
- *self.is_open = true;
- *self.hovered_option = self
- .options
- .iter()
- .position(|option| Some(option) == selected);
-
- event::Status::Captured
- } else {
- event::Status::Ignored
- };
+ limits.resolve(intrinsic).pad(padding)
+ };
+
+ layout::Node::new(size)
+}
- if let Some(last_selection) = self.last_selection.take() {
- shell.publish((self.on_selected)(last_selection));
+/// Processes an [`Event`] and updates the [`State`] of a [`PickList`]
+/// accordingly.
+pub fn update<'a, T, Message>(
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ shell: &mut Shell<'_, Message>,
+ on_selected: &dyn Fn(T) -> Message,
+ selected: Option<&T>,
+ options: &[T],
+ state: impl FnOnce() -> &'a mut State<T>,
+) -> event::Status
+where
+ T: PartialEq + Clone + 'a,
+{
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let state = state();
- *self.is_open = false;
+ let event_status = if state.is_open {
+ // TODO: Encode cursor availability in the type system
+ state.is_open =
+ cursor_position.x < 0.0 || cursor_position.y < 0.0;
- event::Status::Captured
- } else {
- event_status
- }
+ event::Status::Captured
+ } else if layout.bounds().contains(cursor_position) {
+ state.is_open = true;
+ state.hovered_option =
+ options.iter().position(|option| Some(option) == selected);
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ };
+
+ if let Some(last_selection) = state.last_selection.take() {
+ shell.publish((on_selected)(last_selection));
+
+ state.is_open = false;
+
+ event::Status::Captured
+ } else {
+ event_status
}
- Event::Mouse(mouse::Event::WheelScrolled {
- delta: mouse::ScrollDelta::Lines { y, .. },
- }) if self.keyboard_modifiers.command()
+ }
+ Event::Mouse(mouse::Event::WheelScrolled {
+ delta: mouse::ScrollDelta::Lines { y, .. },
+ }) => {
+ let state = state();
+
+ if state.keyboard_modifiers.command()
&& layout.bounds().contains(cursor_position)
- && !*self.is_open =>
+ && !state.is_open
{
fn find_next<'a, T: PartialEq>(
selected: &'a T,
@@ -278,34 +259,219 @@ where
}
let next_option = if y < 0.0 {
- if let Some(selected) = self.selected.as_ref() {
- find_next(selected, self.options.iter())
+ if let Some(selected) = selected {
+ find_next(selected, options.iter())
} else {
- self.options.first()
+ options.first()
}
} else if y > 0.0 {
- if let Some(selected) = self.selected.as_ref() {
- find_next(selected, self.options.iter().rev())
+ if let Some(selected) = selected {
+ find_next(selected, options.iter().rev())
} else {
- self.options.last()
+ options.last()
}
} else {
None
};
if let Some(next_option) = next_option {
- shell.publish((self.on_selected)(next_option.clone()));
+ shell.publish((on_selected)(next_option.clone()));
}
event::Status::Captured
- }
- Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
- *self.keyboard_modifiers = modifiers;
-
+ } else {
event::Status::Ignored
}
- _ => 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_position: Point,
+) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+}
+
+/// Returns the current overlay of a [`PickList`].
+pub fn overlay<'a, T, Message, Renderer>(
+ layout: Layout<'_>,
+ state: &'a mut State<T>,
+ padding: Padding,
+ text_size: Option<u16>,
+ font: Renderer::Font,
+ options: &'a [T],
+ style_sheet: &dyn StyleSheet,
+) -> Option<overlay::Element<'a, Message, Renderer>>
+where
+ Message: 'a,
+ Renderer: text::Renderer + 'a,
+ T: Clone + ToString,
+{
+ if state.is_open {
+ let bounds = layout.bounds();
+
+ let mut menu = Menu::new(
+ &mut state.menu,
+ options,
+ &mut state.hovered_option,
+ &mut state.last_selection,
+ )
+ .width(bounds.width.round() as u16)
+ .padding(padding)
+ .font(font)
+ .style(style_sheet.menu());
+
+ if let Some(text_size) = text_size {
+ menu = menu.text_size(text_size);
+ }
+
+ Some(menu.overlay(layout.position(), bounds.height))
+ } else {
+ None
+ }
+}
+
+/// Draws a [`PickList`].
+pub fn draw<T, Renderer>(
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ padding: Padding,
+ text_size: Option<u16>,
+ font: &Renderer::Font,
+ placeholder: Option<&str>,
+ selected: Option<&T>,
+ style_sheet: &dyn StyleSheet,
+) where
+ Renderer: text::Renderer,
+ T: ToString,
+{
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_selected = selected.is_some();
+
+ let style = if is_mouse_over {
+ style_sheet.hovered()
+ } else {
+ style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: style.border_color,
+ border_width: style.border_width,
+ border_radius: style.border_radius,
+ },
+ style.background,
+ );
+
+ renderer.fill_text(Text {
+ content: &Renderer::ARROW_DOWN_ICON.to_string(),
+ font: Renderer::ICON_FONT,
+ size: bounds.height * style.icon_size,
+ bounds: Rectangle {
+ x: bounds.x + bounds.width - f32::from(padding.horizontal()),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ color: style.text_color,
+ horizontal_alignment: alignment::Horizontal::Right,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+
+ let label = selected.map(ToString::to_string);
+
+ if let Some(label) =
+ label.as_ref().map(String::as_str).or_else(|| placeholder)
+ {
+ renderer.fill_text(Text {
+ content: label,
+ size: f32::from(text_size.unwrap_or(renderer.default_size())),
+ font: font.clone(),
+ color: is_selected
+ .then(|| style.text_color)
+ .unwrap_or(style.placeholder_color),
+ bounds: Rectangle {
+ x: bounds.x + f32::from(padding.left),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ })
+ }
+}
+
+impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
+ for PickList<'a, T, Message, Renderer>
+where
+ T: Clone + ToString + Eq,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Message: 'static,
+ Renderer: text::Renderer + 'a,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.padding,
+ self.text_size,
+ &self.font,
+ self.placeholder.as_ref().map(String::as_str),
+ &self.options,
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ self.on_selected.as_ref(),
+ self.selected.as_ref(),
+ &self.options,
+ || &mut self.state,
+ )
}
fn mouse_interaction(
@@ -315,14 +481,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
-
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- }
+ mouse_interaction(layout, cursor_position)
}
fn draw(
@@ -333,66 +492,17 @@ where
cursor_position: Point,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
- let is_selected = self.selected.is_some();
-
- let style = if is_mouse_over {
- self.style_sheet.hovered()
- } else {
- self.style_sheet.active()
- };
-
- renderer.fill_quad(
- renderer::Quad {
- bounds,
- border_color: style.border_color,
- border_width: style.border_width,
- border_radius: style.border_radius,
- },
- style.background,
- );
-
- renderer.fill_text(Text {
- content: &Renderer::ARROW_DOWN_ICON.to_string(),
- font: Renderer::ICON_FONT,
- size: bounds.height * style.icon_size,
- bounds: Rectangle {
- x: bounds.x + bounds.width
- - f32::from(self.padding.horizontal()),
- y: bounds.center_y(),
- ..bounds
- },
- color: style.text_color,
- horizontal_alignment: alignment::Horizontal::Right,
- vertical_alignment: alignment::Vertical::Center,
- });
-
- if let Some(label) = self
- .selected
- .as_ref()
- .map(ToString::to_string)
- .as_ref()
- .or_else(|| self.placeholder.as_ref())
- {
- renderer.fill_text(Text {
- content: label,
- size: f32::from(
- self.text_size.unwrap_or(renderer.default_size()),
- ),
- font: self.font.clone(),
- color: is_selected
- .then(|| style.text_color)
- .unwrap_or(style.placeholder_color),
- bounds: Rectangle {
- x: bounds.x + f32::from(self.padding.left),
- y: bounds.center_y(),
- ..bounds
- },
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Center,
- })
- }
+ draw(
+ renderer,
+ layout,
+ cursor_position,
+ self.padding,
+ self.text_size,
+ &self.font,
+ self.placeholder.as_ref().map(String::as_str),
+ self.selected.as_ref(),
+ self.style_sheet.as_ref(),
+ )
}
fn overlay(
@@ -400,28 +510,15 @@ where
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- if *self.is_open {
- let bounds = layout.bounds();
-
- let mut menu = Menu::new(
- &mut self.menu,
- &self.options,
- &mut self.hovered_option,
- &mut self.last_selection,
- )
- .width(bounds.width.round() as u16)
- .padding(self.padding)
- .font(self.font.clone())
- .style(self.style_sheet.menu());
-
- if let Some(text_size) = self.text_size {
- menu = menu.text_size(text_size);
- }
-
- Some(menu.overlay(layout.position(), bounds.height))
- } else {
- None
- }
+ overlay(
+ layout,
+ &mut self.state,
+ self.padding,
+ self.text_size,
+ self.font.clone(),
+ &self.options,
+ self.style_sheet.as_ref(),
+ )
}
}
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index fed2925b..657ae786 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -79,7 +79,7 @@ where
) -> Self
where
V: Eq + Copy,
- F: 'static + Fn(V) -> Message,
+ F: FnOnce(V) -> Message,
{
Radio {
is_selected: Some(value) == selected,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index ce734ad8..3752fd71 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -139,235 +139,201 @@ impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
self.content = self.content.push(child);
self
}
-
- fn notify_on_scroll(
- &self,
- bounds: Rectangle,
- content_bounds: Rectangle,
- shell: &mut Shell<'_, Message>,
- ) {
- if content_bounds.height <= bounds.height {
- return;
- }
-
- if let Some(on_scroll) = &self.on_scroll {
- shell.publish(on_scroll(
- self.state.offset.absolute(bounds, content_bounds)
- / (content_bounds.height - bounds.height),
- ));
- }
- }
-
- fn scrollbar(
- &self,
- bounds: Rectangle,
- content_bounds: Rectangle,
- ) -> Option<Scrollbar> {
- let offset = self.state.offset(bounds, content_bounds);
-
- if content_bounds.height > bounds.height {
- let outer_width = self.scrollbar_width.max(self.scroller_width)
- + 2 * self.scrollbar_margin;
-
- let outer_bounds = Rectangle {
- x: bounds.x + bounds.width - outer_width as f32,
- y: bounds.y,
- width: outer_width as f32,
- height: bounds.height,
- };
-
- let scrollbar_bounds = Rectangle {
- x: bounds.x + bounds.width
- - f32::from(outer_width / 2 + self.scrollbar_width / 2),
- y: bounds.y,
- width: self.scrollbar_width as f32,
- height: bounds.height,
- };
-
- let ratio = bounds.height / content_bounds.height;
- let scroller_height = bounds.height * ratio;
- let y_offset = offset as f32 * ratio;
-
- let scroller_bounds = Rectangle {
- x: bounds.x + bounds.width
- - f32::from(outer_width / 2 + self.scroller_width / 2),
- y: scrollbar_bounds.y + y_offset,
- width: self.scroller_width as f32,
- height: scroller_height,
- };
-
- Some(Scrollbar {
- outer_bounds,
- bounds: scrollbar_bounds,
- scroller: Scroller {
- bounds: scroller_bounds,
- },
- })
- } else {
- None
- }
- }
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Scrollable<'a, Message, Renderer>
-where
- Renderer: crate::Renderer,
-{
- fn width(&self) -> Length {
- Widget::<Message, Renderer>::width(&self.content)
- }
-
- fn height(&self) -> Length {
- self.height
- }
+/// Computes the layout of a [`Scrollable`].
+pub fn layout<Renderer>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ height: Length,
+ layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
+) -> layout::Node {
+ let limits = limits.width(width).height(height);
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- let limits = limits
- .max_height(self.max_height)
- .width(Widget::<Message, Renderer>::width(&self.content))
- .height(self.height);
-
- let child_limits = layout::Limits::new(
- Size::new(limits.min().width, 0.0),
- Size::new(limits.max().width, f32::INFINITY),
- );
+ let child_limits = layout::Limits::new(
+ Size::new(limits.min().width, 0.0),
+ Size::new(limits.max().width, f32::INFINITY),
+ );
- let content = self.content.layout(renderer, &child_limits);
- let size = limits.resolve(content.size());
+ let content = layout_content(renderer, &child_limits);
+ let size = limits.resolve(content.size());
- layout::Node::with_children(size, vec![content])
- }
+ layout::Node::with_children(size, vec![content])
+}
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
-
- let content = layout.children().next().unwrap();
- let content_bounds = content.bounds();
-
- let scrollbar = self.scrollbar(bounds, content_bounds);
- let is_mouse_over_scrollbar = scrollbar
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false);
-
- let event_status = {
- let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
- Point::new(
- cursor_position.x,
- cursor_position.y
- + self.state.offset(bounds, content_bounds) as f32,
- )
- } else {
- // TODO: Make `cursor_position` an `Option<Point>` so we can encode
- // cursor availability.
- // This will probably happen naturally once we add multi-window
- // support.
- Point::new(cursor_position.x, -1.0)
- };
-
- self.content.on_event(
- event.clone(),
- content,
- cursor_position,
- renderer,
- clipboard,
- shell,
+/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`]
+/// accordingly.
+pub fn update<Message>(
+ state: &mut State,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
+ on_scroll: &Option<Box<dyn Fn(f32) -> Message>>,
+ update_content: impl FnOnce(
+ Event,
+ Layout<'_>,
+ Point,
+ &mut dyn Clipboard,
+ &mut Shell<'_, Message>,
+ ) -> event::Status,
+) -> event::Status {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let content = layout.children().next().unwrap();
+ let content_bounds = content.bounds();
+
+ let scrollbar = scrollbar(
+ state,
+ scrollbar_width,
+ scrollbar_margin,
+ scroller_width,
+ bounds,
+ content_bounds,
+ );
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let event_status = {
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(
+ cursor_position.x,
+ cursor_position.y + state.offset(bounds, content_bounds) as f32,
)
+ } else {
+ // TODO: Make `cursor_position` an `Option<Point>` so we can encode
+ // cursor availability.
+ // This will probably happen naturally once we add multi-window
+ // support.
+ Point::new(cursor_position.x, -1.0)
};
- if let event::Status::Captured = event_status {
- return event::Status::Captured;
- }
+ update_content(
+ event.clone(),
+ content,
+ cursor_position,
+ clipboard,
+ shell,
+ )
+ };
+
+ if let event::Status::Captured = event_status {
+ return event::Status::Captured;
+ }
+
+ if is_mouse_over {
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ match delta {
+ mouse::ScrollDelta::Lines { y, .. } => {
+ // TODO: Configurable speed (?)
+ state.scroll(y * 60.0, bounds, content_bounds);
+ }
+ mouse::ScrollDelta::Pixels { y, .. } => {
+ state.scroll(y, bounds, content_bounds);
+ }
+ }
- if is_mouse_over {
- match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
- match delta {
- mouse::ScrollDelta::Lines { y, .. } => {
- // TODO: Configurable speed (?)
- self.state.scroll(y * 60.0, bounds, content_bounds);
- }
- mouse::ScrollDelta::Pixels { y, .. } => {
- self.state.scroll(y, bounds, content_bounds);
- }
+ notify_on_scroll(
+ state,
+ on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+
+ return event::Status::Captured;
+ }
+ Event::Touch(event) => {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ state.scroll_box_touched_at = Some(cursor_position);
}
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ state.scroll_box_touched_at
+ {
+ let delta =
+ cursor_position.y - scroll_box_touched_at.y;
- self.notify_on_scroll(bounds, content_bounds, shell);
+ state.scroll(delta, bounds, content_bounds);
- return event::Status::Captured;
- }
- Event::Touch(event) => {
- match event {
- touch::Event::FingerPressed { .. } => {
- self.state.scroll_box_touched_at =
- Some(cursor_position);
- }
- touch::Event::FingerMoved { .. } => {
- if let Some(scroll_box_touched_at) =
- self.state.scroll_box_touched_at
- {
- let delta =
- cursor_position.y - scroll_box_touched_at.y;
-
- self.state.scroll(
- delta,
- bounds,
- content_bounds,
- );
-
- self.state.scroll_box_touched_at =
- Some(cursor_position);
-
- self.notify_on_scroll(
- bounds,
- content_bounds,
- shell,
- );
- }
- }
- touch::Event::FingerLifted { .. }
- | touch::Event::FingerLost { .. } => {
- self.state.scroll_box_touched_at = None;
+ state.scroll_box_touched_at = Some(cursor_position);
+
+ notify_on_scroll(
+ state,
+ on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
}
}
-
- return event::Status::Captured;
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ state.scroll_box_touched_at = None;
+ }
}
- _ => {}
+
+ return event::Status::Captured;
}
+ _ => {}
}
+ }
+
+ if state.is_scroller_grabbed() {
+ match event {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ state.scroller_grabbed_at = None;
- if self.state.is_scroller_grabbed() {
- match event {
- Event::Mouse(mouse::Event::ButtonReleased(
- mouse::Button::Left,
- ))
- | Event::Touch(touch::Event::FingerLifted { .. })
- | Event::Touch(touch::Event::FingerLost { .. }) => {
- self.state.scroller_grabbed_at = None;
+ return event::Status::Captured;
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if let (Some(scrollbar), Some(scroller_grabbed_at)) =
+ (scrollbar, state.scroller_grabbed_at)
+ {
+ state.scroll_to(
+ scrollbar.scroll_percentage(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
+
+ notify_on_scroll(
+ state,
+ on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
return event::Status::Captured;
}
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- if let (Some(scrollbar), Some(scroller_grabbed_at)) =
- (scrollbar, self.state.scroller_grabbed_at)
+ }
+ _ => {}
+ }
+ } else if is_mouse_over_scrollbar {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if let Some(scrollbar) = scrollbar {
+ if let Some(scroller_grabbed_at) =
+ scrollbar.grab_scroller(cursor_position)
{
- self.state.scroll_to(
+ state.scroll_to(
scrollbar.scroll_percentage(
scroller_grabbed_at,
cursor_position,
@@ -376,50 +342,329 @@ where
content_bounds,
);
- self.notify_on_scroll(bounds, content_bounds, shell);
+ state.scroller_grabbed_at = Some(scroller_grabbed_at);
+
+ notify_on_scroll(
+ state,
+ on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
return event::Status::Captured;
}
}
- _ => {}
}
- } else if is_mouse_over_scrollbar {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(
- mouse::Button::Left,
- ))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- if let Some(scrollbar) = scrollbar {
- if let Some(scroller_grabbed_at) =
- scrollbar.grab_scroller(cursor_position)
- {
- self.state.scroll_to(
- scrollbar.scroll_percentage(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
+ _ => {}
+ }
+ }
- self.state.scroller_grabbed_at =
- Some(scroller_grabbed_at);
+ event::Status::Ignored
+}
- self.notify_on_scroll(
- bounds,
- content_bounds,
- shell,
- );
+/// Computes the current [`mouse::Interaction`] of a [`Scrollable`].
+pub fn mouse_interaction(
+ state: &State,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
+ content_interaction: impl FnOnce(
+ Layout<'_>,
+ Point,
+ &Rectangle,
+ ) -> mouse::Interaction,
+) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let scrollbar = scrollbar(
+ state,
+ scrollbar_width,
+ scrollbar_margin,
+ scroller_width,
+ bounds,
+ content_bounds,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
+ mouse::Interaction::Idle
+ } else {
+ let offset = state.offset(bounds, content_bounds);
- return event::Status::Captured;
- }
- }
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(cursor_position.x, cursor_position.y + offset as f32)
+ } else {
+ Point::new(cursor_position.x, -1.0)
+ };
+
+ content_interaction(
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ )
+ }
+}
+
+/// Draws a [`Scrollable`].
+pub fn draw<Renderer>(
+ state: &State,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
+ style_sheet: &dyn StyleSheet,
+ draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle),
+) where
+ Renderer: crate::Renderer,
+{
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let offset = state.offset(bounds, content_bounds);
+ let scrollbar = scrollbar(
+ state,
+ scrollbar_width,
+ scrollbar_margin,
+ scroller_width,
+ bounds,
+ content_bounds,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(cursor_position.x, cursor_position.y + offset as f32)
+ } else {
+ Point::new(cursor_position.x, -1.0)
+ };
+
+ if let Some(scrollbar) = scrollbar {
+ renderer.with_layer(bounds, |renderer| {
+ renderer.with_translation(
+ Vector::new(0.0, -(offset as f32)),
+ |renderer| {
+ draw_content(
+ renderer,
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ );
+ },
+ );
+ });
+
+ let style = if state.is_scroller_grabbed() {
+ style_sheet.dragging()
+ } else if is_mouse_over_scrollbar {
+ style_sheet.hovered()
+ } else {
+ style_sheet.active()
+ };
+
+ let is_scrollbar_visible =
+ style.background.is_some() || style.border_width > 0.0;
+
+ renderer.with_layer(
+ Rectangle {
+ width: bounds.width + 2.0,
+ height: bounds.height + 2.0,
+ ..bounds
+ },
+ |renderer| {
+ if is_scrollbar_visible {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: scrollbar.bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style
+ .background
+ .unwrap_or(Background::Color(Color::TRANSPARENT)),
+ );
}
- _ => {}
- }
- }
- event::Status::Ignored
+ if is_mouse_over
+ || state.is_scroller_grabbed()
+ || is_scrollbar_visible
+ {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: scrollbar.scroller.bounds,
+ border_radius: style.scroller.border_radius,
+ border_width: style.scroller.border_width,
+ border_color: style.scroller.border_color,
+ },
+ style.scroller.color,
+ );
+ }
+ },
+ );
+ } else {
+ draw_content(
+ renderer,
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ );
+ }
+}
+
+fn scrollbar(
+ state: &State,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+) -> Option<Scrollbar> {
+ let offset = state.offset(bounds, content_bounds);
+
+ if content_bounds.height > bounds.height {
+ let outer_width =
+ scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
+
+ let outer_bounds = Rectangle {
+ x: bounds.x + bounds.width - outer_width as f32,
+ y: bounds.y,
+ width: outer_width as f32,
+ height: bounds.height,
+ };
+
+ let scrollbar_bounds = Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + scrollbar_width / 2),
+ y: bounds.y,
+ width: scrollbar_width as f32,
+ height: bounds.height,
+ };
+
+ let ratio = bounds.height / content_bounds.height;
+ let scroller_height = bounds.height * ratio;
+ let y_offset = offset as f32 * ratio;
+
+ let scroller_bounds = Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + scroller_width / 2),
+ y: scrollbar_bounds.y + y_offset,
+ width: scroller_width as f32,
+ height: scroller_height,
+ };
+
+ Some(Scrollbar {
+ outer_bounds,
+ bounds: scrollbar_bounds,
+ scroller: Scroller {
+ bounds: scroller_bounds,
+ },
+ })
+ } else {
+ None
+ }
+}
+
+fn notify_on_scroll<Message>(
+ state: &State,
+ on_scroll: &Option<Box<dyn Fn(f32) -> Message>>,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ shell: &mut Shell<'_, Message>,
+) {
+ if content_bounds.height <= bounds.height {
+ return;
+ }
+
+ if let Some(on_scroll) = on_scroll {
+ shell.publish(on_scroll(
+ state.offset.absolute(bounds, content_bounds)
+ / (content_bounds.height - bounds.height),
+ ));
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Scrollable<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+{
+ fn width(&self) -> Length {
+ Widget::<Message, Renderer>::width(&self.content)
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(
+ renderer,
+ limits,
+ Widget::<Message, Renderer>::width(self),
+ self.height,
+ |renderer, limits| self.content.layout(renderer, limits),
+ )
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ &mut self.state,
+ event,
+ layout,
+ cursor_position,
+ clipboard,
+ shell,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ &self.on_scroll,
+ |event, layout, cursor_position, clipboard, shell| {
+ self.content.on_event(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ },
+ )
}
fn mouse_interaction(
@@ -429,38 +674,22 @@ where
_viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- let bounds = layout.bounds();
- let content_layout = layout.children().next().unwrap();
- let content_bounds = content_layout.bounds();
- let scrollbar = self.scrollbar(bounds, content_bounds);
-
- let is_mouse_over = bounds.contains(cursor_position);
- let is_mouse_over_scrollbar = scrollbar
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false);
-
- if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() {
- mouse::Interaction::Idle
- } else {
- let offset = self.state.offset(bounds, content_bounds);
-
- let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
- Point::new(cursor_position.x, cursor_position.y + offset as f32)
- } else {
- Point::new(cursor_position.x, -1.0)
- };
-
- self.content.mouse_interaction(
- content_layout,
- cursor_position,
- &Rectangle {
- y: bounds.y + offset as f32,
- ..bounds
- },
- renderer,
- )
- }
+ mouse_interaction(
+ &self.state,
+ layout,
+ cursor_position,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ |layout, cursor_position, viewport| {
+ self.content.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ },
+ )
}
fn draw(
@@ -471,103 +700,25 @@ where
cursor_position: Point,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let content_layout = layout.children().next().unwrap();
- let content_bounds = content_layout.bounds();
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = self.scrollbar(bounds, content_bounds);
-
- let is_mouse_over = bounds.contains(cursor_position);
- let is_mouse_over_scrollbar = scrollbar
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false);
-
- let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
- Point::new(cursor_position.x, cursor_position.y + offset as f32)
- } else {
- Point::new(cursor_position.x, -1.0)
- };
-
- if let Some(scrollbar) = scrollbar {
- renderer.with_layer(bounds, |renderer| {
- renderer.with_translation(
- Vector::new(0.0, -(offset as f32)),
- |renderer| {
- self.content.draw(
- renderer,
- style,
- content_layout,
- cursor_position,
- &Rectangle {
- y: bounds.y + offset as f32,
- ..bounds
- },
- );
- },
- );
- });
-
- let style = if self.state.is_scroller_grabbed() {
- self.style_sheet.dragging()
- } else if is_mouse_over_scrollbar {
- self.style_sheet.hovered()
- } else {
- self.style_sheet.active()
- };
-
- let is_scrollbar_visible =
- style.background.is_some() || style.border_width > 0.0;
-
- renderer.with_layer(
- Rectangle {
- width: bounds.width + 2.0,
- height: bounds.height + 2.0,
- ..bounds
- },
- |renderer| {
- if is_scrollbar_visible {
- renderer.fill_quad(
- renderer::Quad {
- bounds: scrollbar.bounds,
- border_radius: style.border_radius,
- border_width: style.border_width,
- border_color: style.border_color,
- },
- style.background.unwrap_or(Background::Color(
- Color::TRANSPARENT,
- )),
- );
- }
-
- if is_mouse_over
- || self.state.is_scroller_grabbed()
- || is_scrollbar_visible
- {
- renderer.fill_quad(
- renderer::Quad {
- bounds: scrollbar.scroller.bounds,
- border_radius: style.scroller.border_radius,
- border_width: style.scroller.border_width,
- border_color: style.scroller.border_color,
- },
- style.scroller.color,
- );
- }
- },
- );
- } else {
- self.content.draw(
- renderer,
- style,
- content_layout,
- cursor_position,
- &Rectangle {
- y: bounds.y + offset as f32,
- ..bounds
- },
- );
- }
+ draw(
+ &self.state,
+ renderer,
+ layout,
+ cursor_position,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ self.style_sheet.as_ref(),
+ |renderer, layout, cursor_position, viewport| {
+ self.content.draw(
+ renderer,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ )
+ },
+ )
}
fn overlay(
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 289f75f5..4c56083e 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -142,6 +142,207 @@ where
}
}
+/// Processes an [`Event`] and updates the [`State`] of a [`Slider`]
+/// accordingly.
+pub fn update<Message, T>(
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ shell: &mut Shell<'_, Message>,
+ state: &mut State,
+ value: &mut T,
+ range: &RangeInclusive<T>,
+ step: T,
+ on_change: &dyn Fn(T) -> Message,
+ on_release: &Option<Message>,
+) -> event::Status
+where
+ T: Copy + Into<f64> + num_traits::FromPrimitive,
+ Message: Clone,
+{
+ let is_dragging = state.is_dragging;
+
+ let mut change = || {
+ let bounds = layout.bounds();
+ let new_value = if cursor_position.x <= bounds.x {
+ *range.start()
+ } else if cursor_position.x >= bounds.x + bounds.width {
+ *range.end()
+ } else {
+ let step = step.into();
+ let start = (*range.start()).into();
+ let end = (*range.end()).into();
+
+ 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 let Some(value) = T::from_f64(value) {
+ value
+ } else {
+ return;
+ }
+ };
+
+ if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
+ shell.publish((on_change)(new_value));
+
+ *value = new_value;
+ }
+ };
+
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if layout.bounds().contains(cursor_position) {
+ 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);
+ }
+ state.is_dragging = false;
+
+ return event::Status::Captured;
+ }
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if is_dragging {
+ change();
+
+ return event::Status::Captured;
+ }
+ }
+ _ => {}
+ }
+
+ event::Status::Ignored
+}
+
+/// Draws a [`Slider`].
+pub fn draw<T>(
+ renderer: &mut impl crate::Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ state: &State,
+ value: T,
+ range: &RangeInclusive<T>,
+ style_sheet: &dyn StyleSheet,
+) where
+ T: Into<f64> + Copy,
+{
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if state.is_dragging {
+ style_sheet.dragging()
+ } else if is_mouse_over {
+ style_sheet.hovered()
+ } else {
+ style_sheet.active()
+ };
+
+ let rail_y = bounds.y + (bounds.height / 2.0).round();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y,
+ width: bounds.width,
+ height: 2.0,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail_colors.0,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y + 2.0,
+ width: bounds.width,
+ height: 2.0,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(style.rail_colors.1),
+ );
+
+ let (handle_width, handle_height, handle_border_radius) = match style
+ .handle
+ .shape
+ {
+ HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), f32::from(bounds.height), border_radius),
+ };
+
+ 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 handle_offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.width - handle_width) * (value - range_start)
+ / (range_end - range_start)
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round(),
+ y: rail_y - handle_height / 2.0,
+ width: handle_width,
+ height: handle_height,
+ },
+ border_radius: handle_border_radius,
+ border_width: style.handle.border_width,
+ border_color: style.handle.border_color,
+ },
+ style.handle.color,
+ );
+}
+
+/// Computes the current [`mouse::Interaction`] of a [`Slider`].
+pub fn mouse_interaction(
+ layout: Layout<'_>,
+ cursor_position: Point,
+ state: &State,
+) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if state.is_dragging {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::default()
+ }
+}
+
/// The local state of a [`Slider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
@@ -192,73 +393,18 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- let is_dragging = self.state.is_dragging;
-
- let mut change = || {
- let bounds = layout.bounds();
- let new_value = if cursor_position.x <= bounds.x {
- *self.range.start()
- } else if cursor_position.x >= bounds.x + bounds.width {
- *self.range.end()
- } else {
- let step = self.step.into();
- let start = (*self.range.start()).into();
- let end = (*self.range.end()).into();
-
- 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 let Some(value) = T::from_f64(value) {
- value
- } else {
- return;
- }
- };
-
- if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
- shell.publish((self.on_change)(new_value));
-
- self.value = new_value;
- }
- };
-
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
- change();
- self.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) = self.on_release.clone() {
- shell.publish(on_release);
- }
- self.state.is_dragging = false;
-
- return event::Status::Captured;
- }
- }
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- if is_dragging {
- change();
-
- return event::Status::Captured;
- }
- }
- _ => {}
- }
-
- event::Status::Ignored
+ update(
+ event,
+ layout,
+ cursor_position,
+ shell,
+ &mut self.state,
+ &mut self.value,
+ &self.range,
+ self.step,
+ self.on_change.as_ref(),
+ &self.on_release,
+ )
}
fn draw(
@@ -269,90 +415,15 @@ where
cursor_position: Point,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
-
- let style = if self.state.is_dragging {
- self.style_sheet.dragging()
- } else if is_mouse_over {
- self.style_sheet.hovered()
- } else {
- self.style_sheet.active()
- };
-
- let rail_y = bounds.y + (bounds.height / 2.0).round();
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: bounds.x,
- y: rail_y,
- width: bounds.width,
- height: 2.0,
- },
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- style.rail_colors.0,
- );
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: bounds.x,
- y: rail_y + 2.0,
- width: bounds.width,
- height: 2.0,
- },
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- Background::Color(style.rail_colors.1),
- );
-
- let (handle_width, handle_height, handle_border_radius) = match style
- .handle
- .shape
- {
- HandleShape::Circle { radius } => {
- (radius * 2.0, radius * 2.0, radius)
- }
- HandleShape::Rectangle {
- width,
- border_radius,
- } => (f32::from(width), f32::from(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 handle_offset = if range_start >= range_end {
- 0.0
- } else {
- (bounds.width - handle_width) * (value - range_start)
- / (range_end - range_start)
- };
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: bounds.x + handle_offset.round(),
- y: rail_y - handle_height / 2.0,
- width: handle_width,
- height: handle_height,
- },
- border_radius: handle_border_radius,
- border_width: style.handle.border_width,
- border_color: style.handle.border_color,
- },
- style.handle.color,
- );
+ draw(
+ renderer,
+ layout,
+ cursor_position,
+ &self.state,
+ self.value,
+ &self.range,
+ self.style_sheet.as_ref(),
+ )
}
fn mouse_interaction(
@@ -362,16 +433,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
-
- if self.state.is_dragging {
- mouse::Interaction::Grabbing
- } else if is_mouse_over {
- mouse::Interaction::Grab
- } else {
- mouse::Interaction::default()
- }
+ mouse_interaction(layout, cursor_position, &self.state)
}
}
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index e30e2343..d13d6ef1 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -24,8 +24,6 @@ use crate::{
Shell, Size, Vector, Widget,
};
-use std::u32;
-
pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text.
@@ -61,10 +59,9 @@ pub struct TextInput<'a, Message, Renderer: text::Renderer> {
is_secure: bool,
font: Renderer::Font,
width: Length,
- max_width: u32,
padding: Padding,
size: Option<u16>,
- on_change: Box<dyn Fn(String) -> Message>,
+ on_change: Box<dyn Fn(String) -> Message + 'a>,
on_submit: Option<Message>,
style_sheet: Box<dyn StyleSheet + 'a>,
}
@@ -88,7 +85,7 @@ where
on_change: F,
) -> Self
where
- F: 'static + Fn(String) -> Message,
+ F: 'a + Fn(String) -> Message,
{
TextInput {
state,
@@ -97,7 +94,6 @@ where
is_secure: false,
font: Default::default(),
width: Length::Fill,
- max_width: u32::MAX,
padding: Padding::ZERO,
size: None,
on_change: Box::new(on_change),
@@ -126,12 +122,6 @@ where
self
}
- /// Sets the maximum width of the [`TextInput`].
- pub fn max_width(mut self, max_width: u32) -> Self {
- self.max_width = max_width;
- self
- }
-
/// Sets the [`Padding`] of the [`TextInput`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
@@ -166,520 +156,313 @@ where
}
}
-impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+/// Computes the layout of a [`TextInput`].
+pub fn layout<Renderer>(
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ padding: Padding,
+ size: Option<u16>,
+) -> layout::Node
where
Renderer: text::Renderer,
{
- /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
- /// [`Value`] if provided.
- pub fn draw(
- &self,
- renderer: &mut Renderer,
- layout: Layout<'_>,
- cursor_position: Point,
- value: Option<&Value>,
- ) {
- let value = value.unwrap_or(&self.value);
- let secure_value = self.is_secure.then(|| value.secure());
- let value = secure_value.as_ref().unwrap_or(&value);
+ let text_size = size.unwrap_or(renderer.default_size());
- let bounds = layout.bounds();
- let text_bounds = layout.children().next().unwrap().bounds();
+ let limits = limits
+ .pad(padding)
+ .width(width)
+ .height(Length::Units(text_size));
- let is_mouse_over = bounds.contains(cursor_position);
+ let mut text = layout::Node::new(limits.resolve(Size::ZERO));
+ text.move_to(Point::new(padding.left.into(), padding.top.into()));
- let style = if self.state.is_focused() {
- self.style_sheet.focused()
- } else if is_mouse_over {
- self.style_sheet.hovered()
- } else {
- self.style_sheet.active()
- };
-
- renderer.fill_quad(
- renderer::Quad {
- bounds,
- border_radius: style.border_radius,
- border_width: style.border_width,
- border_color: style.border_color,
- },
- style.background,
- );
-
- let text = value.to_string();
- let size = self.size.unwrap_or(renderer.default_size());
-
- let (cursor, offset) = if self.state.is_focused() {
- match self.state.cursor.state(&value) {
- cursor::State::Index(position) => {
- let (text_value_width, offset) =
- measure_cursor_and_scroll_offset(
- renderer,
- text_bounds,
- &value,
- size,
- position,
- self.font.clone(),
- );
-
- (
- Some((
- renderer::Quad {
- bounds: Rectangle {
- x: text_bounds.x + text_value_width,
- y: text_bounds.y,
- width: 1.0,
- height: text_bounds.height,
- },
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- self.style_sheet.value_color(),
- )),
- 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(
- renderer,
- text_bounds,
- &value,
- size,
- left,
- self.font.clone(),
- );
-
- let (right_position, right_offset) =
- measure_cursor_and_scroll_offset(
- renderer,
- text_bounds,
- &value,
- size,
- right,
- self.font.clone(),
- );
-
- 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,
- },
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- self.style_sheet.selection_color(),
- )),
- if end == right {
- right_offset
- } else {
- left_offset
- },
- )
- }
- }
- } else {
- (None, 0.0)
- };
-
- let text_width = renderer.measure_width(
- if text.is_empty() {
- &self.placeholder
- } else {
- &text
- },
- size,
- self.font.clone(),
- );
-
- let render = |renderer: &mut Renderer| {
- if let Some((cursor, color)) = cursor {
- renderer.fill_quad(cursor, color);
- }
-
- renderer.fill_text(Text {
- content: if text.is_empty() {
- &self.placeholder
- } else {
- &text
- },
- color: if text.is_empty() {
- self.style_sheet.placeholder_color()
- } else {
- self.style_sheet.value_color()
- },
- font: self.font.clone(),
- bounds: Rectangle {
- y: text_bounds.center_y(),
- width: f32::INFINITY,
- ..text_bounds
- },
- size: f32::from(size),
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Center,
- });
- };
-
- if text_width > text_bounds.width {
- renderer.with_layer(text_bounds, |renderer| {
- renderer.with_translation(Vector::new(-offset, 0.0), render)
- });
- } else {
- render(renderer);
- }
- }
+ layout::Node::with_children(text.size().pad(padding), vec![text])
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for TextInput<'a, Message, Renderer>
+/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
+/// accordingly.
+pub fn update<'a, Message, Renderer>(
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ value: &mut Value,
+ size: Option<u16>,
+ font: &Renderer::Font,
+ is_secure: bool,
+ on_change: &dyn Fn(String) -> Message,
+ on_submit: &Option<Message>,
+ state: impl FnOnce() -> &'a mut State,
+) -> event::Status
where
Message: Clone,
Renderer: text::Renderer,
{
- fn width(&self) -> Length {
- self.width
- }
-
- fn height(&self) -> Length {
- Length::Shrink
- }
-
- fn layout(
- &self,
- renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- let text_size = self.size.unwrap_or(renderer.default_size());
-
- let limits = limits
- .pad(self.padding)
- .width(self.width)
- .max_width(self.max_width)
- .height(Length::Units(text_size));
-
- let mut text = layout::Node::new(limits.resolve(Size::ZERO));
- text.move_to(Point::new(
- self.padding.left.into(),
- self.padding.top.into(),
- ));
-
- layout::Node::with_children(text.size().pad(self.padding), vec![text])
- }
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- renderer: &Renderer,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let is_clicked = layout.bounds().contains(cursor_position);
-
- self.state.is_focused = is_clicked;
-
- if is_clicked {
- let text_layout = layout.children().next().unwrap();
- let target = cursor_position.x - text_layout.bounds().x;
-
- let click = mouse::Click::new(
- cursor_position,
- self.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(
- renderer,
- text_layout.bounds(),
- self.font.clone(),
- self.size,
- &value,
- &self.state,
- target,
- )
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let state = state();
+ let is_clicked = layout.bounds().contains(cursor_position);
+
+ state.is_focused = is_clicked;
+
+ if is_clicked {
+ 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 {
- None
+ value.clone()
};
- self.state.cursor.move_to(position.unwrap_or(0));
- self.state.is_dragging = true;
- }
- click::Kind::Double => {
- if self.is_secure {
- self.state.cursor.select_all(&self.value);
- } else {
- let position = find_cursor_position(
- renderer,
- text_layout.bounds(),
- self.font.clone(),
- self.size,
- &self.value,
- &self.state,
- target,
- )
- .unwrap_or(0);
-
- self.state.cursor.select_range(
- self.value.previous_start_of_word(position),
- self.value.next_end_of_word(position),
- );
- }
+ find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ font.clone(),
+ size,
+ &value,
+ state,
+ target,
+ )
+ } else {
+ None
+ };
- self.state.is_dragging = false;
- }
- click::Kind::Triple => {
- self.state.cursor.select_all(&self.value);
- self.state.is_dragging = false;
+ state.cursor.move_to(position.unwrap_or(0));
+ state.is_dragging = true;
+ }
+ click::Kind::Double => {
+ if is_secure {
+ state.cursor.select_all(value);
+ } else {
+ let position = find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ font.clone(),
+ size,
+ 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;
}
+ }
- self.state.last_click = Some(click);
+ 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 { .. }) => {
- self.state.is_dragging = false;
+ return event::Status::Captured;
}
- Event::Mouse(mouse::Event::CursorMoved { position })
- | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
- if self.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()
- };
+ }
+ 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();
- let position = find_cursor_position(
- renderer,
- text_layout.bounds(),
- self.font.clone(),
- self.size,
- &value,
- &self.state,
- target,
- )
- .unwrap_or(0);
+ if state.is_dragging {
+ let text_layout = layout.children().next().unwrap();
+ let target = position.x - text_layout.bounds().x;
- self.state.cursor.select_range(
- self.state.cursor.start(&value),
- position,
- );
+ let value = if is_secure {
+ value.secure()
+ } else {
+ value.clone()
+ };
+
+ let position = find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ font.clone(),
+ size,
+ &value,
+ state,
+ target,
+ )
+ .unwrap_or(0);
+
+ state
+ .cursor
+ .select_range(state.cursor.start(&value), position);
- return event::Status::Captured;
- }
+ return event::Status::Captured;
}
- Event::Keyboard(keyboard::Event::CharacterReceived(c))
- if self.state.is_focused
- && self.state.is_pasting.is_none()
- && !self.state.keyboard_modifiers.command()
- && !c.is_control() =>
+ }
+ Event::Keyboard(keyboard::Event::CharacterReceived(c)) => {
+ let state = state();
+
+ if state.is_focused
+ && state.is_pasting.is_none()
+ && !state.keyboard_modifiers.command()
+ && !c.is_control()
{
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(value, &mut state.cursor);
editor.insert(c);
- let message = (self.on_change)(editor.contents());
+ let message = (on_change)(editor.contents());
shell.publish(message);
return event::Status::Captured;
}
- Event::Keyboard(keyboard::Event::KeyPressed {
- key_code, ..
- }) if self.state.is_focused => {
- let modifiers = self.state.keyboard_modifiers;
+ }
+ Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => {
+ let state = state();
+
+ if state.is_focused {
+ let modifiers = state.keyboard_modifiers;
match key_code {
keyboard::KeyCode::Enter
| keyboard::KeyCode::NumpadEnter => {
- if let Some(on_submit) = self.on_submit.clone() {
+ if let Some(on_submit) = on_submit.clone() {
shell.publish(on_submit);
}
}
keyboard::KeyCode::Backspace => {
if platform::is_jump_modifier_pressed(modifiers)
- && self
- .state
- .cursor
- .selection(&self.value)
- .is_none()
+ && state.cursor.selection(value).is_none()
{
- if self.is_secure {
- let cursor_pos =
- self.state.cursor.end(&self.value);
- self.state.cursor.select_range(0, cursor_pos);
+ if is_secure {
+ let cursor_pos = state.cursor.end(value);
+ state.cursor.select_range(0, cursor_pos);
} else {
- self.state
- .cursor
- .select_left_by_words(&self.value);
+ state.cursor.select_left_by_words(value);
}
}
- let mut editor = Editor::new(
- &mut self.value,
- &mut self.state.cursor,
- );
-
+ let mut editor = Editor::new(value, &mut state.cursor);
editor.backspace();
- let message = (self.on_change)(editor.contents());
+ let message = (on_change)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Delete => {
if platform::is_jump_modifier_pressed(modifiers)
- && self
- .state
- .cursor
- .selection(&self.value)
- .is_none()
+ && state.cursor.selection(value).is_none()
{
- if self.is_secure {
- let cursor_pos =
- self.state.cursor.end(&self.value);
- self.state
+ if is_secure {
+ let cursor_pos = state.cursor.end(value);
+ state
.cursor
- .select_range(cursor_pos, self.value.len());
+ .select_range(cursor_pos, value.len());
} else {
- self.state
- .cursor
- .select_right_by_words(&self.value);
+ state.cursor.select_right_by_words(value);
}
}
- let mut editor = Editor::new(
- &mut self.value,
- &mut self.state.cursor,
- );
-
+ let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
- let message = (self.on_change)(editor.contents());
+ let message = (on_change)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Left => {
if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
+ && !is_secure
{
if modifiers.shift() {
- self.state
- .cursor
- .select_left_by_words(&self.value);
+ state.cursor.select_left_by_words(value);
} else {
- self.state
- .cursor
- .move_left_by_words(&self.value);
+ state.cursor.move_left_by_words(value);
}
} else if modifiers.shift() {
- self.state.cursor.select_left(&self.value)
+ state.cursor.select_left(value)
} else {
- self.state.cursor.move_left(&self.value);
+ state.cursor.move_left(value);
}
}
keyboard::KeyCode::Right => {
if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
+ && !is_secure
{
if modifiers.shift() {
- self.state
- .cursor
- .select_right_by_words(&self.value);
+ state.cursor.select_right_by_words(value);
} else {
- self.state
- .cursor
- .move_right_by_words(&self.value);
+ state.cursor.move_right_by_words(value);
}
} else if modifiers.shift() {
- self.state.cursor.select_right(&self.value)
+ state.cursor.select_right(value)
} else {
- self.state.cursor.move_right(&self.value);
+ state.cursor.move_right(value);
}
}
keyboard::KeyCode::Home => {
if modifiers.shift() {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- 0,
- );
+ state
+ .cursor
+ .select_range(state.cursor.start(value), 0);
} else {
- self.state.cursor.move_to(0);
+ state.cursor.move_to(0);
}
}
keyboard::KeyCode::End => {
if modifiers.shift() {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- self.value.len(),
+ state.cursor.select_range(
+ state.cursor.start(value),
+ value.len(),
);
} else {
- self.state.cursor.move_to(self.value.len());
+ state.cursor.move_to(value.len());
}
}
keyboard::KeyCode::C
- if self.state.keyboard_modifiers.command() =>
+ if state.keyboard_modifiers.command() =>
{
- match self.state.cursor.selection(&self.value) {
+ match state.cursor.selection(value) {
Some((start, end)) => {
clipboard.write(
- self.value.select(start, end).to_string(),
+ value.select(start, end).to_string(),
);
}
None => {}
}
}
keyboard::KeyCode::X
- if self.state.keyboard_modifiers.command() =>
+ if state.keyboard_modifiers.command() =>
{
- match self.state.cursor.selection(&self.value) {
+ match state.cursor.selection(value) {
Some((start, end)) => {
clipboard.write(
- self.value.select(start, end).to_string(),
+ value.select(start, end).to_string(),
);
}
None => {}
}
- let mut editor = Editor::new(
- &mut self.value,
- &mut self.state.cursor,
- );
-
+ let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
- let message = (self.on_change)(editor.contents());
+ let message = (on_change)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::V => {
- if self.state.keyboard_modifiers.command() {
- let content = match self.state.is_pasting.take() {
+ if state.keyboard_modifiers.command() {
+ let content = match state.is_pasting.take() {
Some(content) => content,
None => {
let content: String = clipboard
@@ -693,32 +476,30 @@ where
}
};
- let mut editor = Editor::new(
- &mut self.value,
- &mut self.state.cursor,
- );
+ let mut editor =
+ Editor::new(value, &mut state.cursor);
editor.paste(content.clone());
- let message = (self.on_change)(editor.contents());
+ let message = (on_change)(editor.contents());
shell.publish(message);
- self.state.is_pasting = Some(content);
+ state.is_pasting = Some(content);
} else {
- self.state.is_pasting = None;
+ state.is_pasting = None;
}
}
keyboard::KeyCode::A
- if self.state.keyboard_modifiers.command() =>
+ if state.keyboard_modifiers.command() =>
{
- self.state.cursor.select_all(&self.value);
+ state.cursor.select_all(value);
}
keyboard::KeyCode::Escape => {
- self.state.is_focused = false;
- self.state.is_dragging = false;
- self.state.is_pasting = None;
+ state.is_focused = false;
+ state.is_dragging = false;
+ state.is_pasting = None;
- self.state.keyboard_modifiers =
+ state.keyboard_modifiers =
keyboard::Modifiers::default();
}
keyboard::KeyCode::Tab
@@ -728,15 +509,17 @@ where
}
_ => {}
}
-
- return event::Status::Captured;
}
- Event::Keyboard(keyboard::Event::KeyReleased {
- key_code, ..
- }) if self.state.is_focused => {
+
+ return event::Status::Captured;
+ }
+ Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => {
+ let state = state();
+
+ if state.is_focused {
match key_code {
keyboard::KeyCode::V => {
- self.state.is_pasting = None;
+ state.is_pasting = None;
}
keyboard::KeyCode::Tab
| keyboard::KeyCode::Up
@@ -748,15 +531,246 @@ where
return event::Status::Captured;
}
- Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers))
- if self.state.is_focused =>
- {
- self.state.keyboard_modifiers = modifiers;
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
+ let state = state();
+
+ if state.is_focused {
+ state.keyboard_modifiers = modifiers;
+ }
+ }
+ _ => {}
+ }
+
+ event::Status::Ignored
+}
+
+/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
+/// [`Value`] if provided.
+pub fn draw<Renderer>(
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ state: &State,
+ value: &Value,
+ placeholder: &str,
+ size: Option<u16>,
+ font: &Renderer::Font,
+ is_secure: bool,
+ style_sheet: &dyn StyleSheet,
+) where
+ 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 text_bounds = layout.children().next().unwrap().bounds();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if state.is_focused() {
+ style_sheet.focused()
+ } else if is_mouse_over {
+ style_sheet.hovered()
+ } else {
+ style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
+
+ let text = value.to_string();
+ let size = size.unwrap_or(renderer.default_size());
+
+ let (cursor, offset) = if state.is_focused() {
+ match state.cursor.state(&value) {
+ cursor::State::Index(position) => {
+ let (text_value_width, offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ position,
+ font.clone(),
+ );
+
+ (
+ Some((
+ renderer::Quad {
+ bounds: Rectangle {
+ x: text_bounds.x + text_value_width,
+ y: text_bounds.y,
+ width: 1.0,
+ height: text_bounds.height,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style_sheet.value_color(),
+ )),
+ 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(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ left,
+ font.clone(),
+ );
+
+ let (right_position, right_offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ right,
+ font.clone(),
+ );
+
+ 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,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style_sheet.selection_color(),
+ )),
+ if end == right {
+ right_offset
+ } else {
+ left_offset
+ },
+ )
}
- _ => {}
}
+ } else {
+ (None, 0.0)
+ };
+
+ let text_width = renderer.measure_width(
+ if text.is_empty() { placeholder } else { &text },
+ size,
+ font.clone(),
+ );
+
+ let render = |renderer: &mut Renderer| {
+ if let Some((cursor, color)) = cursor {
+ renderer.fill_quad(cursor, color);
+ }
+
+ renderer.fill_text(Text {
+ content: if text.is_empty() { placeholder } else { &text },
+ color: if text.is_empty() {
+ style_sheet.placeholder_color()
+ } else {
+ style_sheet.value_color()
+ },
+ font: font.clone(),
+ bounds: Rectangle {
+ y: text_bounds.center_y(),
+ width: f32::INFINITY,
+ ..text_bounds
+ },
+ size: f32::from(size),
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ };
+
+ if text_width > text_bounds.width {
+ renderer.with_layer(text_bounds, |renderer| {
+ renderer.with_translation(Vector::new(-offset, 0.0), render)
+ });
+ } else {
+ render(renderer);
+ }
+}
- event::Status::Ignored
+/// Computes the current [`mouse::Interaction`] of the [`TextInput`].
+pub fn mouse_interaction(
+ layout: Layout<'_>,
+ cursor_position: Point,
+) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Text
+ } else {
+ mouse::Interaction::default()
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: text::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ layout(renderer, limits, self.width, self.padding, self.size)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ update(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ &mut self.value,
+ self.size,
+ &self.font,
+ self.is_secure,
+ self.on_change.as_ref(),
+ &self.on_submit,
+ || &mut self.state,
+ )
}
fn mouse_interaction(
@@ -766,11 +780,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
- mouse::Interaction::Text
- } else {
- mouse::Interaction::default()
- }
+ mouse_interaction(layout, cursor_position)
}
fn draw(
@@ -781,7 +791,18 @@ where
cursor_position: Point,
_viewport: &Rectangle,
) {
- self.draw(renderer, layout, cursor_position, None)
+ draw(
+ renderer,
+ layout,
+ cursor_position,
+ &self.state,
+ &self.value,
+ &self.placeholder,
+ self.size,
+ &self.font,
+ self.is_secure,
+ self.style_sheet.as_ref(),
+ )
}
}
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
index 48237edb..a7847871 100644
--- a/native/src/widget/toggler.rs
+++ b/native/src/widget/toggler.rs
@@ -32,7 +32,7 @@ pub use iced_style::toggler::{Style, StyleSheet};
#[allow(missing_debug_implementations)]
pub struct Toggler<'a, Message, Renderer: text::Renderer> {
is_active: bool,
- on_toggle: Box<dyn Fn(bool) -> Message>,
+ on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
label: Option<String>,
width: Length,
size: u16,
@@ -61,7 +61,7 @@ impl<'a, Message, Renderer: text::Renderer> Toggler<'a, Message, Renderer> {
f: F,
) -> Self
where
- F: 'static + Fn(bool) -> Message,
+ F: 'a + Fn(bool) -> Message,
{
Toggler {
is_active,