From 897188317b5875cc00a0f1c797790df8ac13687f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 17:50:12 +0700 Subject: Rename `iced_virtual` to `iced_pure` `virtual` is a reserved keyword in Rust :grimacing: --- pure/src/widget/button.rs | 272 +++++++++++++++++++++++++++++++++++++++++++++ pure/src/widget/column.rs | 220 ++++++++++++++++++++++++++++++++++++ pure/src/widget/element.rs | 21 ++++ pure/src/widget/text.rs | 185 ++++++++++++++++++++++++++++++ pure/src/widget/tree.rs | 58 ++++++++++ 5 files changed, 756 insertions(+) create mode 100644 pure/src/widget/button.rs create mode 100644 pure/src/widget/column.rs create mode 100644 pure/src/widget/element.rs create mode 100644 pure/src/widget/text.rs create mode 100644 pure/src/widget/tree.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs new file mode 100644 index 00000000..ece90811 --- /dev/null +++ b/pure/src/widget/button.rs @@ -0,0 +1,272 @@ +use crate::widget::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::touch; +use iced_native::{ + Background, Clipboard, Color, Hasher, Layout, Length, Padding, Point, + Rectangle, Shell, Vector, +}; +use iced_style::button::StyleSheet; + +use std::any::Any; + +pub struct Button { + content: Element, + on_press: Option, + style_sheet: Box, + width: Length, + height: Length, + padding: Padding, +} + +impl Button { + pub fn new(content: impl Into>) -> Self { + Button { + content: content.into(), + on_press: None, + style_sheet: Default::default(), + width: Length::Shrink, + height: Length::Shrink, + padding: Padding::new(5), + } + } + + pub fn on_press(mut self, on_press: Message) -> Self { + self.on_press = Some(on_press); + self + } +} + +impl Widget for Button +where + Message: 'static + Clone, + Renderer: 'static + iced_native::Renderer, +{ + fn tag(&self) -> std::any::TypeId { + std::any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(State { is_pressed: false }) + } + + fn children(&self) -> &[Element] { + std::slice::from_ref(&self.content) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.tag().hash(state); + self.width.hash(state); + self.content.as_widget().hash_layout(state); + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .width(self.width) + .height(self.height) + .pad(self.padding); + + let mut content = self.content.as_widget().layout(renderer, &limits); + content.move_to(Point::new( + self.padding.left.into(), + self.padding.top.into(), + )); + + let size = limits.resolve(content.size()).pad(self.padding); + + layout::Node::with_children(size, vec![content]) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let state = if let Some(state) = tree.state.downcast_mut::() { + state + } else { + return event::Status::Ignored; + }; + + if let event::Status::Captured = self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) { + return event::Status::Captured; + } + + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if self.on_press.is_some() { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + state.is_pressed = true; + + return event::Status::Captured; + } + } + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + if let Some(on_press) = self.on_press.clone() { + let bounds = layout.bounds(); + + if state.is_pressed { + state.is_pressed = false; + + if bounds.contains(cursor_position) { + shell.publish(on_press); + } + + return event::Status::Captured; + } + } + } + Event::Touch(touch::Event::FingerLost { .. }) => { + state.is_pressed = false; + } + _ => {} + } + + event::Status::Ignored + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + let state = if let Some(state) = tree.state.downcast_ref::() { + state + } else { + return; + }; + + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + + let is_mouse_over = bounds.contains(cursor_position); + let is_disabled = self.on_press.is_none(); + + let styling = if is_disabled { + self.style_sheet.disabled() + } else if is_mouse_over { + if state.is_pressed { + self.style_sheet.pressed() + } else { + self.style_sheet.hovered() + } + } else { + self.style_sheet.active() + }; + + if styling.background.is_some() || styling.border_width > 0.0 { + if styling.shadow_offset != Vector::default() { + // TODO: Implement proper shadow support + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + ..bounds + }, + border_radius: styling.border_radius, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color([0.0, 0.0, 0.0, 0.5].into()), + ); + } + + renderer.fill_quad( + renderer::Quad { + bounds, + border_radius: styling.border_radius, + border_width: styling.border_width, + border_color: styling.border_color, + }, + styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + ); + } + + self.content.as_widget().draw( + &tree.children[0], + renderer, + &renderer::Style { + text_color: styling.text_color, + }, + content_layout, + cursor_position, + &bounds, + ); + } + + fn mouse_interaction( + &self, + _tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _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() + } + } +} + +#[derive(Debug, Clone)] +struct State { + is_pressed: bool, +} + +impl Into> + for Button +where + Message: Clone + 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn into(self) -> Element { + Element::new(self) + } +} diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs new file mode 100644 index 00000000..2f70282a --- /dev/null +++ b/pure/src/widget/column.rs @@ -0,0 +1,220 @@ +use crate::flex; +use crate::widget::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::{ + Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, +}; + +use std::any::{self, Any}; + +pub struct Column { + spacing: u16, + padding: Padding, + width: Length, + height: Length, + align_items: Alignment, + children: Vec>, +} + +impl<'a, Message, Renderer> Column { + pub fn new() -> Self { + Self::with_children(Vec::new()) + } + + pub fn with_children(children: Vec>) -> Self { + Column { + spacing: 0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + align_items: Alignment::Start, + children, + } + } + + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + pub fn align_items(mut self, align: Alignment) -> Self { + self.align_items = align; + self + } + + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + self.children.push(child.into()); + self + } +} + +impl Widget for Column +where + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> any::TypeId { + struct Marker; + any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children(&self) -> &[Element] { + &self.children + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + flex::resolve( + flex::Axis::Vertical, + renderer, + &limits, + self.padding, + self.spacing as f32, + self.align_items, + &self.children, + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, + layout, + cursor_position, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child.as_widget().draw( + state, + renderer, + style, + layout, + cursor_position, + viewport, + ); + } + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.tag().hash(state); + self.width.hash(state); + self.height.hash(state); + self.align_items.hash(state); + self.spacing.hash(state); + self.padding.hash(state); + + for child in &self.children { + child.as_widget().hash_layout(state); + } + } +} + +impl Into> + for Column +where + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn into(self) -> Element { + Element::new(self) + } +} diff --git a/pure/src/widget/element.rs b/pure/src/widget/element.rs new file mode 100644 index 00000000..5e40d488 --- /dev/null +++ b/pure/src/widget/element.rs @@ -0,0 +1,21 @@ +use crate::Widget; + +pub struct Element { + widget: Box>, +} + +impl Element { + pub fn new(widget: impl Widget + 'static) -> Self { + Self { + widget: Box::new(widget), + } + } + + pub fn as_widget(&self) -> &dyn Widget { + self.widget.as_ref() + } + + pub fn as_widget_mut(&mut self) -> &mut dyn Widget { + self.widget.as_mut() + } +} diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs new file mode 100644 index 00000000..e3a7d299 --- /dev/null +++ b/pure/src/widget/text.rs @@ -0,0 +1,185 @@ +use crate::{Element, Tree, Widget}; + +use iced_native::alignment; +use iced_native::layout::{self, Layout}; +use iced_native::renderer; +use iced_native::text; +use iced_native::{Color, Hasher, Length, Point, Rectangle, Size}; + +use std::any::{self, Any}; + +pub struct Text +where + Renderer: text::Renderer, +{ + content: String, + size: Option, + color: Option, + font: Renderer::Font, + width: Length, + height: Length, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, +} + +impl Text { + /// Create a new fragment of [`Text`] with the given contents. + pub fn new>(label: T) -> Self { + Text { + content: label.into(), + size: None, + color: None, + font: Default::default(), + width: Length::Shrink, + height: Length::Shrink, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + } + } + + /// Sets the size of the [`Text`]. + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the [`Color`] of the [`Text`]. + pub fn color>(mut self, color: C) -> Self { + self.color = Some(color.into()); + self + } + + /// Sets the [`Font`] of the [`Text`]. + /// + /// [`Font`]: Renderer::Font + pub fn font(mut self, font: impl Into) -> Self { + self.font = font.into(); + self + } + + /// Sets the width of the [`Text`] boundaries. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Text`] boundaries. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the [`HorizontalAlignment`] of the [`Text`]. + pub fn horizontal_alignment( + mut self, + alignment: alignment::Horizontal, + ) -> Self { + self.horizontal_alignment = alignment; + self + } + + /// Sets the [`VerticalAlignment`] of the [`Text`]. + pub fn vertical_alignment( + mut self, + alignment: alignment::Vertical, + ) -> Self { + self.vertical_alignment = alignment; + self + } +} + +impl Widget for Text +where + Renderer: text::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children(&self) -> &[Element] { + &[] + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + let size = self.size.unwrap_or(renderer.default_size()); + + let bounds = limits.max(); + + let (width, height) = + renderer.measure(&self.content, size, self.font.clone(), bounds); + + let size = limits.resolve(Size::new(width, height)); + + layout::Node::new(size) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + ) { + iced_native::widget::text::draw( + renderer, + style, + layout, + &self.content, + self.font.clone(), + self.size, + self.color, + self.horizontal_alignment, + self.vertical_alignment, + ); + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + struct Marker; + std::any::TypeId::of::().hash(state); + + self.content.hash(state); + self.size.hash(state); + self.width.hash(state); + self.height.hash(state); + } +} + +impl Into> for Text +where + Renderer: text::Renderer + 'static, +{ + fn into(self) -> Element { + Element::new(self) + } +} + +impl Into> for &'static str +where + Renderer: text::Renderer + 'static, +{ + fn into(self) -> Element { + Text::new(self).into() + } +} diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs new file mode 100644 index 00000000..75f50a2f --- /dev/null +++ b/pure/src/widget/tree.rs @@ -0,0 +1,58 @@ +use crate::widget::Element; + +use std::any::Any; +use std::marker::PhantomData; + +pub struct Tree { + pub state: Box, + pub children: Vec>, + types_: PhantomData<(Message, Renderer)>, +} + +impl Tree { + pub fn new(element: &Element) -> Self { + Self { + state: element.as_widget().state(), + children: element + .as_widget() + .children() + .iter() + .map(Self::new) + .collect(), + types_: PhantomData, + } + } + + pub fn diff( + &mut self, + current: &Element, + new: &Element, + ) { + if current.as_widget().tag() == new.as_widget().tag() { + let current_children = current.as_widget().children(); + let new_children = new.as_widget().children(); + + if current_children.len() > new_children.len() { + self.children.truncate(new_children.len()); + } + + for (child_state, (current, new)) in self + .children + .iter_mut() + .zip(current_children.iter().zip(new_children.iter())) + { + child_state.diff(current, new); + } + + if current_children.len() < new_children.len() { + self.children.extend( + new_children[current_children.len()..] + .iter() + .map(Self::new), + ); + } + } else { + *self = Self::new(new); + } + } +} -- cgit From 43a7ad72ef070929278e6d03d98077ac267fe2a6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 18:42:15 +0700 Subject: Expose function helpers to build widgets in `pure::widget` `button("Hello")` is easier to write and read than `Button::new("Hello")`. --- pure/src/widget/text.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index e3a7d299..73ff71e2 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -24,9 +24,9 @@ where impl Text { /// Create a new fragment of [`Text`] with the given contents. - pub fn new>(label: T) -> Self { + pub fn new(label: T) -> Self { Text { - content: label.into(), + content: label.to_string(), size: None, color: None, font: Default::default(), -- cgit From 01c5004959c9b11f2580840f4553ad7d706f4564 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 22:07:21 +0700 Subject: Allow pure widgets to borrow from `Application` data :tada: --- pure/src/widget/button.rs | 25 +++++++++++------------ pure/src/widget/column.rs | 27 +++++++++++++------------ pure/src/widget/element.rs | 8 ++++---- pure/src/widget/text.rs | 12 +++++++----- pure/src/widget/tree.rs | 49 ++++++++++++++++++++++++---------------------- 5 files changed, 65 insertions(+), 56 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index ece90811..198a3af9 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -13,17 +13,17 @@ use iced_style::button::StyleSheet; use std::any::Any; -pub struct Button { - content: Element, +pub struct Button<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, on_press: Option, - style_sheet: Box, + style_sheet: Box, width: Length, height: Length, padding: Padding, } -impl Button { - pub fn new(content: impl Into>) -> Self { +impl<'a, Message, Renderer> Button<'a, Message, Renderer> { + pub fn new(content: impl Into>) -> Self { Button { content: content.into(), on_press: None, @@ -40,7 +40,8 @@ impl Button { } } -impl Widget for Button +impl<'a, Message, Renderer> Widget + for Button<'a, Message, Renderer> where Message: 'static + Clone, Renderer: 'static + iced_native::Renderer, @@ -96,7 +97,7 @@ where fn on_event( &mut self, - tree: &mut Tree, + tree: &mut Tree, event: Event, layout: Layout<'_>, cursor_position: Point, @@ -162,7 +163,7 @@ where fn draw( &self, - tree: &Tree, + tree: &Tree, renderer: &mut Renderer, _style: &renderer::Style, layout: Layout<'_>, @@ -238,7 +239,7 @@ where fn mouse_interaction( &self, - _tree: &Tree, + _tree: &Tree, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, @@ -260,13 +261,13 @@ struct State { is_pressed: bool, } -impl Into> - for Button +impl<'a, Message, Renderer> Into> + for Button<'a, Message, Renderer> where Message: Clone + 'static, Renderer: iced_native::Renderer + 'static, { - fn into(self) -> Element { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 2f70282a..716fd714 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -11,21 +11,23 @@ use iced_native::{ use std::any::{self, Any}; -pub struct Column { +pub struct Column<'a, Message, Renderer> { spacing: u16, padding: Padding, width: Length, height: Length, align_items: Alignment, - children: Vec>, + children: Vec>, } -impl<'a, Message, Renderer> Column { +impl<'a, Message, Renderer> Column<'a, Message, Renderer> { pub fn new() -> Self { Self::with_children(Vec::new()) } - pub fn with_children(children: Vec>) -> Self { + pub fn with_children( + children: Vec>, + ) -> Self { Column { spacing: 0, padding: Padding::ZERO, @@ -63,14 +65,15 @@ impl<'a, Message, Renderer> Column { pub fn push( mut self, - child: impl Into>, + child: impl Into>, ) -> Self { self.children.push(child.into()); self } } -impl Widget for Column +impl<'a, Message, Renderer> Widget + for Column<'a, Message, Renderer> where Renderer: iced_native::Renderer, { @@ -115,7 +118,7 @@ where fn on_event( &mut self, - tree: &mut Tree, + tree: &mut Tree, event: Event, layout: Layout<'_>, cursor_position: Point, @@ -143,7 +146,7 @@ where fn mouse_interaction( &self, - tree: &Tree, + tree: &Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, @@ -168,7 +171,7 @@ where fn draw( &self, - tree: &Tree, + tree: &Tree, renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, @@ -208,13 +211,13 @@ where } } -impl Into> - for Column +impl<'a, Message, Renderer> Into> + for Column<'a, Message, Renderer> where Message: 'static, Renderer: iced_native::Renderer + 'static, { - fn into(self) -> Element { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/pure/src/widget/element.rs b/pure/src/widget/element.rs index 5e40d488..aedf5973 100644 --- a/pure/src/widget/element.rs +++ b/pure/src/widget/element.rs @@ -1,11 +1,11 @@ use crate::Widget; -pub struct Element { - widget: Box>, +pub struct Element<'a, Message, Renderer> { + widget: Box + 'a>, } -impl Element { - pub fn new(widget: impl Widget + 'static) -> Self { +impl<'a, Message, Renderer> Element<'a, Message, Renderer> { + pub fn new(widget: impl Widget + 'a) -> Self { Self { widget: Box::new(widget), } diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index 73ff71e2..5a5f360e 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -133,7 +133,7 @@ where fn draw( &self, - _tree: &Tree, + _tree: &Tree, renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, @@ -166,20 +166,22 @@ where } } -impl Into> for Text +impl<'a, Message, Renderer> Into> + for Text where Renderer: text::Renderer + 'static, { - fn into(self) -> Element { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } -impl Into> for &'static str +impl<'a, Message, Renderer> Into> + for &'static str where Renderer: text::Renderer + 'static, { - fn into(self) -> Element { + fn into(self) -> Element<'a, Message, Renderer> { Text::new(self).into() } } diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 75f50a2f..2353edc5 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -1,17 +1,27 @@ use crate::widget::Element; -use std::any::Any; -use std::marker::PhantomData; +use std::any::{self, Any}; -pub struct Tree { +pub struct Tree { + pub tag: any::TypeId, pub state: Box, - pub children: Vec>, - types_: PhantomData<(Message, Renderer)>, + pub children: Vec, } -impl Tree { - pub fn new(element: &Element) -> Self { +impl Tree { + pub fn empty() -> Self { Self { + tag: any::TypeId::of::<()>(), + state: Box::new(()), + children: Vec::new(), + } + } + + pub fn new( + element: &Element<'_, Message, Renderer>, + ) -> Self { + Self { + tag: element.as_widget().tag(), state: element.as_widget().state(), children: element .as_widget() @@ -19,36 +29,29 @@ impl Tree { .iter() .map(Self::new) .collect(), - types_: PhantomData, } } - pub fn diff( + pub fn diff( &mut self, - current: &Element, - new: &Element, + new: &Element<'_, Message, Renderer>, ) { - if current.as_widget().tag() == new.as_widget().tag() { - let current_children = current.as_widget().children(); + if self.tag == new.as_widget().tag() { let new_children = new.as_widget().children(); - if current_children.len() > new_children.len() { + if self.children.len() > new_children.len() { self.children.truncate(new_children.len()); } - for (child_state, (current, new)) in self - .children - .iter_mut() - .zip(current_children.iter().zip(new_children.iter())) + for (child_state, new) in + self.children.iter_mut().zip(new_children.iter()) { - child_state.diff(current, new); + child_state.diff(new); } - if current_children.len() < new_children.len() { + if self.children.len() < new_children.len() { self.children.extend( - new_children[current_children.len()..] - .iter() - .map(Self::new), + new_children[self.children.len()..].iter().map(Self::new), ); } } else { -- cgit From ecb3df8e018930c407e469ce2b8f4208a9d15426 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 23:17:07 +0700 Subject: Expose reusable `Button` logic ... and reuse it in `iced_pure`! --- pure/src/widget/button.rs | 162 +++++++++++----------------------------------- pure/src/widget/tree.rs | 14 ++++ 2 files changed, 52 insertions(+), 124 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 198a3af9..89acb7f5 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -4,15 +4,16 @@ use iced_native::event::{self, Event}; use iced_native::layout; use iced_native::mouse; use iced_native::renderer; -use iced_native::touch; +use iced_native::widget::button; use iced_native::{ - Background, Clipboard, Color, Hasher, Layout, Length, Padding, Point, - Rectangle, Shell, Vector, + Clipboard, Hasher, Layout, Length, Padding, Point, Rectangle, Shell, }; use iced_style::button::StyleSheet; use std::any::Any; +pub use button::State; + pub struct Button<'a, Message, Renderer> { content: Element<'a, Message, Renderer>, on_press: Option, @@ -51,7 +52,7 @@ where } fn state(&self) -> Box { - Box::new(State { is_pressed: false }) + Box::new(State::new()) } fn children(&self) -> &[Element] { @@ -71,6 +72,8 @@ where self.tag().hash(state); self.width.hash(state); + self.height.hash(state); + self.padding.hash(state); self.content.as_widget().hash_layout(state); } @@ -79,20 +82,16 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits - .width(self.width) - .height(self.height) - .pad(self.padding); - - let mut content = self.content.as_widget().layout(renderer, &limits); - content.move_to(Point::new( - self.padding.left.into(), - self.padding.top.into(), - )); - - let size = limits.resolve(content.size()).pad(self.padding); - - layout::Node::with_children(size, vec![content]) + button::layout( + renderer, + limits, + self.width, + self.height, + self.padding, + |renderer, limits| { + self.content.as_widget().layout(renderer, &limits) + }, + ) } fn on_event( @@ -105,12 +104,6 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - let state = if let Some(state) = tree.state.downcast_mut::() { - state - } else { - return event::Status::Ignored; - }; - if let event::Status::Captured = self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), @@ -123,42 +116,14 @@ where return event::Status::Captured; } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if self.on_press.is_some() { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - state.is_pressed = true; - - return event::Status::Captured; - } - } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = self.on_press.clone() { - let bounds = layout.bounds(); - - if state.is_pressed { - state.is_pressed = false; - - if bounds.contains(cursor_position) { - shell.publish(on_press); - } - - return event::Status::Captured; - } - } - } - Event::Touch(touch::Event::FingerLost { .. }) => { - state.is_pressed = false; - } - _ => {} - } - - event::Status::Ignored + button::update( + event, + layout, + cursor_position, + shell, + &self.on_press, + || tree.state_mut::(), + ) } fn draw( @@ -170,60 +135,17 @@ where cursor_position: Point, _viewport: &Rectangle, ) { - let state = if let Some(state) = tree.state.downcast_ref::() { - state - } else { - return; - }; - let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); - let is_mouse_over = bounds.contains(cursor_position); - let is_disabled = self.on_press.is_none(); - - let styling = if is_disabled { - self.style_sheet.disabled() - } else if is_mouse_over { - if state.is_pressed { - self.style_sheet.pressed() - } else { - self.style_sheet.hovered() - } - } else { - self.style_sheet.active() - }; - - if styling.background.is_some() || styling.border_width > 0.0 { - if styling.shadow_offset != Vector::default() { - // TODO: Implement proper shadow support - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x + styling.shadow_offset.x, - y: bounds.y + styling.shadow_offset.y, - ..bounds - }, - border_radius: styling.border_radius, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Background::Color([0.0, 0.0, 0.0, 0.5].into()), - ); - } - - renderer.fill_quad( - renderer::Quad { - bounds, - border_radius: styling.border_radius, - border_width: styling.border_width, - border_color: styling.border_color, - }, - styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - ); - } + let styling = button::draw( + renderer, + bounds, + cursor_position, + self.on_press.is_some(), + self.style_sheet.as_ref(), + || tree.state::(), + ); self.content.as_widget().draw( &tree.children[0], @@ -245,22 +167,14 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); - let is_disabled = self.on_press.is_none(); - - if is_mouse_over && !is_disabled { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } + button::mouse_interaction( + layout, + cursor_position, + self.on_press.is_some(), + ) } } -#[derive(Debug, Clone)] -struct State { - is_pressed: bool, -} - impl<'a, Message, Renderer> Into> for Button<'a, Message, Renderer> where diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 2353edc5..1ab6d80b 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -58,4 +58,18 @@ impl Tree { *self = Self::new(new); } } + + pub fn state(&self) -> &T + where + T: 'static, + { + self.state.downcast_ref().expect("Downcast widget state") + } + + pub fn state_mut(&mut self) -> &mut T + where + T: 'static, + { + self.state.downcast_mut().expect("Downcast widget state") + } } -- cgit From dd3e74e74de3a416d9d2dcfee051d78ba03dc540 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 23:33:03 +0700 Subject: Complete `Button` in `iced_pure` --- pure/src/widget/button.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 89acb7f5..b9561b09 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -35,8 +35,38 @@ impl<'a, Message, Renderer> Button<'a, Message, Renderer> { } } - pub fn on_press(mut self, on_press: Message) -> Self { - self.on_press = Some(on_press); + /// Sets the width of the [`Button`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Button`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the [`Padding`] of the [`Button`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed. + /// + /// Unless `on_press` is called, the [`Button`] will be disabled. + pub fn on_press(mut self, msg: Message) -> Self { + self.on_press = Some(msg); + self + } + + /// Sets the style of the [`Button`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); self } } -- cgit From af122265f6663e429a3732ecdbbf2356688702b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 23:39:41 +0700 Subject: Implement `Row` in `iced_pure` --- pure/src/widget/row.rs | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 pure/src/widget/row.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs new file mode 100644 index 00000000..2581b38a --- /dev/null +++ b/pure/src/widget/row.rs @@ -0,0 +1,223 @@ +use crate::flex; +use crate::widget::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::{ + Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, +}; + +use std::any::{self, Any}; + +pub struct Row<'a, Message, Renderer> { + spacing: u16, + padding: Padding, + width: Length, + height: Length, + align_items: Alignment, + children: Vec>, +} + +impl<'a, Message, Renderer> Row<'a, Message, Renderer> { + pub fn new() -> Self { + Self::with_children(Vec::new()) + } + + pub fn with_children( + children: Vec>, + ) -> Self { + Row { + spacing: 0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + align_items: Alignment::Start, + children, + } + } + + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + pub fn align_items(mut self, align: Alignment) -> Self { + self.align_items = align; + self + } + + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + self.children.push(child.into()); + self + } +} + +impl<'a, Message, Renderer> Widget + for Row<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> any::TypeId { + struct Marker; + any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children(&self) -> &[Element] { + &self.children + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + flex::resolve( + flex::Axis::Horizontal, + renderer, + &limits, + self.padding, + self.spacing as f32, + self.align_items, + &self.children, + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, + layout, + cursor_position, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child.as_widget().draw( + state, + renderer, + style, + layout, + cursor_position, + viewport, + ); + } + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.tag().hash(state); + self.width.hash(state); + self.height.hash(state); + self.align_items.hash(state); + self.spacing.hash(state); + self.padding.hash(state); + + for child in &self.children { + child.as_widget().hash_layout(state); + } + } +} + +impl<'a, Message, Renderer> Into> + for Row<'a, Message, Renderer> +where + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From 8b27083cdaa2ef7b749e0fd2c1a94b5606ed1c3d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 11 Feb 2022 23:40:24 +0700 Subject: Use `TypeId` of `()` for `Column` and `Row` tags in `iced_pure` --- pure/src/widget/column.rs | 3 +-- pure/src/widget/row.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 716fd714..ed097d33 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -78,8 +78,7 @@ where Renderer: iced_native::Renderer, { fn tag(&self) -> any::TypeId { - struct Marker; - any::TypeId::of::() + any::TypeId::of::<()>() } fn state(&self) -> Box { diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index 2581b38a..147a0850 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -78,8 +78,7 @@ where Renderer: iced_native::Renderer, { fn tag(&self) -> any::TypeId { - struct Marker; - any::TypeId::of::() + any::TypeId::of::<()>() } fn state(&self) -> Box { -- cgit From 182fb9446c577a6be988052a5103010e1a79addd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 14:07:04 +0700 Subject: Implement `Container` widget in `iced_pure` --- pure/src/widget/container.rs | 258 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 pure/src/widget/container.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs new file mode 100644 index 00000000..94a6b07b --- /dev/null +++ b/pure/src/widget/container.rs @@ -0,0 +1,258 @@ +//! Decorate content and apply alignment. +use crate::{Element, Tree, Widget}; + +use iced_native::alignment; +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::container; +use iced_native::{ + Clipboard, Hasher, Layout, Length, Padding, Point, Rectangle, Shell, +}; + +use std::any::{self, Any}; +use std::hash::Hash; +use std::u32; + +pub use iced_style::container::{Style, StyleSheet}; + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct Container<'a, Message, Renderer> { + padding: Padding, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + style_sheet: Box, + content: Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Container<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates an empty [`Container`]. + pub fn new(content: T) -> Self + where + T: Into>, + { + Container { + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + style_sheet: Default::default(), + content: content.into(), + } + } + + /// Sets the [`Padding`] of the [`Container`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`Container`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Container`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Container`]. + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Container`] in pixels. + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the content alignment for the horizontal axis of the [`Container`]. + pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { + self.horizontal_alignment = alignment; + self + } + + /// Sets the content alignment for the vertical axis of the [`Container`]. + pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { + self.vertical_alignment = alignment; + self + } + + /// Centers the contents in the horizontal axis of the [`Container`]. + pub fn center_x(mut self) -> Self { + self.horizontal_alignment = alignment::Horizontal::Center; + self + } + + /// Centers the contents in the vertical axis of the [`Container`]. + pub fn center_y(mut self) -> Self { + self.vertical_alignment = alignment::Vertical::Center; + self + } + + /// Sets the style of the [`Container`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +impl<'a, Message, Renderer> Widget + for Container<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children(&self) -> &[Element] { + std::slice::from_ref(&self.content) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + container::layout( + renderer, + limits, + self.width, + self.height, + self.padding, + self.horizontal_alignment, + self.vertical_alignment, + |renderer, limits| { + self.content.as_widget().layout(renderer, limits) + }, + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let style = self.style_sheet.style(); + + container::draw_background(renderer, &style, layout.bounds()); + + self.content.as_widget().draw( + &tree.children[0], + renderer, + &renderer::Style { + text_color: style + .text_color + .unwrap_or(renderer_style.text_color), + }, + layout.children().next().unwrap(), + cursor_position, + viewport, + ); + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.padding.hash(state); + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.horizontal_alignment.hash(state); + self.vertical_alignment.hash(state); + + self.content.as_widget().hash_layout(state); + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + iced_native::Renderer, + Message: 'a, +{ + fn from( + column: Container<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(column) + } +} -- cgit From dee3dba632709f57b5573dbe28827ad481287648 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 14:22:17 +0700 Subject: Reuse `Text` widget from `iced_native` in `iced_pure` --- pure/src/widget/text.rs | 131 +++++++----------------------------------------- 1 file changed, 17 insertions(+), 114 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index 5a5f360e..f437b48b 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -1,92 +1,13 @@ use crate::{Element, Tree, Widget}; -use iced_native::alignment; use iced_native::layout::{self, Layout}; use iced_native::renderer; use iced_native::text; -use iced_native::{Color, Hasher, Length, Point, Rectangle, Size}; +use iced_native::{Hasher, Length, Point, Rectangle}; use std::any::{self, Any}; -pub struct Text -where - Renderer: text::Renderer, -{ - content: String, - size: Option, - color: Option, - font: Renderer::Font, - width: Length, - height: Length, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, -} - -impl Text { - /// Create a new fragment of [`Text`] with the given contents. - pub fn new(label: T) -> Self { - Text { - content: label.to_string(), - size: None, - color: None, - font: Default::default(), - width: Length::Shrink, - height: Length::Shrink, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - } - } - - /// Sets the size of the [`Text`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the [`Color`] of the [`Text`]. - pub fn color>(mut self, color: C) -> Self { - self.color = Some(color.into()); - self - } - - /// Sets the [`Font`] of the [`Text`]. - /// - /// [`Font`]: Renderer::Font - pub fn font(mut self, font: impl Into) -> Self { - self.font = font.into(); - self - } - - /// Sets the width of the [`Text`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Text`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the [`HorizontalAlignment`] of the [`Text`]. - pub fn horizontal_alignment( - mut self, - alignment: alignment::Horizontal, - ) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the [`VerticalAlignment`] of the [`Text`]. - pub fn vertical_alignment( - mut self, - alignment: alignment::Vertical, - ) -> Self { - self.vertical_alignment = alignment; - self - } -} +pub use iced_native::widget::Text; impl Widget for Text where @@ -105,11 +26,11 @@ where } fn width(&self) -> Length { - self.width + >::width(self) } fn height(&self) -> Length { - self.height + >::height(self) } fn layout( @@ -117,18 +38,9 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - - let size = self.size.unwrap_or(renderer.default_size()); - - let bounds = limits.max(); - - let (width, height) = - renderer.measure(&self.content, size, self.font.clone(), bounds); - - let size = limits.resolve(Size::new(width, height)); - - layout::Node::new(size) + >::layout( + self, renderer, limits, + ) } fn draw( @@ -137,32 +49,23 @@ where renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, + cursor_position: Point, + viewport: &Rectangle, ) { - iced_native::widget::text::draw( + >::draw( + self, renderer, style, layout, - &self.content, - self.font.clone(), - self.size, - self.color, - self.horizontal_alignment, - self.vertical_alignment, - ); + cursor_position, + viewport, + ) } fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash; - - struct Marker; - std::any::TypeId::of::().hash(state); - - self.content.hash(state); - self.size.hash(state); - self.width.hash(state); - self.height.hash(state); + >::hash_layout( + self, state, + ) } } -- cgit From 178914ec23a107cb7fa38c39be30a35d235248ab Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 14:26:17 +0700 Subject: Implement `Checkbox` in `iced_pure` --- pure/src/widget/checkbox.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 pure/src/widget/checkbox.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/checkbox.rs b/pure/src/widget/checkbox.rs new file mode 100644 index 00000000..8ee2b7bb --- /dev/null +++ b/pure/src/widget/checkbox.rs @@ -0,0 +1,82 @@ +use crate::{Element, Tree, Widget}; + +use iced_native::layout::{self, Layout}; +use iced_native::renderer; +use iced_native::text; +use iced_native::{Hasher, Length, Point, Rectangle}; + +use std::any::{self, Any}; + +pub use iced_native::widget::Checkbox; + +impl<'a, Message, Renderer> Widget + for Checkbox<'a, Message, Renderer> +where + Renderer: text::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children(&self) -> &[Element] { + &[] + } + + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + >::hash_layout( + self, state, + ) + } +} + +impl<'a, Message, Renderer> Into> + for Checkbox<'a, Message, Renderer> +where + Message: 'a, + Renderer: text::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From b2670e8752eb96a4018f93b9cb8945da81a7ebff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 15:17:44 +0700 Subject: Implement `Scrollable` in `iced_pure` --- pure/src/widget/button.rs | 4 +- pure/src/widget/scrollable.rs | 268 ++++++++++++++++++++++++++++++++++++++++++ pure/src/widget/tree.rs | 18 +-- 3 files changed, 281 insertions(+), 9 deletions(-) create mode 100644 pure/src/widget/scrollable.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index b9561b09..d0f9e53e 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -152,7 +152,7 @@ where cursor_position, shell, &self.on_press, - || tree.state_mut::(), + || tree.state.downcast_mut::(), ) } @@ -174,7 +174,7 @@ where cursor_position, self.on_press.is_some(), self.style_sheet.as_ref(), - || tree.state::(), + || tree.state.downcast_ref::(), ); self.content.as_widget().draw( diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs new file mode 100644 index 00000000..69acabb7 --- /dev/null +++ b/pure/src/widget/scrollable.rs @@ -0,0 +1,268 @@ +use crate::widget::{Column, Tree}; +use crate::{Element, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::scrollable; +use iced_native::{ + Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, +}; + +pub use iced_style::scrollable::StyleSheet; + +use std::any::{self, Any}; + +/// A widget that can vertically display an infinite amount of content with a +/// scrollbar. +#[allow(missing_debug_implementations)] +pub struct Scrollable<'a, Message, Renderer> { + height: Length, + scrollbar_width: u16, + scrollbar_margin: u16, + scroller_width: u16, + content: Column<'a, Message, Renderer>, + on_scroll: Option Message>>, + style_sheet: Box, +} + +impl<'a, Message, Renderer: iced_native::Renderer> + Scrollable<'a, Message, Renderer> +{ + /// Creates a new [`Scrollable`] with the given [`State`]. + pub fn new() -> Self { + Scrollable { + height: Length::Shrink, + scrollbar_width: 10, + scrollbar_margin: 0, + scroller_width: 10, + content: Column::new(), + on_scroll: None, + style_sheet: Default::default(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.content = self.content.spacing(units); + self + } + + /// Sets the [`Padding`] of the [`Scrollable`]. + pub fn padding>(mut self, padding: P) -> Self { + self.content = self.content.padding(padding); + self + } + + /// Sets the width of the [`Scrollable`]. + pub fn width(mut self, width: Length) -> Self { + self.content = self.content.width(width); + self + } + + /// Sets the height of the [`Scrollable`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Scrollable`] . + pub fn align_items(mut self, align_items: Alignment) -> Self { + self.content = self.content.align_items(align_items); + self + } + + /// Sets the scrollbar width of the [`Scrollable`] . + /// Silently enforces a minimum value of 1. + pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self { + self.scrollbar_width = scrollbar_width.max(1); + self + } + + /// Sets the scrollbar margin of the [`Scrollable`] . + pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self { + self.scrollbar_margin = scrollbar_margin; + self + } + + /// Sets the scroller width of the [`Scrollable`] . + /// + /// It silently enforces a minimum value of 1. + pub fn scroller_width(mut self, scroller_width: u16) -> Self { + self.scroller_width = scroller_width.max(1); + self + } + + /// Sets a function to call when the [`Scrollable`] is scrolled. + /// + /// The function takes the new relative offset of the [`Scrollable`] + /// (e.g. `0` means top, while `1` means bottom). + pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self { + self.on_scroll = Some(Box::new(f)); + self + } + + /// Sets the style of the [`Scrollable`] . + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } + + /// Adds an element to the [`Scrollable`]. + pub fn push(mut self, child: E) -> Self + where + E: Into>, + { + self.content = self.content.push(child); + self + } +} + +impl<'a, Message, Renderer> Widget + for Scrollable<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(scrollable::State::new()) + } + + fn children(&self) -> &[Element] { + self.content.children() + } + + fn width(&self) -> Length { + Widget::::width(&self.content) + } + + fn height(&self) -> Length { + self.height + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.tag().hash(state); + self.height.hash(state); + self.content.hash_layout(state) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + scrollable::layout( + renderer, + limits, + Widget::::width(self), + self.height, + |renderer, limits| self.content.layout(renderer, limits), + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + scrollable::update( + tree.state.downcast_mut::(), + 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( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + scrollable::draw( + tree.state.downcast_ref::(), + 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( + &tree.children[0], + renderer, + style, + layout, + cursor_position, + viewport, + ) + }, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + scrollable::mouse_interaction( + tree.state.downcast_ref::(), + layout, + cursor_position, + self.scrollbar_width, + self.scrollbar_margin, + self.scroller_width, + |layout, cursor_position, viewport| { + self.content.mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + }, + ) + } +} diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 1ab6d80b..98e976ad 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -4,7 +4,7 @@ use std::any::{self, Any}; pub struct Tree { pub tag: any::TypeId, - pub state: Box, + pub state: State, pub children: Vec, } @@ -12,7 +12,7 @@ impl Tree { pub fn empty() -> Self { Self { tag: any::TypeId::of::<()>(), - state: Box::new(()), + state: State(Box::new(())), children: Vec::new(), } } @@ -22,7 +22,7 @@ impl Tree { ) -> Self { Self { tag: element.as_widget().tag(), - state: element.as_widget().state(), + state: State(element.as_widget().state()), children: element .as_widget() .children() @@ -58,18 +58,22 @@ impl Tree { *self = Self::new(new); } } +} + +pub struct State(Box); - pub fn state(&self) -> &T +impl State { + pub fn downcast_ref(&self) -> &T where T: 'static, { - self.state.downcast_ref().expect("Downcast widget state") + self.0.downcast_ref().expect("Downcast widget state") } - pub fn state_mut(&mut self) -> &mut T + pub fn downcast_mut(&mut self) -> &mut T where T: 'static, { - self.state.downcast_mut().expect("Downcast widget state") + self.0.downcast_mut().expect("Downcast widget state") } } -- cgit From e3108494e5886c34312184292ec05dddeb8bf3ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 16:11:22 +0700 Subject: Implement `TextInput` in `iced_pure` --- pure/src/widget/text_input.rs | 239 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 pure/src/widget/text_input.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/text_input.rs b/pure/src/widget/text_input.rs new file mode 100644 index 00000000..06ab2910 --- /dev/null +++ b/pure/src/widget/text_input.rs @@ -0,0 +1,239 @@ +use crate::widget::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::text; +use iced_native::widget::text_input; +use iced_native::{ + Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, +}; + +pub use iced_style::text_input::StyleSheet; + +use std::any::{self, Any}; + +/// A field that can be filled with text. +/// +/// # Example +/// ``` +/// # use iced_native::renderer::Null; +/// # use iced_native::widget::text_input; +/// # +/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>; +/// #[derive(Debug, Clone)] +/// enum Message { +/// TextInputChanged(String), +/// } +/// +/// let mut state = text_input::State::new(); +/// let value = "Some text"; +/// +/// let input = TextInput::new( +/// &mut state, +/// "This is the placeholder...", +/// value, +/// Message::TextInputChanged, +/// ) +/// .padding(10); +/// ``` +/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true) +#[allow(missing_debug_implementations)] +pub struct TextInput<'a, Message, Renderer: text::Renderer> { + placeholder: String, + value: text_input::Value, + is_secure: bool, + font: Renderer::Font, + width: Length, + padding: Padding, + size: Option, + on_change: Box Message + 'a>, + on_submit: Option, + style_sheet: Box, +} + +impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: text::Renderer, +{ + /// Creates a new [`TextInput`]. + /// + /// It expects: + /// - some [`State`] + /// - a placeholder + /// - the current value + /// - a function that produces a message when the [`TextInput`] changes + pub fn new(placeholder: &str, value: &str, on_change: F) -> Self + where + F: 'a + Fn(String) -> Message, + { + TextInput { + placeholder: String::from(placeholder), + value: text_input::Value::new(value), + is_secure: false, + font: Default::default(), + width: Length::Fill, + padding: Padding::ZERO, + size: None, + on_change: Box::new(on_change), + on_submit: None, + style_sheet: Default::default(), + } + } + + /// Converts the [`TextInput`] into a secure password input. + pub fn password(mut self) -> Self { + self.is_secure = true; + self + } + + /// Sets the [`Font`] of the [`Text`]. + /// + /// [`Font`]: crate::widget::text::Renderer::Font + /// [`Text`]: crate::widget::Text + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + /// Sets the width of the [`TextInput`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the [`Padding`] of the [`TextInput`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the text size of the [`TextInput`]. + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the message that should be produced when the [`TextInput`] is + /// focused and the enter key is pressed. + pub fn on_submit(mut self, message: Message) -> Self { + self.on_submit = Some(message); + self + } + + /// Sets the style of the [`TextInput`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +impl<'a, Message, Renderer> Widget + for TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: iced_native::text::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(text_input::State::new()) + } + + fn children(&self) -> &[Element] { + &[] + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn hash_layout(&self, state: &mut Hasher) { + text_input::hash_layout(state, self.width, self.padding, self.size); + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + text_input::layout( + renderer, + limits, + self.width, + self.padding, + self.size, + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + text_input::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, + || tree.state.downcast_mut::(), + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + text_input::draw( + renderer, + layout, + cursor_position, + tree.state.downcast_ref::(), + &self.value, + &self.placeholder, + self.size, + &self.font, + self.is_secure, + self.style_sheet.as_ref(), + ) + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + text_input::mouse_interaction(layout, cursor_position) + } +} -- cgit From bd22cc0bc0f7551d29cf2acd22520f4a906f253c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 17:21:28 +0700 Subject: Implement pure version of `todos` example :tada: The `Widget` trait in `iced_pure` needed to change a bit to make the implementation of `Element::map` possible. Specifically, the `children` method has been split into `diff` and `children_state`. --- pure/src/widget/button.rs | 8 ++- pure/src/widget/checkbox.rs | 6 +- pure/src/widget/column.rs | 8 ++- pure/src/widget/container.rs | 8 ++- pure/src/widget/element.rs | 149 +++++++++++++++++++++++++++++++++++++++++- pure/src/widget/row.rs | 8 ++- pure/src/widget/scrollable.rs | 86 ++++++++++-------------- pure/src/widget/text.rs | 6 +- pure/src/widget/text_input.rs | 19 +++++- pure/src/widget/tree.rs | 44 ++++++------- 10 files changed, 252 insertions(+), 90 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index d0f9e53e..6dc1016c 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -85,8 +85,12 @@ where Box::new(State::new()) } - fn children(&self) -> &[Element] { - std::slice::from_ref(&self.content) + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) + } + + fn children_state(&self) -> Vec { + vec![Tree::new(&self.content)] } fn width(&self) -> Length { diff --git a/pure/src/widget/checkbox.rs b/pure/src/widget/checkbox.rs index 8ee2b7bb..1cfe89a8 100644 --- a/pure/src/widget/checkbox.rs +++ b/pure/src/widget/checkbox.rs @@ -22,8 +22,10 @@ where Box::new(()) } - fn children(&self) -> &[Element] { - &[] + fn diff(&self, _tree: &mut Tree) {} + + fn children_state(&self) -> Vec { + Vec::new() } fn width(&self) -> Length { diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index ed097d33..68d3c4b4 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -85,8 +85,12 @@ where Box::new(()) } - fn children(&self) -> &[Element] { - &self.children + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&self.children); + } + + fn children_state(&self) -> Vec { + self.children.iter().map(Tree::new).collect() } fn width(&self) -> Length { diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs index 94a6b07b..85ea8039 100644 --- a/pure/src/widget/container.rs +++ b/pure/src/widget/container.rs @@ -132,8 +132,12 @@ where Box::new(()) } - fn children(&self) -> &[Element] { - std::slice::from_ref(&self.content) + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) + } + + fn children_state(&self) -> Vec { + vec![Tree::new(&self.content)] } fn width(&self) -> Length { diff --git a/pure/src/widget/element.rs b/pure/src/widget/element.rs index aedf5973..2a137d40 100644 --- a/pure/src/widget/element.rs +++ b/pure/src/widget/element.rs @@ -1,4 +1,12 @@ -use crate::Widget; +use crate::widget::{Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; + +use std::any::{self, Any}; pub struct Element<'a, Message, Renderer> { widget: Box + 'a>, @@ -18,4 +26,143 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { pub fn as_widget_mut(&mut self) -> &mut dyn Widget { self.widget.as_mut() } + + pub fn map( + self, + f: impl Fn(Message) -> B + 'a, + ) -> Element<'a, B, Renderer> + where + Message: 'a, + Renderer: iced_native::Renderer + 'a, + B: 'a, + { + Element::new(Map::new(self.widget, f)) + } +} + +struct Map<'a, A, B, Renderer> { + widget: Box + 'a>, + mapper: Box B + 'a>, +} + +impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { + pub fn new( + widget: Box + 'a>, + mapper: F, + ) -> Map<'a, A, B, Renderer> + where + F: 'a + Fn(A) -> B, + { + Map { + widget, + mapper: Box::new(mapper), + } + } +} + +impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> +where + Renderer: iced_native::Renderer + 'a, + A: 'a, + B: 'a, +{ + fn tag(&self) -> any::TypeId { + self.widget.tag() + } + + fn state(&self) -> Box { + self.widget.state() + } + + fn diff(&self, tree: &mut Tree) { + self.widget.diff(tree) + } + + fn children_state(&self) -> Vec { + self.widget.children_state() + } + + fn width(&self) -> Length { + self.widget.width() + } + + fn height(&self) -> Length { + self.widget.height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.widget.layout(renderer, limits) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, B>, + ) -> event::Status { + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); + + let status = self.widget.on_event( + tree, + event, + layout, + cursor_position, + renderer, + clipboard, + &mut local_shell, + ); + + shell.merge(local_shell, &self.mapper); + + status + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.widget.draw( + tree, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.widget.mouse_interaction( + tree, + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.widget.hash_layout(state); + } } diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index 147a0850..ec7e144c 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -85,8 +85,12 @@ where Box::new(()) } - fn children(&self) -> &[Element] { - &self.children + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&self.children) + } + + fn children_state(&self) -> Vec { + self.children.iter().map(Tree::new).collect() } fn width(&self) -> Length { diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 69acabb7..badc9fc2 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,4 +1,4 @@ -use crate::widget::{Column, Tree}; +use crate::widget::Tree; use crate::{Element, Widget}; use iced_native::event::{self, Event}; @@ -6,9 +6,7 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::renderer; use iced_native::widget::scrollable; -use iced_native::{ - Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, -}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; pub use iced_style::scrollable::StyleSheet; @@ -22,61 +20,33 @@ pub struct Scrollable<'a, Message, Renderer> { scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, - content: Column<'a, Message, Renderer>, on_scroll: Option Message>>, style_sheet: Box, + content: Element<'a, Message, Renderer>, } impl<'a, Message, Renderer: iced_native::Renderer> Scrollable<'a, Message, Renderer> { - /// Creates a new [`Scrollable`] with the given [`State`]. - pub fn new() -> Self { + /// Creates a new [`Scrollable`]. + pub fn new(content: impl Into>) -> Self { Scrollable { height: Length::Shrink, scrollbar_width: 10, scrollbar_margin: 0, scroller_width: 10, - content: Column::new(), on_scroll: None, style_sheet: Default::default(), + content: content.into(), } } - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.content = self.content.spacing(units); - self - } - - /// Sets the [`Padding`] of the [`Scrollable`]. - pub fn padding>(mut self, padding: P) -> Self { - self.content = self.content.padding(padding); - self - } - - /// Sets the width of the [`Scrollable`]. - pub fn width(mut self, width: Length) -> Self { - self.content = self.content.width(width); - self - } - /// Sets the height of the [`Scrollable`]. pub fn height(mut self, height: Length) -> Self { self.height = height; self } - /// Sets the horizontal alignment of the contents of the [`Scrollable`] . - pub fn align_items(mut self, align_items: Alignment) -> Self { - self.content = self.content.align_items(align_items); - self - } - /// Sets the scrollbar width of the [`Scrollable`] . /// Silently enforces a minimum value of 1. pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self { @@ -115,15 +85,6 @@ impl<'a, Message, Renderer: iced_native::Renderer> self.style_sheet = style_sheet.into(); self } - - /// Adds an element to the [`Scrollable`]. - pub fn push(mut self, child: E) -> Self - where - E: Into>, - { - self.content = self.content.push(child); - self - } } impl<'a, Message, Renderer> Widget @@ -139,12 +100,16 @@ where Box::new(scrollable::State::new()) } - fn children(&self) -> &[Element] { - self.content.children() + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) + } + + fn children_state(&self) -> Vec { + vec![Tree::new(&self.content)] } fn width(&self) -> Length { - Widget::::width(&self.content) + self.content.as_widget().width() } fn height(&self) -> Length { @@ -156,7 +121,7 @@ where self.tag().hash(state); self.height.hash(state); - self.content.hash_layout(state) + self.content.as_widget().hash_layout(state) } fn layout( @@ -169,7 +134,9 @@ where limits, Widget::::width(self), self.height, - |renderer, limits| self.content.layout(renderer, limits), + |renderer, limits| { + self.content.as_widget().layout(renderer, limits) + }, ) } @@ -195,7 +162,7 @@ where self.scroller_width, &self.on_scroll, |event, layout, cursor_position, clipboard, shell| { - self.content.on_event( + self.content.as_widget_mut().on_event( &mut tree.children[0], event, layout, @@ -227,7 +194,7 @@ where self.scroller_width, self.style_sheet.as_ref(), |renderer, layout, cursor_position, viewport| { - self.content.draw( + self.content.as_widget().draw( &tree.children[0], renderer, style, @@ -255,7 +222,7 @@ where self.scrollbar_margin, self.scroller_width, |layout, cursor_position, viewport| { - self.content.mouse_interaction( + self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor_position, @@ -266,3 +233,16 @@ where ) } } + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + iced_native::Renderer, +{ + fn from( + text_input: Scrollable<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(text_input) + } +} diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index f437b48b..8f157ea0 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -21,8 +21,10 @@ where Box::new(()) } - fn children(&self) -> &[Element] { - &[] + fn diff(&self, _tree: &mut Tree) {} + + fn children_state(&self) -> Vec { + Vec::new() } fn width(&self) -> Length { diff --git a/pure/src/widget/text_input.rs b/pure/src/widget/text_input.rs index 06ab2910..e18a2bf0 100644 --- a/pure/src/widget/text_input.rs +++ b/pure/src/widget/text_input.rs @@ -146,8 +146,10 @@ where Box::new(text_input::State::new()) } - fn children(&self) -> &[Element] { - &[] + fn diff(&self, _tree: &mut Tree) {} + + fn children_state(&self) -> Vec { + Vec::new() } fn width(&self) -> Length { @@ -237,3 +239,16 @@ where text_input::mouse_interaction(layout, cursor_position) } } + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + text::Renderer, +{ + fn from( + text_input: TextInput<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(text_input) + } +} diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 98e976ad..3a5f4433 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -23,12 +23,7 @@ impl Tree { Self { tag: element.as_widget().tag(), state: State(element.as_widget().state()), - children: element - .as_widget() - .children() - .iter() - .map(Self::new) - .collect(), + children: element.as_widget().children_state(), } } @@ -37,25 +32,30 @@ impl Tree { new: &Element<'_, Message, Renderer>, ) { if self.tag == new.as_widget().tag() { - let new_children = new.as_widget().children(); + new.as_widget().diff(self) + } else { + *self = Self::new(new); + } + } - if self.children.len() > new_children.len() { - self.children.truncate(new_children.len()); - } + pub fn diff_children( + &mut self, + new_children: &[Element<'_, Message, Renderer>], + ) { + if self.children.len() > new_children.len() { + self.children.truncate(new_children.len()); + } - for (child_state, new) in - self.children.iter_mut().zip(new_children.iter()) - { - child_state.diff(new); - } + for (child_state, new) in + self.children.iter_mut().zip(new_children.iter()) + { + child_state.diff(new); + } - if self.children.len() < new_children.len() { - self.children.extend( - new_children[self.children.len()..].iter().map(Self::new), - ); - } - } else { - *self = Self::new(new); + if self.children.len() < new_children.len() { + self.children.extend( + new_children[self.children.len()..].iter().map(Self::new), + ); } } } -- cgit From 4c61601aa3fe7f6735e27c27d379090d777b56f7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 17:26:49 +0700 Subject: Implement missing `on_event` and `mouse_interaction` for `Checkbox` in `iced_pure` --- pure/src/widget/checkbox.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/checkbox.rs b/pure/src/widget/checkbox.rs index 1cfe89a8..5352fad3 100644 --- a/pure/src/widget/checkbox.rs +++ b/pure/src/widget/checkbox.rs @@ -1,9 +1,11 @@ use crate::{Element, Tree, Widget}; +use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; +use iced_native::mouse; use iced_native::renderer; use iced_native::text; -use iced_native::{Hasher, Length, Point, Rectangle}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; use std::any::{self, Any}; @@ -46,6 +48,27 @@ where ) } + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + >::on_event( + self, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + fn draw( &self, _tree: &Tree, @@ -65,6 +88,23 @@ where ) } + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + >::mouse_interaction( + self, + layout, + cursor_position, + viewport, + renderer, + ) + } + fn hash_layout(&self, state: &mut Hasher) { >::hash_layout( self, state, -- cgit From 09c96a6d8123a62411e2c461a018c3900dec71cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 12 Feb 2022 18:02:29 +0700 Subject: Add `max_width` to `Column` in `iced_pure` --- pure/src/widget/column.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 68d3c4b4..a9d7246e 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -10,12 +10,14 @@ use iced_native::{ }; use std::any::{self, Any}; +use std::u32; pub struct Column<'a, Message, Renderer> { spacing: u16, padding: Padding, width: Length, height: Length, + max_width: u32, align_items: Alignment, children: Vec>, } @@ -33,6 +35,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, + max_width: u32::MAX, align_items: Alignment::Start, children, } @@ -58,6 +61,12 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { self } + /// Sets the maximum width of the [`Column`]. + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + pub fn align_items(mut self, align: Alignment) -> Self { self.align_items = align; self @@ -106,7 +115,10 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); + let limits = limits + .max_width(self.max_width) + .width(self.width) + .height(self.height); flex::resolve( flex::Axis::Vertical, @@ -204,6 +216,7 @@ where self.tag().hash(state); self.width.hash(state); self.height.hash(state); + self.max_width.hash(state); self.align_items.hash(state); self.spacing.hash(state); self.padding.hash(state); -- cgit From 45455be45000c0d41d18eced1b62eab049c5e9c0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Feb 2022 16:51:31 +0700 Subject: Implement `Image` in `iced_pure` --- pure/src/widget/image.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 pure/src/widget/image.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/image.rs b/pure/src/widget/image.rs new file mode 100644 index 00000000..b33dad2b --- /dev/null +++ b/pure/src/widget/image.rs @@ -0,0 +1,74 @@ +use crate::widget::{Tree, Widget}; + +use iced_native::layout::{self, Layout}; +use iced_native::renderer; +use iced_native::widget::image; +use iced_native::{Hasher, Length, Point, Rectangle}; + +use std::any::{self, Any}; +use std::hash::Hash; + +pub use image::Image; + +impl Widget for Image +where + Handle: Clone + Hash, + Renderer: iced_native::image::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children_state(&self) -> Vec { + Vec::new() + } + + fn diff(&self, _tree: &mut Tree) {} + + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + >::hash_layout( + self, state, + ) + } +} -- cgit From 3f1a45ca47dc086a5c4e45867d3f9c63a4e7ba19 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Feb 2022 17:20:10 +0700 Subject: Implement `Slider` in `iced_pure` --- pure/src/widget/slider.rs | 253 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 pure/src/widget/slider.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/slider.rs b/pure/src/widget/slider.rs new file mode 100644 index 00000000..f659c2ed --- /dev/null +++ b/pure/src/widget/slider.rs @@ -0,0 +1,253 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +use crate::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::slider; +use iced_native::{ + Clipboard, Hasher, Layout, Length, Point, Rectangle, Shell, Size, +}; + +use std::any::{self, Any}; +use std::ops::RangeInclusive; + +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// A [`Slider`] will try to fill the horizontal space of its container. +/// +/// The [`Slider`] range of numeric values is generic and its step size defaults +/// to 1 unit. +/// +/// # Example +/// ``` +/// # use iced_native::widget::slider::{self, Slider}; +/// # +/// #[derive(Clone)] +/// pub enum Message { +/// SliderChanged(f32), +/// } +/// +/// let state = &mut slider::State::new(); +/// let value = 50.0; +/// +/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); +/// ``` +/// +/// ![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> { + range: RangeInclusive, + step: T, + value: T, + on_change: Box Message + 'a>, + on_release: Option, + width: Length, + height: u16, + style_sheet: Box, +} + +impl<'a, T, Message> Slider<'a, T, Message> +where + T: Copy + From + std::cmp::PartialOrd, + Message: Clone, +{ + /// The default height of a [`Slider`]. + pub const DEFAULT_HEIGHT: u16 = 22; + + /// Creates a new [`Slider`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`Slider`] + /// * a function that will be called when the [`Slider`] is dragged. + /// It receives the new value of the [`Slider`] and must produce a + /// `Message`. + pub fn new(range: RangeInclusive, value: T, on_change: F) -> Self + where + F: 'a + Fn(T) -> Message, + { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + + Slider { + value, + range, + step: T::from(1), + on_change: Box::new(on_change), + on_release: None, + width: Length::Fill, + height: Self::DEFAULT_HEIGHT, + style_sheet: Default::default(), + } + } + + /// Sets the release message of the [`Slider`]. + /// This is called when the mouse is released from the slider. + /// + /// Typically, the user's interaction with the slider is finished when this message is produced. + /// This is useful if you need to spawn a long-running task from the slider's result, where + /// the default on_change message could create too many events. + pub fn on_release(mut self, on_release: Message) -> Self { + self.on_release = Some(on_release); + self + } + + /// Sets the width of the [`Slider`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Slider`]. + pub fn height(mut self, height: u16) -> Self { + self.height = height; + self + } + + /// Sets the style of the [`Slider`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } + + /// Sets the step size of the [`Slider`]. + pub fn step(mut self, step: T) -> Self { + self.step = step; + self + } +} + +impl<'a, T, Message, Renderer> Widget + for Slider<'a, T, Message> +where + T: Copy + Into + num_traits::FromPrimitive, + Message: Clone, + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::() + } + + fn state(&self) -> Box { + Box::new(slider::State::new()) + } + + fn children_state(&self) -> Vec { + Vec::new() + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = + limits.width(self.width).height(Length::Units(self.height)); + + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + slider::update( + event, + layout, + cursor_position, + shell, + tree.state.downcast_mut::(), + &mut self.value, + &self.range, + self.step, + self.on_change.as_ref(), + &self.on_release, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + slider::draw( + renderer, + layout, + cursor_position, + tree.state.downcast_ref::(), + self.value, + &self.range, + self.style_sheet.as_ref(), + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + slider::mouse_interaction( + layout, + cursor_position, + tree.state.downcast_ref::(), + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + slider::hash_layout(state, self.width) + } +} + +impl<'a, T, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + T: 'a + Copy + Into + num_traits::FromPrimitive, + Message: 'a + Clone, + Renderer: 'a + iced_native::Renderer, +{ + fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> { + Element::new(slider) + } +} -- cgit From 0fec0a2b77b6b9447117f2fea81c700a25fbca6d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Feb 2022 19:01:09 +0700 Subject: Implement `Toggler` in `iced_pure` --- pure/src/widget/image.rs | 13 +++++ pure/src/widget/toggler.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 pure/src/widget/toggler.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/image.rs b/pure/src/widget/image.rs index b33dad2b..51a24ed1 100644 --- a/pure/src/widget/image.rs +++ b/pure/src/widget/image.rs @@ -1,4 +1,5 @@ use crate::widget::{Tree, Widget}; +use crate::Element; use iced_native::layout::{self, Layout}; use iced_native::renderer; @@ -72,3 +73,15 @@ where ) } } + +impl<'a, Message, Renderer, Handle> Into> + for Image +where + Message: Clone + 'a, + Renderer: iced_native::image::Renderer + 'a, + Handle: Clone + Hash + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/pure/src/widget/toggler.rs b/pure/src/widget/toggler.rs new file mode 100644 index 00000000..ec86fff0 --- /dev/null +++ b/pure/src/widget/toggler.rs @@ -0,0 +1,123 @@ +use crate::widget::{Tree, Widget}; +use crate::Element; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::text; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; + +use std::any::{self, Any}; + +pub use iced_native::widget::toggler::{Style, StyleSheet, Toggler}; + +impl<'a, Message, Renderer> Widget + for Toggler<'a, Message, Renderer> +where + Renderer: text::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn children_state(&self) -> Vec { + Vec::new() + } + + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn hash_layout(&self, state: &mut Hasher) { + >::hash_layout( + self, state, + ) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn draw( + &self, + _state: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + >::mouse_interaction( + self, + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + >::on_event( + self, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } +} + +impl<'a, Message, Renderer> Into> + for Toggler<'a, Message, Renderer> +where + Message: 'a, + Renderer: text::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From 53f382043235d7ab9eae9b0882de3e8c77cc0d40 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Feb 2022 22:13:47 +0700 Subject: Implement `Radio` in `iced_pure` --- pure/src/widget/radio.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 pure/src/widget/radio.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/radio.rs b/pure/src/widget/radio.rs new file mode 100644 index 00000000..25fe5bdd --- /dev/null +++ b/pure/src/widget/radio.rs @@ -0,0 +1,125 @@ +use crate::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::text; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; + +use std::any::{self, Any}; + +pub use iced_native::widget::Radio; + +impl<'a, Message, Renderer> Widget + for Radio<'a, Message, Renderer> +where + Message: Clone, + Renderer: text::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn diff(&self, _tree: &mut Tree) {} + + fn children_state(&self) -> Vec { + Vec::new() + } + + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + >::on_event( + self, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + >::mouse_interaction( + self, + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + >::hash_layout( + self, state, + ) + } +} + +impl<'a, Message, Renderer> Into> + for Radio<'a, Message, Renderer> +where + Message: 'a + Clone, + Renderer: text::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From 6689ede6d8ce0d65ec3ce29fd863ec7f26052621 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Feb 2022 22:18:21 +0700 Subject: Implement `Space` in `iced_pure` --- pure/src/widget/space.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 pure/src/widget/space.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/space.rs b/pure/src/widget/space.rs new file mode 100644 index 00000000..67d17c16 --- /dev/null +++ b/pure/src/widget/space.rs @@ -0,0 +1,123 @@ +use crate::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::text; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; + +use std::any::{self, Any}; + +pub use iced_native::widget::Space; + +impl<'a, Message, Renderer> Widget for Space +where + Message: Clone, + Renderer: text::Renderer, +{ + fn tag(&self) -> any::TypeId { + any::TypeId::of::<()>() + } + + fn state(&self) -> Box { + Box::new(()) + } + + fn diff(&self, _tree: &mut Tree) {} + + fn children_state(&self) -> Vec { + Vec::new() + } + + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + >::on_event( + self, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + >::mouse_interaction( + self, + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + >::hash_layout( + self, state, + ) + } +} + +impl<'a, Message, Renderer> Into> for Space +where + Message: 'a + Clone, + Renderer: text::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From 35e9b75e415ef3b9124051696b60628ef56afe47 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 15:44:50 +0700 Subject: Introduce `Tag` and `State` opaque types in `iced_pure::widget::tree` --- pure/src/widget/button.rs | 21 +++++++++-------- pure/src/widget/checkbox.rs | 16 ------------- pure/src/widget/column.rs | 13 ++--------- pure/src/widget/container.rs | 13 ++--------- pure/src/widget/element.rs | 17 +++++++------- pure/src/widget/image.rs | 15 ------------- pure/src/widget/radio.rs | 16 ------------- pure/src/widget/row.rs | 14 ++---------- pure/src/widget/scrollable.rs | 20 ++++++++--------- pure/src/widget/slider.rs | 16 +++++-------- pure/src/widget/space.rs | 16 ------------- pure/src/widget/text.rs | 16 ------------- pure/src/widget/text_input.rs | 19 +++++----------- pure/src/widget/toggler.rs | 14 ------------ pure/src/widget/tree.rs | 52 ++++++++++++++++++++++++++++++++++++------- 15 files changed, 89 insertions(+), 189 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 6dc1016c..55cbf8b4 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -1,4 +1,5 @@ -use crate::widget::{Element, Tree, Widget}; +use crate::widget::tree::{self, Tree}; +use crate::widget::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout; @@ -10,8 +11,6 @@ use iced_native::{ }; use iced_style::button::StyleSheet; -use std::any::Any; - pub use button::State; pub struct Button<'a, Message, Renderer> { @@ -77,20 +76,20 @@ where Message: 'static + Clone, Renderer: 'static + iced_native::Renderer, { - fn tag(&self) -> std::any::TypeId { - std::any::TypeId::of::() + fn tag(&self) -> tree::Tag { + tree::Tag::of::() } - fn state(&self) -> Box { - Box::new(State::new()) + fn state(&self) -> tree::State { + tree::State::new(State::new()) } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] } - fn children_state(&self) -> Vec { - vec![Tree::new(&self.content)] + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) } fn width(&self) -> Length { diff --git a/pure/src/widget/checkbox.rs b/pure/src/widget/checkbox.rs index 5352fad3..8aa4e845 100644 --- a/pure/src/widget/checkbox.rs +++ b/pure/src/widget/checkbox.rs @@ -7,8 +7,6 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; -use std::any::{self, Any}; - pub use iced_native::widget::Checkbox; impl<'a, Message, Renderer> Widget @@ -16,20 +14,6 @@ impl<'a, Message, Renderer> Widget where Renderer: text::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) - } - - fn diff(&self, _tree: &mut Tree) {} - - fn children_state(&self) -> Vec { - Vec::new() - } - fn width(&self) -> Length { >::width(self) } diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index a9d7246e..4ab3e00d 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -9,7 +9,6 @@ use iced_native::{ Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, }; -use std::any::{self, Any}; use std::u32; pub struct Column<'a, Message, Renderer> { @@ -86,22 +85,14 @@ impl<'a, Message, Renderer> Widget where Renderer: iced_native::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() } fn diff(&self, tree: &mut Tree) { tree.diff_children(&self.children); } - fn children_state(&self) -> Vec { - self.children.iter().map(Tree::new).collect() - } - fn width(&self) -> Length { self.width } diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs index 85ea8039..f42b127d 100644 --- a/pure/src/widget/container.rs +++ b/pure/src/widget/container.rs @@ -11,7 +11,6 @@ use iced_native::{ Clipboard, Hasher, Layout, Length, Padding, Point, Rectangle, Shell, }; -use std::any::{self, Any}; use std::hash::Hash; use std::u32; @@ -124,22 +123,14 @@ impl<'a, Message, Renderer> Widget where Renderer: iced_native::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] } fn diff(&self, tree: &mut Tree) { tree.diff_children(std::slice::from_ref(&self.content)) } - fn children_state(&self) -> Vec { - vec![Tree::new(&self.content)] - } - fn width(&self) -> Length { self.width } diff --git a/pure/src/widget/element.rs b/pure/src/widget/element.rs index 2a137d40..d905b924 100644 --- a/pure/src/widget/element.rs +++ b/pure/src/widget/element.rs @@ -1,4 +1,5 @@ -use crate::widget::{Tree, Widget}; +use crate::widget::tree::{self, Tree}; +use crate::widget::Widget; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; @@ -6,8 +7,6 @@ use iced_native::mouse; use iced_native::renderer; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; -use std::any::{self, Any}; - pub struct Element<'a, Message, Renderer> { widget: Box + 'a>, } @@ -66,20 +65,20 @@ where A: 'a, B: 'a, { - fn tag(&self) -> any::TypeId { + fn tag(&self) -> tree::Tag { self.widget.tag() } - fn state(&self) -> Box { + fn state(&self) -> tree::State { self.widget.state() } - fn diff(&self, tree: &mut Tree) { - self.widget.diff(tree) + fn children(&self) -> Vec { + self.widget.children() } - fn children_state(&self) -> Vec { - self.widget.children_state() + fn diff(&self, tree: &mut Tree) { + self.widget.diff(tree) } fn width(&self) -> Length { diff --git a/pure/src/widget/image.rs b/pure/src/widget/image.rs index 51a24ed1..ce807813 100644 --- a/pure/src/widget/image.rs +++ b/pure/src/widget/image.rs @@ -6,7 +6,6 @@ use iced_native::renderer; use iced_native::widget::image; use iced_native::{Hasher, Length, Point, Rectangle}; -use std::any::{self, Any}; use std::hash::Hash; pub use image::Image; @@ -16,20 +15,6 @@ where Handle: Clone + Hash, Renderer: iced_native::image::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) - } - - fn children_state(&self) -> Vec { - Vec::new() - } - - fn diff(&self, _tree: &mut Tree) {} - fn width(&self) -> Length { >::width(self) } diff --git a/pure/src/widget/radio.rs b/pure/src/widget/radio.rs index 25fe5bdd..233297b3 100644 --- a/pure/src/widget/radio.rs +++ b/pure/src/widget/radio.rs @@ -7,8 +7,6 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; -use std::any::{self, Any}; - pub use iced_native::widget::Radio; impl<'a, Message, Renderer> Widget @@ -17,20 +15,6 @@ where Message: Clone, Renderer: text::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) - } - - fn diff(&self, _tree: &mut Tree) {} - - fn children_state(&self) -> Vec { - Vec::new() - } - fn width(&self) -> Length { >::width(self) } diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index ec7e144c..1f281446 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -9,8 +9,6 @@ use iced_native::{ Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell, }; -use std::any::{self, Any}; - pub struct Row<'a, Message, Renderer> { spacing: u16, padding: Padding, @@ -77,22 +75,14 @@ impl<'a, Message, Renderer> Widget where Renderer: iced_native::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() } fn diff(&self, tree: &mut Tree) { tree.diff_children(&self.children) } - fn children_state(&self) -> Vec { - self.children.iter().map(Tree::new).collect() - } - fn width(&self) -> Length { self.width } diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index badc9fc2..c3289f9e 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,4 +1,4 @@ -use crate::widget::Tree; +use crate::widget::tree::{self, Tree}; use crate::{Element, Widget}; use iced_native::event::{self, Event}; @@ -10,8 +10,6 @@ use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; pub use iced_style::scrollable::StyleSheet; -use std::any::{self, Any}; - /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] @@ -92,20 +90,20 @@ impl<'a, Message, Renderer> Widget where Renderer: iced_native::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::() + fn tag(&self) -> tree::Tag { + tree::Tag::of::() } - fn state(&self) -> Box { - Box::new(scrollable::State::new()) + fn state(&self) -> tree::State { + tree::State::new(scrollable::State::new()) } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)) + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] } - fn children_state(&self) -> Vec { - vec![Tree::new(&self.content)] + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) } fn width(&self) -> Length { diff --git a/pure/src/widget/slider.rs b/pure/src/widget/slider.rs index f659c2ed..691d3f18 100644 --- a/pure/src/widget/slider.rs +++ b/pure/src/widget/slider.rs @@ -1,7 +1,8 @@ //! Display an interactive selector of a single value from a range of values. //! //! A [`Slider`] has some local [`State`]. -use crate::{Element, Tree, Widget}; +use crate::widget::tree::{self, Tree}; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout; @@ -12,7 +13,6 @@ use iced_native::{ Clipboard, Hasher, Layout, Length, Point, Rectangle, Shell, Size, }; -use std::any::{self, Any}; use std::ops::RangeInclusive; pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; @@ -143,16 +143,12 @@ where Message: Clone, Renderer: iced_native::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::() + fn tag(&self) -> tree::Tag { + tree::Tag::of::() } - fn state(&self) -> Box { - Box::new(slider::State::new()) - } - - fn children_state(&self) -> Vec { - Vec::new() + fn state(&self) -> tree::State { + tree::State::new(slider::State::new()) } fn width(&self) -> Length { diff --git a/pure/src/widget/space.rs b/pure/src/widget/space.rs index 67d17c16..d7394398 100644 --- a/pure/src/widget/space.rs +++ b/pure/src/widget/space.rs @@ -7,8 +7,6 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; -use std::any::{self, Any}; - pub use iced_native::widget::Space; impl<'a, Message, Renderer> Widget for Space @@ -16,20 +14,6 @@ where Message: Clone, Renderer: text::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) - } - - fn diff(&self, _tree: &mut Tree) {} - - fn children_state(&self) -> Vec { - Vec::new() - } - fn width(&self) -> Length { >::width(self) } diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index 8f157ea0..696d0ae1 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -5,28 +5,12 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Hasher, Length, Point, Rectangle}; -use std::any::{self, Any}; - pub use iced_native::widget::Text; impl Widget for Text where Renderer: text::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) - } - - fn diff(&self, _tree: &mut Tree) {} - - fn children_state(&self) -> Vec { - Vec::new() - } - fn width(&self) -> Length { >::width(self) } diff --git a/pure/src/widget/text_input.rs b/pure/src/widget/text_input.rs index e18a2bf0..40ce140c 100644 --- a/pure/src/widget/text_input.rs +++ b/pure/src/widget/text_input.rs @@ -1,4 +1,5 @@ -use crate::widget::{Element, Tree, Widget}; +use crate::widget::tree::{self, Tree}; +use crate::widget::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; @@ -12,8 +13,6 @@ use iced_native::{ pub use iced_style::text_input::StyleSheet; -use std::any::{self, Any}; - /// A field that can be filled with text. /// /// # Example @@ -138,18 +137,12 @@ where Message: Clone, Renderer: iced_native::text::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::() - } - - fn state(&self) -> Box { - Box::new(text_input::State::new()) + fn tag(&self) -> tree::Tag { + tree::Tag::of::() } - fn diff(&self, _tree: &mut Tree) {} - - fn children_state(&self) -> Vec { - Vec::new() + fn state(&self) -> tree::State { + tree::State::new(text_input::State::new()) } fn width(&self) -> Length { diff --git a/pure/src/widget/toggler.rs b/pure/src/widget/toggler.rs index ec86fff0..08619866 100644 --- a/pure/src/widget/toggler.rs +++ b/pure/src/widget/toggler.rs @@ -8,8 +8,6 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; -use std::any::{self, Any}; - pub use iced_native::widget::toggler::{Style, StyleSheet, Toggler}; impl<'a, Message, Renderer> Widget @@ -17,18 +15,6 @@ impl<'a, Message, Renderer> Widget where Renderer: text::Renderer, { - fn tag(&self) -> any::TypeId { - any::TypeId::of::<()>() - } - - fn state(&self) -> Box { - Box::new(()) - } - - fn children_state(&self) -> Vec { - Vec::new() - } - fn width(&self) -> Length { >::width(self) } diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 3a5f4433..33f5693a 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -3,7 +3,7 @@ use crate::widget::Element; use std::any::{self, Any}; pub struct Tree { - pub tag: any::TypeId, + pub tag: Tag, pub state: State, pub children: Vec, } @@ -11,8 +11,8 @@ pub struct Tree { impl Tree { pub fn empty() -> Self { Self { - tag: any::TypeId::of::<()>(), - state: State(Box::new(())), + tag: Tag::stateless(), + state: State::None, children: Vec::new(), } } @@ -22,8 +22,8 @@ impl Tree { ) -> Self { Self { tag: element.as_widget().tag(), - state: State(element.as_widget().state()), - children: element.as_widget().children_state(), + state: element.as_widget().state(), + children: element.as_widget().children(), } } @@ -60,20 +60,56 @@ impl Tree { } } -pub struct State(Box); +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct Tag(any::TypeId); + +impl Tag { + pub fn of() -> Self + where + T: 'static, + { + Self(any::TypeId::of::()) + } + + pub fn stateless() -> Self { + Self::of::<()>() + } +} + +pub enum State { + None, + Some(Box), +} impl State { + pub fn new(state: T) -> Self + where + T: 'static, + { + State::Some(Box::new(state)) + } + pub fn downcast_ref(&self) -> &T where T: 'static, { - self.0.downcast_ref().expect("Downcast widget state") + match self { + State::None => panic!("Downcast on stateless state"), + State::Some(state) => { + state.downcast_ref().expect("Downcast widget state") + } + } } pub fn downcast_mut(&mut self) -> &mut T where T: 'static, { - self.0.downcast_mut().expect("Downcast widget state") + match self { + State::None => panic!("Downcast on stateless state"), + State::Some(state) => { + state.downcast_mut().expect("Downcast widget state") + } + } } } -- cgit From 019af8ddbf96680ffcee2b3407819e90575760cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 17:07:25 +0700 Subject: Add `overlay` support in `iced_pure` and port `PickList` :tada: --- pure/src/widget/button.rs | 14 +++ pure/src/widget/column.rs | 10 ++ pure/src/widget/container.rs | 14 +++ pure/src/widget/pick_list.rs | 245 ++++++++++++++++++++++++++++++++++++++++++ pure/src/widget/row.rs | 10 ++ pure/src/widget/scrollable.rs | 14 +++ 6 files changed, 307 insertions(+) create mode 100644 pure/src/widget/pick_list.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 55cbf8b4..f5e78933 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -1,3 +1,4 @@ +use crate::overlay; use crate::widget::tree::{self, Tree}; use crate::widget::{Element, Widget}; @@ -206,6 +207,19 @@ where self.on_press.is_some(), ) } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 4ab3e00d..1f025335 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -1,4 +1,5 @@ use crate::flex; +use crate::overlay; use crate::widget::{Element, Tree, Widget}; use iced_native::event::{self, Event}; @@ -216,6 +217,15 @@ where child.as_widget().hash_layout(state); } } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs index f42b127d..8ad6a064 100644 --- a/pure/src/widget/container.rs +++ b/pure/src/widget/container.rs @@ -5,6 +5,7 @@ use iced_native::alignment; use iced_native::event::{self, Event}; use iced_native::layout; use iced_native::mouse; +use iced_native::overlay; use iced_native::renderer; use iced_native::widget::container; use iced_native::{ @@ -237,6 +238,19 @@ where self.content.as_widget().hash_layout(state); } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> From> diff --git a/pure/src/widget/pick_list.rs b/pure/src/widget/pick_list.rs new file mode 100644 index 00000000..324950e1 --- /dev/null +++ b/pure/src/widget/pick_list.rs @@ -0,0 +1,245 @@ +//! Display a dropdown list of selectable values. +use crate::widget::tree::{self, Tree}; +use crate::{Element, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::text; +use iced_native::widget::pick_list; +use iced_native::{ + Clipboard, Hasher, Layout, Length, Padding, Point, Rectangle, Shell, +}; + +use std::borrow::Cow; + +pub use iced_style::pick_list::{Style, StyleSheet}; + +/// A widget for selecting a single value from a list of options. +#[allow(missing_debug_implementations)] +pub struct PickList<'a, T, Message, Renderer: text::Renderer> +where + [T]: ToOwned>, +{ + on_selected: Box Message + 'a>, + options: Cow<'a, [T]>, + placeholder: Option, + selected: Option, + width: Length, + padding: Padding, + text_size: Option, + font: Renderer::Font, + style_sheet: Box, +} + +impl<'a, T: 'a, Message, Renderer: text::Renderer> + PickList<'a, T, Message, Renderer> +where + T: ToString + Eq, + [T]: ToOwned>, +{ + /// The default padding of a [`PickList`]. + pub const DEFAULT_PADDING: Padding = Padding::new(5); + + /// Creates a new [`PickList`] with the given [`State`], a list of options, + /// the current selected value, and the message to produce when an option is + /// selected. + pub fn new( + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'a, + ) -> Self { + Self { + on_selected: Box::new(on_selected), + options: options.into(), + placeholder: None, + selected, + width: Length::Shrink, + text_size: None, + padding: Self::DEFAULT_PADDING, + font: Default::default(), + style_sheet: Default::default(), + } + } + + /// Sets the placeholder of the [`PickList`]. + pub fn placeholder(mut self, placeholder: impl Into) -> Self { + self.placeholder = Some(placeholder.into()); + self + } + + /// Sets the width of the [`PickList`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the [`Padding`] of the [`PickList`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the text size of the [`PickList`]. + pub fn text_size(mut self, size: u16) -> Self { + self.text_size = Some(size); + self + } + + /// Sets the font of the [`PickList`]. + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + + /// Sets the style of the [`PickList`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +impl<'a, T: 'a, Message, Renderer> Widget + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq + 'static, + [T]: ToOwned>, + Message: 'static, + Renderer: text::Renderer + 'a, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(pick_list::State::::new()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + pick_list::layout( + renderer, + limits, + self.width, + self.padding, + self.text_size, + &self.font, + self.placeholder.as_ref().map(String::as_str), + &self.options, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + pick_list::hash_layout( + state, + self.width, + self.padding, + self.text_size, + self.placeholder.as_ref().map(String::as_str), + &self.options, + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + pick_list::update( + event, + layout, + cursor_position, + shell, + self.on_selected.as_ref(), + self.selected.as_ref(), + &self.options, + || tree.state.downcast_mut::>(), + ) + } + + fn mouse_interaction( + &self, + _tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + pick_list::mouse_interaction(layout, cursor_position) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + pick_list::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<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + let state = tree.state.downcast_mut::>(); + + pick_list::overlay( + layout, + state, + self.padding, + self.text_size, + self.font.clone(), + &self.options, + self.style_sheet.as_ref(), + ) + } +} + +impl<'a, T: 'a, Message, Renderer> Into> + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq + 'static, + [T]: ToOwned>, + Renderer: text::Renderer + 'a, + Message: 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index 1f281446..29128589 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -1,4 +1,5 @@ use crate::flex; +use crate::overlay; use crate::widget::{Element, Tree, Widget}; use iced_native::event::{self, Event}; @@ -202,6 +203,15 @@ where child.as_widget().hash_layout(state); } } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + overlay::from_children(&mut self.children, tree, layout, renderer) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index c3289f9e..6653125e 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -1,3 +1,4 @@ +use crate::overlay; use crate::widget::tree::{self, Tree}; use crate::{Element, Widget}; @@ -230,6 +231,19 @@ where }, ) } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> From> -- cgit From 0ca066277a296469fff95bef48e8c23e1d2b375e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 17:15:56 +0700 Subject: Fix `overlay` translation for `Scrollable` in `iced_pure` --- pure/src/widget/scrollable.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 6653125e..8a206a6c 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -7,7 +7,7 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::renderer; use iced_native::widget::scrollable; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Vector}; pub use iced_style::scrollable::StyleSheet; @@ -238,11 +238,24 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - ) + self.content + .as_widget_mut() + .overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + .map(|overlay| { + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + let offset = tree + .state + .downcast_ref::() + .offset(bounds, content_bounds); + + overlay.translate(Vector::new(0.0, -(offset as f32))) + }) } } -- cgit From da45b6c1627935bff5334d213096c4e78972af46 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 17 Feb 2022 19:08:54 +0700 Subject: Implement `pure::Component` in `iced_lazy` --- pure/src/widget/button.rs | 4 ++-- pure/src/widget/column.rs | 4 ++-- pure/src/widget/container.rs | 4 ++-- pure/src/widget/pick_list.rs | 2 +- pure/src/widget/row.rs | 4 ++-- pure/src/widget/scrollable.rs | 4 ++-- pure/src/widget/space.rs | 7 ++----- 7 files changed, 13 insertions(+), 16 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index f5e78933..2ed67a9c 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -209,12 +209,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( + self.content.as_widget().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 1f025335..698d7e9c 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -219,12 +219,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer) + overlay::from_children(&self.children, tree, layout, renderer) } } diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs index 8ad6a064..c8f0b3a2 100644 --- a/pure/src/widget/container.rs +++ b/pure/src/widget/container.rs @@ -240,12 +240,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.content.as_widget_mut().overlay( + self.content.as_widget().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, diff --git a/pure/src/widget/pick_list.rs b/pure/src/widget/pick_list.rs index 324950e1..9dc847ee 100644 --- a/pure/src/widget/pick_list.rs +++ b/pure/src/widget/pick_list.rs @@ -212,7 +212,7 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index 29128589..1c574d51 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -205,12 +205,12 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - overlay::from_children(&mut self.children, tree, layout, renderer) + overlay::from_children(&self.children, tree, layout, renderer) } } diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 8a206a6c..bbda50e5 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -233,13 +233,13 @@ where } fn overlay<'b>( - &'b mut self, + &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { self.content - .as_widget_mut() + .as_widget() .overlay( &mut tree.children[0], layout.children().next().unwrap(), diff --git a/pure/src/widget/space.rs b/pure/src/widget/space.rs index d7394398..e0c9c285 100644 --- a/pure/src/widget/space.rs +++ b/pure/src/widget/space.rs @@ -4,15 +4,13 @@ use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::renderer; -use iced_native::text; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; pub use iced_native::widget::Space; impl<'a, Message, Renderer> Widget for Space where - Message: Clone, - Renderer: text::Renderer, + Renderer: iced_native::Renderer, { fn width(&self) -> Length { >::width(self) @@ -98,8 +96,7 @@ where impl<'a, Message, Renderer> Into> for Space where - Message: 'a + Clone, - Renderer: text::Renderer + 'a, + Renderer: iced_native::Renderer + 'a, { fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) -- cgit From 0fbd1d98b5534a85eaa8bff40f5fa1d395edc977 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Mar 2022 16:58:55 +0700 Subject: Implement `pure` version of `Rule` widget --- pure/src/widget/rule.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 pure/src/widget/rule.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/rule.rs b/pure/src/widget/rule.rs new file mode 100644 index 00000000..375bed9e --- /dev/null +++ b/pure/src/widget/rule.rs @@ -0,0 +1,98 @@ +use crate::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; + +pub use iced_native::widget::rule::*; + +impl<'a, Message, Renderer> Widget for Rule<'a> +where + Renderer: iced_native::Renderer, +{ + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + >::on_event( + self, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + >::mouse_interaction( + self, + layout, + cursor_position, + viewport, + renderer, + ) + } +} + +impl<'a, Message, Renderer> Into> for Rule<'a> +where + Renderer: iced_native::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From 3efb59dea3d206a9d627ce5a7a7a93c00d769ba8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Mar 2022 17:01:57 +0700 Subject: Implement `pure` version of `ProgressBar` widget --- pure/src/widget/progress_bar.rs | 99 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 pure/src/widget/progress_bar.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/progress_bar.rs b/pure/src/widget/progress_bar.rs new file mode 100644 index 00000000..9b996f02 --- /dev/null +++ b/pure/src/widget/progress_bar.rs @@ -0,0 +1,99 @@ +use crate::{Element, Tree, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; + +pub use iced_native::widget::progress_bar::*; + +impl<'a, Message, Renderer> Widget for ProgressBar<'a> +where + Renderer: iced_native::Renderer, +{ + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + >::on_event( + self, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + >::mouse_interaction( + self, + layout, + cursor_position, + viewport, + renderer, + ) + } +} + +impl<'a, Message, Renderer> Into> + for ProgressBar<'a> +where + Renderer: iced_native::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit From 6dd187ff0822230f084e43636b1aabeb1baf06f6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 10 Mar 2022 19:25:57 +0700 Subject: Implement `pure` version of `PaneGrid` widget --- pure/src/widget/pane_grid.rs | 399 +++++++++++++++++++++++++++++++++ pure/src/widget/pane_grid/content.rs | 331 +++++++++++++++++++++++++++ pure/src/widget/pane_grid/title_bar.rs | 355 +++++++++++++++++++++++++++++ pure/src/widget/tree.rs | 17 +- 4 files changed, 1100 insertions(+), 2 deletions(-) create mode 100644 pure/src/widget/pane_grid.rs create mode 100644 pure/src/widget/pane_grid/content.rs create mode 100644 pure/src/widget/pane_grid/title_bar.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/pane_grid.rs b/pure/src/widget/pane_grid.rs new file mode 100644 index 00000000..717c9ceb --- /dev/null +++ b/pure/src/widget/pane_grid.rs @@ -0,0 +1,399 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +//! +//! # Example +//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, +//! drag and drop, and hotkey support. +//! +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.3/examples/pane_grid +mod content; +mod title_bar; + +pub use content::Content; +pub use title_bar::TitleBar; + +pub use iced_native::widget::pane_grid::{ + Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, + State, +}; + +use crate::overlay; +use crate::widget::tree::{self, Tree}; +use crate::{Element, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::pane_grid; +use iced_native::widget::pane_grid::state; +use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell}; + +pub use iced_style::pane_grid::{Line, StyleSheet}; + +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](https://gfycat.com/frailfreshairedaleterrier) +/// +/// This distribution of space is common in tiling window managers (like +/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even +/// [`tmux`](https://github.com/tmux/tmux)). +/// +/// A [`PaneGrid`] supports: +/// +/// * Vertical and horizontal splits +/// * Tracking of the last active pane +/// * Mouse-based resizing +/// * Drag and drop to reorganize panes +/// * Hotkey support +/// * Configurable modifier keys +/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.) +/// +/// ## Example +/// +/// ``` +/// # use iced_pure::widget::{pane_grid, text}; +/// # +/// # type PaneGrid<'a, Message> = +/// # iced_pure::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; +/// # +/// enum PaneState { +/// SomePane, +/// AnotherKindOfPane, +/// } +/// +/// enum Message { +/// PaneDragged(pane_grid::DragEvent), +/// PaneResized(pane_grid::ResizeEvent), +/// } +/// +/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); +/// +/// let pane_grid = +/// PaneGrid::new(&state, |pane, state| { +/// pane_grid::Content::new(match state { +/// PaneState::SomePane => text("This is some pane"), +/// PaneState::AnotherKindOfPane => text("This is another kind of pane"), +/// }) +/// }) +/// .on_drag(Message::PaneDragged) +/// .on_resize(10, Message::PaneResized); +/// ``` +#[allow(missing_debug_implementations)] +pub struct PaneGrid<'a, Message, Renderer> { + state: &'a state::Internal, + elements: Vec<(Pane, Content<'a, Message, Renderer>)>, + width: Length, + height: Length, + spacing: u16, + on_click: Option Message + 'a>>, + on_drag: Option Message + 'a>>, + on_resize: Option<(u16, Box Message + 'a>)>, + style_sheet: Box, +} + +impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates a [`PaneGrid`] with the given [`State`] and view function. + /// + /// The view function will be called to display each [`Pane`] present in the + /// [`State`]. + pub fn new( + state: &'a State, + view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>, + ) -> Self { + let elements = { + state + .panes + .iter() + .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) + .collect() + }; + + Self { + elements, + state: &state.internal, + width: Length::Fill, + height: Length::Fill, + spacing: 0, + on_click: None, + on_drag: None, + on_resize: None, + style_sheet: Default::default(), + } + } + + /// Sets the width of the [`PaneGrid`]. + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`PaneGrid`]. + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the spacing _between_ the panes of the [`PaneGrid`]. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the message that will be produced when a [`Pane`] of the + /// [`PaneGrid`] is clicked. + pub fn on_click(mut self, f: F) -> Self + where + F: 'a + Fn(Pane) -> Message, + { + self.on_click = Some(Box::new(f)); + self + } + + /// Enables the drag and drop interactions of the [`PaneGrid`], which will + /// use the provided function to produce messages. + pub fn on_drag(mut self, f: F) -> Self + where + F: 'a + Fn(DragEvent) -> Message, + { + self.on_drag = Some(Box::new(f)); + self + } + + /// Enables the resize interactions of the [`PaneGrid`], which will + /// use the provided function to produce messages. + /// + /// The `leeway` describes the amount of space around a split that can be + /// used to grab it. + /// + /// The grabbable area of a split will have a length of `spacing + leeway`, + /// properly centered. In other words, a length of + /// `(spacing + leeway) / 2.0` on either side of the split line. + pub fn on_resize(mut self, leeway: u16, f: F) -> Self + where + F: 'a + Fn(ResizeEvent) -> Message, + { + self.on_resize = Some((leeway, Box::new(f))); + self + } + + /// Sets the style of the [`PaneGrid`]. + pub fn style(mut self, style: impl Into>) -> Self { + self.style_sheet = style.into(); + self + } +} + +impl<'a, Message, Renderer> Widget + for PaneGrid<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(state::Action::Idle) + } + + fn children(&self) -> Vec { + self.elements + .iter() + .map(|(_, content)| content.state()) + .collect() + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children_custom( + &self.elements, + |(_, content), state| content.diff(state), + |(_, content)| content.state(), + ) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + pane_grid::layout( + renderer, + limits, + self.state, + self.width, + self.height, + self.spacing, + self.elements.iter().map(|(pane, content)| (*pane, content)), + |element, renderer, limits| element.layout(renderer, limits), + ) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let action = tree.state.downcast_mut::(); + + let event_status = pane_grid::update( + action, + self.state, + &event, + layout, + cursor_position, + shell, + self.spacing, + self.elements.iter().map(|(pane, content)| (*pane, content)), + &self.on_click, + &self.on_drag, + &self.on_resize, + ); + + let picked_pane = action.picked_pane().map(|(pane, _)| pane); + + self.elements + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|(((pane, content), tree), layout)| { + let is_picked = picked_pane == Some(*pane); + + content.on_event( + tree, + event.clone(), + layout, + cursor_position, + renderer, + clipboard, + shell, + is_picked, + ) + }) + .fold(event_status, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + pane_grid::mouse_interaction( + tree.state.downcast_ref(), + self.state, + layout, + cursor_position, + self.spacing, + self.on_resize.as_ref().map(|(leeway, _)| *leeway), + ) + .unwrap_or_else(|| { + self.elements + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|(((_pane, content), tree), layout)| { + content.mouse_interaction( + tree, + layout, + cursor_position, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + }) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + pane_grid::draw( + tree.state.downcast_ref(), + self.state, + layout, + cursor_position, + renderer, + style, + viewport, + self.spacing, + self.on_resize.as_ref().map(|(leeway, _)| *leeway), + self.style_sheet.as_ref(), + self.elements + .iter() + .zip(&tree.children) + .map(|((pane, content), tree)| (*pane, (content, tree))), + |(content, tree), + renderer, + style, + layout, + cursor_position, + rectangle| { + content.draw( + tree, + renderer, + style, + layout, + cursor_position, + rectangle, + ); + }, + ) + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.elements + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .filter_map(|(((_, pane), tree), layout)| { + pane.overlay(tree, layout, renderer) + }) + .next() + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + iced_native::Renderer, + Message: 'a, +{ + fn from( + pane_grid: PaneGrid<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(pane_grid) + } +} diff --git a/pure/src/widget/pane_grid/content.rs b/pure/src/widget/pane_grid/content.rs new file mode 100644 index 00000000..a928b28c --- /dev/null +++ b/pure/src/widget/pane_grid/content.rs @@ -0,0 +1,331 @@ +use crate::widget::pane_grid::TitleBar; +use crate::widget::tree::Tree; +use crate::Element; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::container; +use iced_native::widget::pane_grid::Draggable; +use iced_native::{Clipboard, Layout, Point, Rectangle, Shell, Size}; + +/// The content of a [`Pane`]. +/// +/// [`Pane`]: crate::widget::pane_grid::Pane +#[allow(missing_debug_implementations)] +pub struct Content<'a, Message, Renderer> { + title_bar: Option>, + body: Element<'a, Message, Renderer>, + style_sheet: Box, +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates a new [`Content`] with the provided body. + pub fn new(body: impl Into>) -> Self { + Self { + title_bar: None, + body: body.into(), + style_sheet: Default::default(), + } + } + + /// Sets the [`TitleBar`] of this [`Content`]. + pub fn title_bar( + mut self, + title_bar: TitleBar<'a, Message, Renderer>, + ) -> Self { + self.title_bar = Some(title_bar); + self + } + + /// Sets the style of the [`Content`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + pub fn state(&self) -> Tree { + let children = if let Some(title_bar) = self.title_bar.as_ref() { + vec![Tree::new(&self.body), title_bar.state()] + } else { + vec![Tree::new(&self.body), Tree::empty()] + }; + + Tree { + children, + ..Tree::empty() + } + } + + pub fn diff(&self, tree: &mut Tree) { + if tree.children.len() == 2 { + if let Some(title_bar) = self.title_bar.as_ref() { + title_bar.diff(&mut tree.children[1]); + } + + tree.children[0].diff(&self.body); + } else { + *tree = self.state(); + } + } + + /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. + /// + /// [`Renderer`]: crate::widget::pane_grid::Renderer + pub fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + + { + let style = self.style_sheet.style(); + + container::draw_background(renderer, &style, bounds); + } + + if let Some(title_bar) = &self.title_bar { + let mut children = layout.children(); + let title_bar_layout = children.next().unwrap(); + let body_layout = children.next().unwrap(); + + let show_controls = bounds.contains(cursor_position); + + title_bar.draw( + &tree.children[1], + renderer, + style, + title_bar_layout, + cursor_position, + viewport, + show_controls, + ); + + self.body.as_widget().draw( + &tree.children[0], + renderer, + style, + body_layout, + cursor_position, + viewport, + ); + } else { + self.body.as_widget().draw( + &tree.children[0], + renderer, + style, + layout, + cursor_position, + viewport, + ); + } + } + + pub(crate) fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + if let Some(title_bar) = &self.title_bar { + let max_size = limits.max(); + + let title_bar_layout = title_bar + .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + + let title_bar_size = title_bar_layout.size(); + + let mut body_layout = self.body.as_widget().layout( + renderer, + &layout::Limits::new( + Size::ZERO, + Size::new( + max_size.width, + max_size.height - title_bar_size.height, + ), + ), + ); + + body_layout.move_to(Point::new(0.0, title_bar_size.height)); + + layout::Node::with_children( + max_size, + vec![title_bar_layout, body_layout], + ) + } else { + self.body.as_widget().layout(renderer, limits) + } + } + + pub(crate) fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + is_picked: bool, + ) -> event::Status { + let mut event_status = event::Status::Ignored; + + let body_layout = if let Some(title_bar) = &mut self.title_bar { + let mut children = layout.children(); + + event_status = title_bar.on_event( + &mut tree.children[1], + event.clone(), + children.next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ); + + children.next().unwrap() + } else { + layout + }; + + let body_status = if is_picked { + event::Status::Ignored + } else { + self.body.as_widget_mut().on_event( + &mut tree.children[0], + event, + body_layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }; + + event_status.merge(body_status) + } + + pub(crate) fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let (body_layout, title_bar_interaction) = + if let Some(title_bar) = &self.title_bar { + let mut children = layout.children(); + let title_bar_layout = children.next().unwrap(); + + let is_over_pick_area = title_bar + .is_over_pick_area(title_bar_layout, cursor_position); + + if is_over_pick_area { + return mouse::Interaction::Grab; + } + + let mouse_interaction = title_bar.mouse_interaction( + &tree.children[1], + title_bar_layout, + cursor_position, + viewport, + renderer, + ); + + (children.next().unwrap(), mouse_interaction) + } else { + (layout, mouse::Interaction::default()) + }; + + self.body + .as_widget() + .mouse_interaction( + &tree.children[0], + body_layout, + cursor_position, + viewport, + renderer, + ) + .max(title_bar_interaction) + } + + pub(crate) fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + if let Some(title_bar) = self.title_bar.as_ref() { + let mut children = layout.children(); + let title_bar_layout = children.next()?; + + let mut states = tree.children.iter_mut(); + let body_state = states.next().unwrap(); + let title_bar_state = states.next().unwrap(); + + match title_bar.overlay(title_bar_state, title_bar_layout, renderer) + { + Some(overlay) => Some(overlay), + None => self.body.as_widget().overlay( + body_state, + children.next()?, + renderer, + ), + } + } else { + self.body.as_widget().overlay( + &mut tree.children[0], + layout, + renderer, + ) + } + } +} + +impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn can_be_dragged_at( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + if let Some(title_bar) = &self.title_bar { + let mut children = layout.children(); + let title_bar_layout = children.next().unwrap(); + + title_bar.is_over_pick_area(title_bar_layout, cursor_position) + } else { + false + } + } +} + +impl<'a, T, Message, Renderer> From for Content<'a, Message, Renderer> +where + T: Into>, + Renderer: iced_native::Renderer, +{ + fn from(element: T) -> Self { + Self::new(element) + } +} diff --git a/pure/src/widget/pane_grid/title_bar.rs b/pure/src/widget/pane_grid/title_bar.rs new file mode 100644 index 00000000..dd68b073 --- /dev/null +++ b/pure/src/widget/pane_grid/title_bar.rs @@ -0,0 +1,355 @@ +use crate::widget::Tree; +use crate::Element; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::container; +use iced_native::{Clipboard, Layout, Padding, Point, Rectangle, Shell, Size}; + +/// The title bar of a [`Pane`]. +/// +/// [`Pane`]: crate::widget::pane_grid::Pane +#[allow(missing_debug_implementations)] +pub struct TitleBar<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, + controls: Option>, + padding: Padding, + always_show_controls: bool, + style_sheet: Box, +} + +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates a new [`TitleBar`] with the given content. + pub fn new(content: E) -> Self + where + E: Into>, + { + Self { + content: content.into(), + controls: None, + padding: Padding::ZERO, + always_show_controls: false, + style_sheet: Default::default(), + } + } + + /// Sets the controls of the [`TitleBar`]. + pub fn controls( + mut self, + controls: impl Into>, + ) -> Self { + self.controls = Some(controls.into()); + self + } + + /// Sets the [`Padding`] of the [`TitleBar`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the style of the [`TitleBar`]. + pub fn style( + mut self, + style: impl Into>, + ) -> Self { + self.style_sheet = style.into(); + self + } + + /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are + /// always visible. + /// + /// By default, the controls are only visible when the [`Pane`] of this + /// [`TitleBar`] is hovered. + /// + /// [`controls`]: Self::controls + /// [`Pane`]: crate::widget::pane_grid::Pane + pub fn always_show_controls(mut self) -> Self { + self.always_show_controls = true; + self + } +} + +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + pub fn state(&self) -> Tree { + let children = if let Some(controls) = self.controls.as_ref() { + vec![Tree::new(&self.content), Tree::new(controls)] + } else { + vec![Tree::new(&self.content), Tree::empty()] + }; + + Tree { + children, + ..Tree::empty() + } + } + + pub fn diff(&self, tree: &mut Tree) { + if tree.children.len() == 2 { + if let Some(controls) = self.controls.as_ref() { + tree.children[1].diff(controls); + } + + tree.children[0].diff(&self.content); + } else { + *tree = self.state(); + } + } + + /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. + /// + /// [`Renderer`]: crate::widget::pane_grid::Renderer + pub fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + inherited_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + show_controls: bool, + ) { + let bounds = layout.bounds(); + let style = self.style_sheet.style(); + let inherited_style = renderer::Style { + text_color: style.text_color.unwrap_or(inherited_style.text_color), + }; + + container::draw_background(renderer, &style, bounds); + + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + + self.content.as_widget().draw( + &tree.children[0], + renderer, + &inherited_style, + title_layout, + cursor_position, + viewport, + ); + + if let Some(controls) = &self.controls { + let controls_layout = children.next().unwrap(); + + if show_controls || self.always_show_controls { + controls.as_widget().draw( + &tree.children[1], + renderer, + &inherited_style, + controls_layout, + cursor_position, + viewport, + ); + } + } + } + + /// Returns whether the mouse cursor is over the pick area of the + /// [`TitleBar`] or not. + /// + /// The whole [`TitleBar`] is a pick area, except its controls. + pub fn is_over_pick_area( + &self, + layout: Layout<'_>, + cursor_position: Point, + ) -> bool { + if layout.bounds().contains(cursor_position) { + let mut children = layout.children(); + let padded = children.next().unwrap(); + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + + if self.controls.is_some() { + let controls_layout = children.next().unwrap(); + + !controls_layout.bounds().contains(cursor_position) + && !title_layout.bounds().contains(cursor_position) + } else { + !title_layout.bounds().contains(cursor_position) + } + } else { + false + } + } + + pub(crate) fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.pad(self.padding); + let max_size = limits.max(); + + let title_layout = self + .content + .as_widget() + .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + + let title_size = title_layout.size(); + + let mut node = if let Some(controls) = &self.controls { + let mut controls_layout = controls + .as_widget() + .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + + let controls_size = controls_layout.size(); + let space_before_controls = max_size.width - controls_size.width; + + let height = title_size.height.max(controls_size.height); + + controls_layout.move_to(Point::new(space_before_controls, 0.0)); + + layout::Node::with_children( + Size::new(max_size.width, height), + vec![title_layout, controls_layout], + ) + } else { + layout::Node::with_children( + Size::new(max_size.width, title_size.height), + vec![title_layout], + ) + }; + + node.move_to(Point::new( + self.padding.left.into(), + self.padding.top.into(), + )); + + layout::Node::with_children(node.size().pad(self.padding), vec![node]) + } + + pub(crate) fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + + let control_status = if let Some(controls) = &mut self.controls { + let controls_layout = children.next().unwrap(); + + controls.as_widget_mut().on_event( + &mut tree.children[1], + event.clone(), + controls_layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } else { + event::Status::Ignored + }; + + let title_status = self.content.as_widget_mut().on_event( + &mut tree.children[0], + event, + title_layout, + cursor_position, + renderer, + clipboard, + shell, + ); + + control_status.merge(title_status) + } + + pub(crate) fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + + let title_interaction = self.content.as_widget().mouse_interaction( + &tree.children[0], + title_layout, + cursor_position, + viewport, + renderer, + ); + + if let Some(controls) = &self.controls { + let controls_layout = children.next().unwrap(); + + controls + .as_widget() + .mouse_interaction( + &tree.children[1], + controls_layout, + cursor_position, + viewport, + renderer, + ) + .max(title_interaction) + } else { + title_interaction + } + } + + pub(crate) fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let mut children = layout.children(); + let padded = children.next()?; + + let mut children = padded.children(); + let title_layout = children.next()?; + + let Self { + content, controls, .. + } = self; + + let mut states = tree.children.iter_mut(); + let title_state = states.next().unwrap(); + let controls_state = states.next().unwrap(); + + content + .as_widget() + .overlay(title_state, title_layout, renderer) + .or_else(move || { + controls.as_ref().and_then(|controls| { + let controls_layout = children.next()?; + + controls.as_widget().overlay( + controls_state, + controls_layout, + renderer, + ) + }) + }) + } +} diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 33f5693a..3fcf0922 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -41,6 +41,19 @@ impl Tree { pub fn diff_children( &mut self, new_children: &[Element<'_, Message, Renderer>], + ) { + self.diff_children_custom( + new_children, + |new, child_state| child_state.diff(new), + Self::new, + ) + } + + pub fn diff_children_custom( + &mut self, + new_children: &[T], + diff: impl Fn(&T, &mut Tree), + new_state: impl Fn(&T) -> Self, ) { if self.children.len() > new_children.len() { self.children.truncate(new_children.len()); @@ -49,12 +62,12 @@ impl Tree { for (child_state, new) in self.children.iter_mut().zip(new_children.iter()) { - child_state.diff(new); + diff(new, child_state); } if self.children.len() < new_children.len() { self.children.extend( - new_children[self.children.len()..].iter().map(Self::new), + new_children[self.children.len()..].iter().map(new_state), ); } } -- cgit From d7100fd2597da82d97eaf196d50573ea64f3f8ff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Mar 2022 17:37:19 +0700 Subject: Export widget modules in `iced_pure` ... and fix collisions with the new `helpers` --- pure/src/widget/button.rs | 7 +- pure/src/widget/checkbox.rs | 5 +- pure/src/widget/column.rs | 3 +- pure/src/widget/container.rs | 3 +- pure/src/widget/element.rs | 163 ---------------------------------------- pure/src/widget/pane_grid.rs | 3 +- pure/src/widget/progress_bar.rs | 3 +- pure/src/widget/radio.rs | 5 +- pure/src/widget/row.rs | 3 +- pure/src/widget/rule.rs | 3 +- pure/src/widget/scrollable.rs | 2 +- pure/src/widget/space.rs | 3 +- pure/src/widget/text.rs | 3 +- pure/src/widget/text_input.rs | 4 +- pure/src/widget/tree.rs | 2 +- 15 files changed, 30 insertions(+), 182 deletions(-) delete mode 100644 pure/src/widget/element.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/button.rs b/pure/src/widget/button.rs index 4380b608..f99d3018 100644 --- a/pure/src/widget/button.rs +++ b/pure/src/widget/button.rs @@ -1,6 +1,6 @@ use crate::overlay; use crate::widget::tree::{self, Tree}; -use crate::widget::{Element, Widget}; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout; @@ -10,9 +10,10 @@ use iced_native::widget::button; use iced_native::{ Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, }; -use iced_style::button::StyleSheet; -pub use button::State; +pub use iced_style::button::{Style, StyleSheet}; + +use button::State; pub struct Button<'a, Message, Renderer> { content: Element<'a, Message, Renderer>, diff --git a/pure/src/widget/checkbox.rs b/pure/src/widget/checkbox.rs index 3448e616..971980e3 100644 --- a/pure/src/widget/checkbox.rs +++ b/pure/src/widget/checkbox.rs @@ -1,4 +1,5 @@ -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; @@ -7,7 +8,7 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; -pub use iced_native::widget::Checkbox; +pub use iced_native::widget::checkbox::{Checkbox, Style, StyleSheet}; impl<'a, Message, Renderer> Widget for Checkbox<'a, Message, Renderer> diff --git a/pure/src/widget/column.rs b/pure/src/widget/column.rs index 37ff96c5..6b447270 100644 --- a/pure/src/widget/column.rs +++ b/pure/src/widget/column.rs @@ -1,6 +1,7 @@ use crate::flex; use crate::overlay; -use crate::widget::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; diff --git a/pure/src/widget/container.rs b/pure/src/widget/container.rs index ebf69cab..91db1f3f 100644 --- a/pure/src/widget/container.rs +++ b/pure/src/widget/container.rs @@ -1,5 +1,6 @@ //! Decorate content and apply alignment. -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::alignment; use iced_native::event::{self, Event}; diff --git a/pure/src/widget/element.rs b/pure/src/widget/element.rs deleted file mode 100644 index 3d5697fe..00000000 --- a/pure/src/widget/element.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::widget::tree::{self, Tree}; -use crate::widget::Widget; - -use iced_native::event::{self, Event}; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; - -pub struct Element<'a, Message, Renderer> { - widget: Box + 'a>, -} - -impl<'a, Message, Renderer> Element<'a, Message, Renderer> { - pub fn new(widget: impl Widget + 'a) -> Self { - Self { - widget: Box::new(widget), - } - } - - pub fn as_widget(&self) -> &dyn Widget { - self.widget.as_ref() - } - - pub fn as_widget_mut(&mut self) -> &mut dyn Widget { - self.widget.as_mut() - } - - pub fn map( - self, - f: impl Fn(Message) -> B + 'a, - ) -> Element<'a, B, Renderer> - where - Message: 'a, - Renderer: iced_native::Renderer + 'a, - B: 'a, - { - Element::new(Map::new(self.widget, f)) - } -} - -struct Map<'a, A, B, Renderer> { - widget: Box + 'a>, - mapper: Box B + 'a>, -} - -impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { - pub fn new( - widget: Box + 'a>, - mapper: F, - ) -> Map<'a, A, B, Renderer> - where - F: 'a + Fn(A) -> B, - { - Map { - widget, - mapper: Box::new(mapper), - } - } -} - -impl<'a, A, B, Renderer> Widget for Map<'a, A, B, Renderer> -where - Renderer: iced_native::Renderer + 'a, - A: 'a, - B: 'a, -{ - fn tag(&self) -> tree::Tag { - self.widget.tag() - } - - fn state(&self) -> tree::State { - self.widget.state() - } - - fn children(&self) -> Vec { - self.widget.children() - } - - fn diff(&self, tree: &mut Tree) { - self.widget.diff(tree) - } - - fn width(&self) -> Length { - self.widget.width() - } - - fn height(&self) -> Length { - self.widget.height() - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.widget.layout(renderer, limits) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, B>, - ) -> event::Status { - let mut local_messages = Vec::new(); - let mut local_shell = Shell::new(&mut local_messages); - - let status = self.widget.on_event( - tree, - event, - layout, - cursor_position, - renderer, - clipboard, - &mut local_shell, - ); - - shell.merge(local_shell, &self.mapper); - - status - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - self.widget.draw( - tree, - renderer, - style, - layout, - cursor_position, - viewport, - ) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.widget.mouse_interaction( - tree, - layout, - cursor_position, - viewport, - renderer, - ) - } -} diff --git a/pure/src/widget/pane_grid.rs b/pure/src/widget/pane_grid.rs index 717c9ceb..34a56bcc 100644 --- a/pure/src/widget/pane_grid.rs +++ b/pure/src/widget/pane_grid.rs @@ -54,7 +54,8 @@ pub use iced_style::pane_grid::{Line, StyleSheet}; /// ## Example /// /// ``` -/// # use iced_pure::widget::{pane_grid, text}; +/// # use iced_pure::widget::pane_grid; +/// # use iced_pure::text; /// # /// # type PaneGrid<'a, Message> = /// # iced_pure::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; diff --git a/pure/src/widget/progress_bar.rs b/pure/src/widget/progress_bar.rs index 9b996f02..3f4ffd55 100644 --- a/pure/src/widget/progress_bar.rs +++ b/pure/src/widget/progress_bar.rs @@ -1,4 +1,5 @@ -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; diff --git a/pure/src/widget/radio.rs b/pure/src/widget/radio.rs index ce3ede84..c20f8f3e 100644 --- a/pure/src/widget/radio.rs +++ b/pure/src/widget/radio.rs @@ -1,4 +1,5 @@ -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; @@ -7,7 +8,7 @@ use iced_native::renderer; use iced_native::text; use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; -pub use iced_native::widget::Radio; +pub use iced_native::widget::radio::{Radio, Style, StyleSheet}; impl<'a, Message, Renderer> Widget for Radio<'a, Message, Renderer> diff --git a/pure/src/widget/row.rs b/pure/src/widget/row.rs index fa0efa68..d7f90540 100644 --- a/pure/src/widget/row.rs +++ b/pure/src/widget/row.rs @@ -1,6 +1,7 @@ use crate::flex; use crate::overlay; -use crate::widget::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; diff --git a/pure/src/widget/rule.rs b/pure/src/widget/rule.rs index 375bed9e..40b1fc90 100644 --- a/pure/src/widget/rule.rs +++ b/pure/src/widget/rule.rs @@ -1,4 +1,5 @@ -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; diff --git a/pure/src/widget/scrollable.rs b/pure/src/widget/scrollable.rs index 1548fa9d..f9a51200 100644 --- a/pure/src/widget/scrollable.rs +++ b/pure/src/widget/scrollable.rs @@ -9,7 +9,7 @@ use iced_native::renderer; use iced_native::widget::scrollable; use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Vector}; -pub use iced_style::scrollable::StyleSheet; +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. diff --git a/pure/src/widget/space.rs b/pure/src/widget/space.rs index c04d962a..b408153b 100644 --- a/pure/src/widget/space.rs +++ b/pure/src/widget/space.rs @@ -1,4 +1,5 @@ -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index bfcbaa4b..edc35cd1 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -1,4 +1,5 @@ -use crate::{Element, Tree, Widget}; +use crate::widget::Tree; +use crate::{Element, Widget}; use iced_native::layout::{self, Layout}; use iced_native::renderer; diff --git a/pure/src/widget/text_input.rs b/pure/src/widget/text_input.rs index dec11164..d6041d7f 100644 --- a/pure/src/widget/text_input.rs +++ b/pure/src/widget/text_input.rs @@ -1,5 +1,5 @@ use crate::widget::tree::{self, Tree}; -use crate::widget::{Element, Widget}; +use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; @@ -9,7 +9,7 @@ use iced_native::text; use iced_native::widget::text_input; use iced_native::{Clipboard, Length, Padding, Point, Rectangle, Shell}; -pub use iced_style::text_input::StyleSheet; +pub use iced_style::text_input::{Style, StyleSheet}; /// A field that can be filled with text. /// diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 3fcf0922..bd7c259c 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -1,4 +1,4 @@ -use crate::widget::Element; +use crate::Element; use std::any::{self, Any}; -- cgit From 9157f5b9e47713d5920a4e262c25a993998b312f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 22 Mar 2022 13:27:12 +0700 Subject: Use application lifetime in `Into` implementation for `&str` --- pure/src/widget/text.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'pure/src/widget') diff --git a/pure/src/widget/text.rs b/pure/src/widget/text.rs index edc35cd1..b78d4117 100644 --- a/pure/src/widget/text.rs +++ b/pure/src/widget/text.rs @@ -60,8 +60,7 @@ where } } -impl<'a, Message, Renderer> Into> - for &'static str +impl<'a, Message, Renderer> Into> for &'a str where Renderer: text::Renderer + 'static, { -- cgit From ef4c79ea23e86fec9a8ad0fb27463296c14400e5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 22 Mar 2022 23:40:08 +0700 Subject: Implement `pure` version of `Svg` widget --- pure/src/widget/svg.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 pure/src/widget/svg.rs (limited to 'pure/src/widget') diff --git a/pure/src/widget/svg.rs b/pure/src/widget/svg.rs new file mode 100644 index 00000000..2758c5b1 --- /dev/null +++ b/pure/src/widget/svg.rs @@ -0,0 +1,62 @@ +use crate::widget::{Tree, Widget}; +use crate::Element; + +use iced_native::layout::{self, Layout}; +use iced_native::renderer; +use iced_native::widget::svg; +use iced_native::{Length, Point, Rectangle}; + +pub use iced_native::svg::Handle; +pub use svg::Svg; + +impl Widget for Svg +where + Renderer: iced_native::svg::Renderer, +{ + fn width(&self) -> Length { + >::width(self) + } + + fn height(&self) -> Length { + >::height(self) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + >::layout( + self, renderer, limits, + ) + } + + fn draw( + &self, + _tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + >::draw( + self, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } +} + +impl<'a, Message, Renderer> Into> for Svg +where + Message: Clone + 'a, + Renderer: iced_native::svg::Renderer + 'a, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} -- cgit