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/application.rs | 1 + pure/src/flex.rs | 232 ++++++++++++++++++++++++++++++++++++++ pure/src/lib.rs | 146 ++++++++++++++++++++++++ pure/src/widget.rs | 73 ++++++++++++ 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 ++++++++++ 9 files changed, 1208 insertions(+) create mode 100644 pure/src/application.rs create mode 100644 pure/src/flex.rs create mode 100644 pure/src/lib.rs create mode 100644 pure/src/widget.rs 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') diff --git a/pure/src/application.rs b/pure/src/application.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pure/src/application.rs @@ -0,0 +1 @@ + diff --git a/pure/src/flex.rs b/pure/src/flex.rs new file mode 100644 index 00000000..8d473f08 --- /dev/null +++ b/pure/src/flex.rs @@ -0,0 +1,232 @@ +//! Distribute elements using a flex-based layout. +// This code is heavily inspired by the [`druid`] codebase. +// +// [`druid`]: https://github.com/xi-editor/druid +// +// Copyright 2018 The xi-editor Authors, Héctor Ramón +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::Element; + +use iced_native::layout::{Limits, Node}; +use iced_native::{Alignment, Padding, Point, Size}; + +/// The main axis of a flex layout. +#[derive(Debug)] +pub enum Axis { + /// The horizontal axis + Horizontal, + + /// The vertical axis + Vertical, +} + +impl Axis { + fn main(&self, size: Size) -> f32 { + match self { + Axis::Horizontal => size.width, + Axis::Vertical => size.height, + } + } + + fn cross(&self, size: Size) -> f32 { + match self { + Axis::Horizontal => size.height, + Axis::Vertical => size.width, + } + } + + fn pack(&self, main: f32, cross: f32) -> (f32, f32) { + match self { + Axis::Horizontal => (main, cross), + Axis::Vertical => (cross, main), + } + } +} + +/// Computes the flex layout with the given axis and limits, applying spacing, +/// padding and alignment to the items as needed. +/// +/// It returns a new layout [`Node`]. +pub fn resolve( + axis: Axis, + renderer: &Renderer, + limits: &Limits, + padding: Padding, + spacing: f32, + align_items: Alignment, + items: &[Element], +) -> Node +where + Renderer: iced_native::Renderer, +{ + let limits = limits.pad(padding); + let total_spacing = spacing * items.len().saturating_sub(1) as f32; + let max_cross = axis.cross(limits.max()); + + let mut fill_sum = 0; + let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill())); + let mut available = axis.main(limits.max()) - total_spacing; + + let mut nodes: Vec = Vec::with_capacity(items.len()); + nodes.resize(items.len(), Node::default()); + + if align_items == Alignment::Fill { + let mut fill_cross = axis.cross(limits.min()); + + items.iter().for_each(|child| { + let cross_fill_factor = match axis { + Axis::Horizontal => child.as_widget().height(), + Axis::Vertical => child.as_widget().width(), + } + .fill_factor(); + + if cross_fill_factor == 0 { + let (max_width, max_height) = axis.pack(available, max_cross); + + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = child.as_widget().layout(renderer, &child_limits); + let size = layout.size(); + + fill_cross = fill_cross.max(axis.cross(size)); + } + }); + + cross = fill_cross; + } + + for (i, child) in items.iter().enumerate() { + let fill_factor = match axis { + Axis::Horizontal => child.as_widget().width(), + Axis::Vertical => child.as_widget().height(), + } + .fill_factor(); + + if fill_factor == 0 { + let (min_width, min_height) = if align_items == Alignment::Fill { + axis.pack(0.0, cross) + } else { + axis.pack(0.0, 0.0) + }; + + let (max_width, max_height) = if align_items == Alignment::Fill { + axis.pack(available, cross) + } else { + axis.pack(available, max_cross) + }; + + let child_limits = Limits::new( + Size::new(min_width, min_height), + Size::new(max_width, max_height), + ); + + let layout = child.as_widget().layout(renderer, &child_limits); + let size = layout.size(); + + available -= axis.main(size); + + if align_items != Alignment::Fill { + cross = cross.max(axis.cross(size)); + } + + nodes[i] = layout; + } else { + fill_sum += fill_factor; + } + } + + let remaining = available.max(0.0); + + for (i, child) in items.iter().enumerate() { + let fill_factor = match axis { + Axis::Horizontal => child.as_widget().width(), + Axis::Vertical => child.as_widget().height(), + } + .fill_factor(); + + if fill_factor != 0 { + let max_main = remaining * fill_factor as f32 / fill_sum as f32; + let min_main = if max_main.is_infinite() { + 0.0 + } else { + max_main + }; + + let (min_width, min_height) = if align_items == Alignment::Fill { + axis.pack(min_main, cross) + } else { + axis.pack(min_main, axis.cross(limits.min())) + }; + + let (max_width, max_height) = if align_items == Alignment::Fill { + axis.pack(max_main, cross) + } else { + axis.pack(max_main, max_cross) + }; + + let child_limits = Limits::new( + Size::new(min_width, min_height), + Size::new(max_width, max_height), + ); + + let layout = child.as_widget().layout(renderer, &child_limits); + + if align_items != Alignment::Fill { + cross = cross.max(axis.cross(layout.size())); + } + + nodes[i] = layout; + } + } + + let pad = axis.pack(padding.left as f32, padding.top as f32); + let mut main = pad.0; + + for (i, node) in nodes.iter_mut().enumerate() { + if i > 0 { + main += spacing; + } + + let (x, y) = axis.pack(main, pad.1); + + node.move_to(Point::new(x, y)); + + match axis { + Axis::Horizontal => { + node.align( + Alignment::Start, + align_items, + Size::new(0.0, cross), + ); + } + Axis::Vertical => { + node.align( + align_items, + Alignment::Start, + Size::new(cross, 0.0), + ); + } + } + + let size = node.size(); + + main += axis.main(size); + } + + let (width, height) = axis.pack(main - pad.0, cross); + let size = limits.resolve(Size::new(width, height)); + + Node::with_children(size.pad(padding), nodes) +} diff --git a/pure/src/lib.rs b/pure/src/lib.rs new file mode 100644 index 00000000..4381bfc8 --- /dev/null +++ b/pure/src/lib.rs @@ -0,0 +1,146 @@ +pub mod widget; + +pub(crate) mod flex; + +pub use 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}; + +pub struct Pure<'a, Message, Renderer> { + state: &'a mut State, +} + +impl<'a, Message, Renderer> Pure<'a, Message, Renderer> +where + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + pub fn new( + state: &'a mut State, + content: impl Into>, + ) -> Self { + let _ = state.diff(content.into()); + + Self { state } + } +} + +pub struct State { + state_tree: widget::Tree, + last_element: Element, +} + +impl State +where + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + pub fn new() -> Self { + let last_element = Element::new(widget::Column::new()); + + Self { + state_tree: widget::Tree::new(&last_element), + last_element, + } + } + + fn diff(&mut self, new_element: Element) { + self.state_tree.diff(&self.last_element, &new_element); + + self.last_element = new_element; + } +} + +impl<'a, Message, Renderer> iced_native::Widget + for Pure<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn width(&self) -> Length { + self.state.last_element.as_widget().width() + } + + fn height(&self) -> Length { + self.state.last_element.as_widget().height() + } + + fn hash_layout(&self, state: &mut Hasher) { + self.state.last_element.as_widget().hash_layout(state) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.state.last_element.as_widget().layout(renderer, limits) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.state.last_element.as_widget_mut().on_event( + &mut self.state.state_tree, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.state.last_element.as_widget().draw( + &self.state.state_tree, + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.state.last_element.as_widget().mouse_interaction( + &self.state.state_tree, + layout, + cursor_position, + viewport, + renderer, + ) + } +} + +impl<'a, Message, Renderer> Into> + for Pure<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn into(self) -> iced_native::Element<'a, Message, Renderer> { + iced_native::Element::new(self) + } +} diff --git a/pure/src/widget.rs b/pure/src/widget.rs new file mode 100644 index 00000000..bf63b999 --- /dev/null +++ b/pure/src/widget.rs @@ -0,0 +1,73 @@ +mod button; +mod column; +mod element; +mod text; +mod tree; + +pub use button::Button; +pub use column::Column; +pub use element::Element; +pub use text::Text; +pub use tree::Tree; + +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 trait Widget { + fn tag(&self) -> any::TypeId; + + fn state(&self) -> Box; + + fn children(&self) -> &[Element]; + + fn width(&self) -> Length; + + fn height(&self) -> Length; + + fn hash_layout(&self, state: &mut Hasher); + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node; + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ); + + fn mouse_interaction( + &self, + _state: &Tree, + _layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + mouse::Interaction::Idle + } + + 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 { + event::Status::Ignored + } +} 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.rs | 17 +++++++++++++++++ pure/src/widget/text.rs | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index bf63b999..7215e99e 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -71,3 +71,20 @@ pub trait Widget { event::Status::Ignored } } + +pub fn column() -> Column { + Column::new() +} + +pub fn button( + content: impl Into>, +) -> Button { + Button::new(content) +} + +pub fn text(text: impl ToString) -> Text +where + Renderer: iced_native::text::Renderer, +{ + Text::new(text) +} 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/application.rs | 1 - pure/src/lib.rs | 54 +++++++++++++++++++++------------------------- pure/src/widget.rs | 14 ++++++------ 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 +++++++++++++++++++++-------------------- 8 files changed, 97 insertions(+), 93 deletions(-) delete mode 100644 pure/src/application.rs (limited to 'pure/src') diff --git a/pure/src/application.rs b/pure/src/application.rs deleted file mode 100644 index 8b137891..00000000 --- a/pure/src/application.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pure/src/lib.rs b/pure/src/lib.rs index 4381bfc8..a179a84b 100644 --- a/pure/src/lib.rs +++ b/pure/src/lib.rs @@ -11,7 +11,8 @@ use iced_native::renderer; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; pub struct Pure<'a, Message, Renderer> { - state: &'a mut State, + state: &'a mut State, + element: Element<'a, Message, Renderer>, } impl<'a, Message, Renderer> Pure<'a, Message, Renderer> @@ -20,38 +21,32 @@ where Renderer: iced_native::Renderer + 'static, { pub fn new( - state: &'a mut State, - content: impl Into>, + state: &'a mut State, + content: impl Into>, ) -> Self { - let _ = state.diff(content.into()); + let element = content.into(); + let _ = state.diff(&element); - Self { state } + Self { state, element } } } -pub struct State { - state_tree: widget::Tree, - last_element: Element, +pub struct State { + state_tree: widget::Tree, } -impl State -where - Message: 'static, - Renderer: iced_native::Renderer + 'static, -{ +impl State { pub fn new() -> Self { - let last_element = Element::new(widget::Column::new()); - Self { - state_tree: widget::Tree::new(&last_element), - last_element, + state_tree: widget::Tree::empty(), } } - fn diff(&mut self, new_element: Element) { - self.state_tree.diff(&self.last_element, &new_element); - - self.last_element = new_element; + fn diff( + &mut self, + new_element: &Element, + ) { + self.state_tree.diff(new_element); } } @@ -61,15 +56,15 @@ where Renderer: iced_native::Renderer, { fn width(&self) -> Length { - self.state.last_element.as_widget().width() + self.element.as_widget().width() } fn height(&self) -> Length { - self.state.last_element.as_widget().height() + self.element.as_widget().height() } fn hash_layout(&self, state: &mut Hasher) { - self.state.last_element.as_widget().hash_layout(state) + self.element.as_widget().hash_layout(state) } fn layout( @@ -77,7 +72,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - self.state.last_element.as_widget().layout(renderer, limits) + self.element.as_widget().layout(renderer, limits) } fn on_event( @@ -89,7 +84,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - self.state.last_element.as_widget_mut().on_event( + self.element.as_widget_mut().on_event( &mut self.state.state_tree, event, layout, @@ -108,7 +103,7 @@ where cursor_position: Point, viewport: &Rectangle, ) { - self.state.last_element.as_widget().draw( + self.element.as_widget().draw( &self.state.state_tree, renderer, style, @@ -125,7 +120,7 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - self.state.last_element.as_widget().mouse_interaction( + self.element.as_widget().mouse_interaction( &self.state.state_tree, layout, cursor_position, @@ -138,7 +133,8 @@ where impl<'a, Message, Renderer> Into> for Pure<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Message: 'a, + Renderer: iced_native::Renderer + 'a, { fn into(self) -> iced_native::Element<'a, Message, Renderer> { iced_native::Element::new(self) diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 7215e99e..9a4dffe3 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -39,7 +39,7 @@ pub trait Widget { fn draw( &self, - state: &Tree, + state: &Tree, renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, @@ -49,7 +49,7 @@ pub trait Widget { fn mouse_interaction( &self, - _state: &Tree, + _state: &Tree, _layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, @@ -60,7 +60,7 @@ pub trait Widget { fn on_event( &mut self, - _state: &mut Tree, + _state: &mut Tree, _event: Event, _layout: Layout<'_>, _cursor_position: Point, @@ -72,13 +72,13 @@ pub trait Widget { } } -pub fn column() -> Column { +pub fn column<'a, Message, Renderer>() -> Column<'a, Message, Renderer> { Column::new() } -pub fn button( - content: impl Into>, -) -> Button { +pub fn button<'a, Message, Renderer>( + content: impl Into>, +) -> Button<'a, Message, Renderer> { Button::new(content) } 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') 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') 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.rs | 6 ++ pure/src/widget/row.rs | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 pure/src/widget/row.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 9a4dffe3..1cbd3b78 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,12 +1,14 @@ mod button; mod column; mod element; +mod row; mod text; mod tree; pub use button::Button; pub use column::Column; pub use element::Element; +pub use row::Row; pub use text::Text; pub use tree::Tree; @@ -76,6 +78,10 @@ pub fn column<'a, Message, Renderer>() -> Column<'a, Message, Renderer> { Column::new() } +pub fn row<'a, Message, Renderer>() -> Row<'a, Message, Renderer> { + Row::new() +} + pub fn button<'a, Message, Renderer>( content: impl Into>, ) -> Button<'a, Message, Renderer> { 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') 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.rs | 11 ++ pure/src/widget/container.rs | 258 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 pure/src/widget/container.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 1cbd3b78..3bf6a5aa 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,5 +1,6 @@ mod button; mod column; +mod container; mod element; mod row; mod text; @@ -7,6 +8,7 @@ mod tree; pub use button::Button; pub use column::Column; +pub use container::Container; pub use element::Element; pub use row::Row; pub use text::Text; @@ -74,6 +76,15 @@ pub trait Widget { } } +pub fn container<'a, Message, Renderer>( + content: impl Into>, +) -> Container<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + Container::new(content) +} + pub fn column<'a, Message, Renderer>() -> Column<'a, Message, Renderer> { Column::new() } 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.rs | 2 +- pure/src/widget/text.rs | 131 +++++++----------------------------------------- 2 files changed, 18 insertions(+), 115 deletions(-) (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 3bf6a5aa..9ab65614 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -99,7 +99,7 @@ pub fn button<'a, Message, Renderer>( Button::new(content) } -pub fn text(text: impl ToString) -> Text +pub fn text(text: impl Into) -> Text where Renderer: iced_native::text::Renderer, { 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.rs | 13 +++++++ pure/src/widget/checkbox.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 pure/src/widget/checkbox.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 9ab65614..3488f99d 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,4 +1,5 @@ mod button; +mod checkbox; mod column; mod container; mod element; @@ -7,6 +8,7 @@ mod text; mod tree; pub use button::Button; +pub use checkbox::Checkbox; pub use column::Column; pub use container::Container; pub use element::Element; @@ -105,3 +107,14 @@ where { Text::new(text) } + +pub fn checkbox<'a, Message, Renderer>( + label: impl Into, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> Checkbox<'a, Message, Renderer> +where + Renderer: iced_native::text::Renderer, +{ + Checkbox::new(is_checked, label, f) +} 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.rs | 9 ++ pure/src/widget/button.rs | 4 +- pure/src/widget/scrollable.rs | 268 ++++++++++++++++++++++++++++++++++++++++++ pure/src/widget/tree.rs | 18 +-- 4 files changed, 290 insertions(+), 9 deletions(-) create mode 100644 pure/src/widget/scrollable.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 3488f99d..02bf3a85 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -4,6 +4,7 @@ mod column; mod container; mod element; mod row; +mod scrollable; mod text; mod tree; @@ -13,6 +14,7 @@ pub use column::Column; pub use container::Container; pub use element::Element; pub use row::Row; +pub use scrollable::Scrollable; pub use text::Text; pub use tree::Tree; @@ -95,6 +97,13 @@ pub fn row<'a, Message, Renderer>() -> Row<'a, Message, Renderer> { Row::new() } +pub fn scrollable<'a, Message, Renderer>() -> Scrollable<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + Scrollable::new() +} + pub fn button<'a, Message, Renderer>( content: impl Into>, ) -> Button<'a, Message, Renderer> { 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.rs | 14 +++ pure/src/widget/text_input.rs | 239 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 pure/src/widget/text_input.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 02bf3a85..8df64426 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -6,6 +6,7 @@ mod element; mod row; mod scrollable; mod text; +mod text_input; mod tree; pub use button::Button; @@ -16,6 +17,7 @@ pub use element::Element; pub use row::Row; pub use scrollable::Scrollable; pub use text::Text; +pub use text_input::TextInput; pub use tree::Tree; use iced_native::event::{self, Event}; @@ -127,3 +129,15 @@ where { Checkbox::new(is_checked, label, f) } + +pub fn text_input<'a, Message, Renderer>( + placeholder: &str, + value: &str, + on_change: impl Fn(String) -> Message + 'a, +) -> TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: iced_native::text::Renderer, +{ + TextInput::new(placeholder, value, on_change) +} 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/lib.rs | 3 +- pure/src/widget.rs | 10 ++- 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 ++++++------- 12 files changed, 261 insertions(+), 94 deletions(-) (limited to 'pure/src') diff --git a/pure/src/lib.rs b/pure/src/lib.rs index a179a84b..07f068cc 100644 --- a/pure/src/lib.rs +++ b/pure/src/lib.rs @@ -53,7 +53,8 @@ impl State { impl<'a, Message, Renderer> iced_native::Widget for Pure<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Message: 'a, + Renderer: iced_native::Renderer + 'a, { fn width(&self) -> Length { self.element.as_widget().width() diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 8df64426..302a057a 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -33,7 +33,9 @@ pub trait Widget { fn state(&self) -> Box; - fn children(&self) -> &[Element]; + fn diff(&self, tree: &mut Tree); + + fn children_state(&self) -> Vec; fn width(&self) -> Length; @@ -99,11 +101,13 @@ pub fn row<'a, Message, Renderer>() -> Row<'a, Message, Renderer> { Row::new() } -pub fn scrollable<'a, Message, Renderer>() -> Scrollable<'a, Message, Renderer> +pub fn scrollable<'a, Message, Renderer>( + content: impl Into>, +) -> Scrollable<'a, Message, Renderer> where Renderer: iced_native::Renderer, { - Scrollable::new() + Scrollable::new(content) } pub fn button<'a, Message, Renderer>( 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') 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') 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.rs | 7 +++++ pure/src/widget/image.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 pure/src/widget/image.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 302a057a..93298c61 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,3 +1,5 @@ +pub mod image; + mod button; mod checkbox; mod column; @@ -14,6 +16,7 @@ pub use checkbox::Checkbox; pub use column::Column; pub use container::Container; pub use element::Element; +pub use image::Image; pub use row::Row; pub use scrollable::Scrollable; pub use text::Text; @@ -145,3 +148,7 @@ where { TextInput::new(placeholder, value, on_change) } + +pub fn image(handle: Handle) -> Image { + Image::new(handle) +} 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.rs | 19 +++- pure/src/widget/slider.rs | 253 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 pure/src/widget/slider.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 93298c61..009741a8 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -7,6 +7,7 @@ mod container; mod element; mod row; mod scrollable; +mod slider; mod text; mod text_input; mod tree; @@ -19,6 +20,7 @@ pub use element::Element; pub use image::Image; pub use row::Row; pub use scrollable::Scrollable; +pub use slider::Slider; pub use text::Text; pub use text_input::TextInput; pub use tree::Tree; @@ -36,8 +38,6 @@ pub trait Widget { fn state(&self) -> Box; - fn diff(&self, tree: &mut Tree); - fn children_state(&self) -> Vec; fn width(&self) -> Length; @@ -62,6 +62,8 @@ pub trait Widget { viewport: &Rectangle, ); + fn diff(&self, _tree: &mut Tree) {} + fn mouse_interaction( &self, _state: &Tree, @@ -149,6 +151,19 @@ where TextInput::new(placeholder, value, on_change) } +pub fn slider<'a, Message, Renderer, T>( + range: std::ops::RangeInclusive, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> Slider<'a, T, Message> +where + Message: Clone, + Renderer: iced_native::Renderer, + T: Copy + From + std::cmp::PartialOrd, +{ + Slider::new(range, value, on_change) +} + pub fn image(handle: Handle) -> Image { Image::new(handle) } 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.rs | 2 + pure/src/widget/image.rs | 13 +++++ pure/src/widget/toggler.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 pure/src/widget/toggler.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 009741a8..1c0633a6 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -10,6 +10,7 @@ mod scrollable; mod slider; mod text; mod text_input; +mod toggler; mod tree; pub use button::Button; @@ -23,6 +24,7 @@ pub use scrollable::Scrollable; pub use slider::Slider; pub use text::Text; pub use text_input::TextInput; +pub use toggler::Toggler; pub use tree::Tree; use iced_native::event::{self, Event}; 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 e50e639b0edc6eee41754e6faee45936fedeebd8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 13 Feb 2022 19:23:51 +0700 Subject: Expose additional helpers in `iced::pure` --- pure/src/widget.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 1c0633a6..fc86e1c2 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -141,6 +141,17 @@ where Checkbox::new(is_checked, label, f) } +pub fn toggler<'a, Message, Renderer>( + label: impl Into>, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> Toggler<'a, Message, Renderer> +where + Renderer: iced_native::text::Renderer, +{ + Toggler::new(is_checked, label, f) +} + pub fn text_input<'a, Message, Renderer>( placeholder: &str, value: &str, @@ -153,19 +164,18 @@ where TextInput::new(placeholder, value, on_change) } -pub fn slider<'a, Message, Renderer, T>( +pub fn slider<'a, Message, T>( range: std::ops::RangeInclusive, value: T, on_change: impl Fn(T) -> Message + 'a, ) -> Slider<'a, T, Message> where Message: Clone, - Renderer: iced_native::Renderer, T: Copy + From + std::cmp::PartialOrd, { Slider::new(range, value, on_change) } -pub fn image(handle: Handle) -> Image { - Image::new(handle) +pub fn image(handle: impl Into) -> Image { + Image::new(handle.into()) } -- 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.rs | 16 ++++++ pure/src/widget/radio.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 pure/src/widget/radio.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index fc86e1c2..7b5fc0bc 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -5,6 +5,7 @@ mod checkbox; mod column; mod container; mod element; +mod radio; mod row; mod scrollable; mod slider; @@ -19,6 +20,7 @@ pub use column::Column; pub use container::Container; pub use element::Element; pub use image::Image; +pub use radio::Radio; pub use row::Row; pub use scrollable::Scrollable; pub use slider::Slider; @@ -141,6 +143,20 @@ where Checkbox::new(is_checked, label, f) } +pub fn radio<'a, Message, Renderer, V>( + label: impl Into, + value: V, + selected: Option, + on_click: impl FnOnce(V) -> Message, +) -> Radio<'a, Message, Renderer> +where + Message: Clone, + Renderer: iced_native::text::Renderer, + V: Copy + Eq, +{ + Radio::new(value, label, selected, on_click) +} + pub fn toggler<'a, Message, Renderer>( label: impl Into>, is_checked: bool, 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.rs | 10 ++++ pure/src/widget/space.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 pure/src/widget/space.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 7b5fc0bc..9112dd9a 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -9,6 +9,7 @@ mod radio; mod row; mod scrollable; mod slider; +mod space; mod text; mod text_input; mod toggler; @@ -24,6 +25,7 @@ pub use radio::Radio; pub use row::Row; pub use scrollable::Scrollable; pub use slider::Slider; +pub use space::Space; pub use text::Text; pub use text_input::TextInput; pub use toggler::Toggler; @@ -195,3 +197,11 @@ where pub fn image(handle: impl Into) -> Image { Image::new(handle.into()) } + +pub fn horizontal_space(width: Length) -> Space { + Space::with_width(width) +} + +pub fn vertical_space(height: Length) -> Space { + Space::with_height(height) +} 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.rs | 20 ++++++++++------- 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 ++++++++++++++++++++++++++++++++++++------- 16 files changed, 101 insertions(+), 197 deletions(-) (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 9112dd9a..03b668d3 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -37,15 +37,7 @@ use iced_native::mouse; use iced_native::renderer; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; -use std::any::{self, Any}; - pub trait Widget { - fn tag(&self) -> any::TypeId; - - fn state(&self) -> Box; - - fn children_state(&self) -> Vec; - fn width(&self) -> Length; fn height(&self) -> Length; @@ -68,6 +60,18 @@ pub trait Widget { viewport: &Rectangle, ); + fn tag(&self) -> tree::Tag { + tree::Tag::stateless() + } + + fn state(&self) -> tree::State { + tree::State::None + } + + fn children(&self) -> Vec { + Vec::new() + } + fn diff(&self, _tree: &mut Tree) {} fn mouse_interaction( 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/lib.rs | 13 +++ pure/src/overlay.rs | 21 ++++ pure/src/widget.rs | 12 +++ 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 +++ 9 files changed, 353 insertions(+) create mode 100644 pure/src/overlay.rs create mode 100644 pure/src/widget/pick_list.rs (limited to 'pure/src') diff --git a/pure/src/lib.rs b/pure/src/lib.rs index 07f068cc..bab3bbc7 100644 --- a/pure/src/lib.rs +++ b/pure/src/lib.rs @@ -1,3 +1,4 @@ +pub mod overlay; pub mod widget; pub(crate) mod flex; @@ -129,6 +130,18 @@ where renderer, ) } + + fn overlay( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.element.as_widget_mut().overlay( + &mut self.state.state_tree, + layout, + renderer, + ) + } } impl<'a, Message, Renderer> Into> diff --git a/pure/src/overlay.rs b/pure/src/overlay.rs new file mode 100644 index 00000000..b009fde8 --- /dev/null +++ b/pure/src/overlay.rs @@ -0,0 +1,21 @@ +use crate::Tree; + +use iced_native::Layout; + +pub use iced_native::overlay::*; + +pub fn from_children<'a, Message, Renderer>( + children: &'a mut [crate::Element<'_, Message, Renderer>], + tree: &'a mut Tree, + layout: Layout<'_>, + renderer: &Renderer, +) -> Option> { + children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .filter_map(|((child, state), layout)| { + child.as_widget_mut().overlay(state, layout, renderer) + }) + .next() +} diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 03b668d3..6dda653d 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -5,6 +5,7 @@ mod checkbox; mod column; mod container; mod element; +mod pick_list; mod radio; mod row; mod scrollable; @@ -21,6 +22,7 @@ pub use column::Column; pub use container::Container; pub use element::Element; pub use image::Image; +pub use pick_list::PickList; pub use radio::Radio; pub use row::Row; pub use scrollable::Scrollable; @@ -34,6 +36,7 @@ pub use tree::Tree; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; use iced_native::mouse; +use iced_native::overlay; use iced_native::renderer; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; @@ -97,6 +100,15 @@ pub trait Widget { ) -> event::Status { event::Status::Ignored } + + fn overlay<'a>( + &'a mut self, + _state: &'a mut Tree, + _layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + None + } } pub fn container<'a, Message, Renderer>( 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 6e242fe0e506f8086371a24f03e7fbe3a10ca2ae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 16 Feb 2022 17:15:43 +0700 Subject: Add `pick_list` function helper in `iced_pure::widget` --- pure/src/widget.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 6dda653d..62f9d95b 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -40,6 +40,8 @@ use iced_native::overlay; use iced_native::renderer; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell}; +use std::borrow::Cow; + pub trait Widget { fn width(&self) -> Length; @@ -210,6 +212,19 @@ where Slider::new(range, value, on_change) } +pub fn pick_list<'a, Message, Renderer, T>( + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'a, +) -> PickList<'a, T, Message, Renderer> +where + T: ToString + Eq + 'static, + [T]: ToOwned>, + Renderer: iced_native::text::Renderer, +{ + PickList::new(options, selected, on_selected) +} + pub fn image(handle: impl Into) -> Image { Image::new(handle.into()) } -- 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') 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/overlay.rs | 6 +++--- pure/src/widget.rs | 4 ++-- 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 ++----- 9 files changed, 18 insertions(+), 21 deletions(-) (limited to 'pure/src') diff --git a/pure/src/overlay.rs b/pure/src/overlay.rs index b009fde8..72415634 100644 --- a/pure/src/overlay.rs +++ b/pure/src/overlay.rs @@ -5,17 +5,17 @@ use iced_native::Layout; pub use iced_native::overlay::*; pub fn from_children<'a, Message, Renderer>( - children: &'a mut [crate::Element<'_, Message, Renderer>], + children: &'a [crate::Element<'_, Message, Renderer>], tree: &'a mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { children - .iter_mut() + .iter() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { - child.as_widget_mut().overlay(state, layout, renderer) + child.as_widget().overlay(state, layout, renderer) }) .next() } diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 62f9d95b..a12d6fad 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,4 +1,5 @@ pub mod image; +pub mod tree; mod button; mod checkbox; @@ -14,7 +15,6 @@ mod space; mod text; mod text_input; mod toggler; -mod tree; pub use button::Button; pub use checkbox::Checkbox; @@ -104,7 +104,7 @@ pub trait Widget { } fn overlay<'a>( - &'a mut self, + &'a self, _state: &'a mut Tree, _layout: Layout<'_>, _renderer: &Renderer, 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.rs | 12 ++++++ pure/src/widget/rule.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 pure/src/widget/rule.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index c516c1f2..bee21633 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,4 +1,5 @@ pub mod image; +pub mod rule; pub mod tree; mod button; @@ -25,6 +26,7 @@ pub use image::Image; pub use pick_list::PickList; pub use radio::Radio; pub use row::Row; +pub use rule::Rule; pub use scrollable::Scrollable; pub use slider::Slider; pub use space::Space; @@ -234,3 +236,13 @@ pub fn horizontal_space(width: Length) -> Space { pub fn vertical_space(height: Length) -> Space { Space::with_height(height) } + +/// Creates a horizontal [`Rule`] with the given height. +pub fn horizontal_rule<'a>(height: u16) -> Rule<'a> { + Rule::horizontal(height) +} + +/// Creates a vertical [`Rule`] with the given width. +pub fn vertical_rule<'a>(width: u16) -> Rule<'a> { + Rule::horizontal(width) +} 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.rs | 15 +++++++ pure/src/widget/progress_bar.rs | 99 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 pure/src/widget/progress_bar.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index bee21633..8f2cf920 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,4 +1,5 @@ pub mod image; +pub mod progress_bar; pub mod rule; pub mod tree; @@ -24,6 +25,7 @@ pub use container::Container; pub use element::Element; pub use image::Image; pub use pick_list::PickList; +pub use progress_bar::ProgressBar; pub use radio::Radio; pub use row::Row; pub use rule::Rule; @@ -43,6 +45,7 @@ use iced_native::renderer; use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; use std::borrow::Cow; +use std::ops::RangeInclusive; pub trait Widget { fn width(&self) -> Length; @@ -246,3 +249,15 @@ pub fn horizontal_rule<'a>(height: u16) -> Rule<'a> { pub fn vertical_rule<'a>(width: u16) -> Rule<'a> { Rule::horizontal(width) } + +/// Creates a new [`ProgressBar`]. +/// +/// It expects: +/// * an inclusive range of possible values +/// * the current value of the [`ProgressBar`] +pub fn progress_bar<'a>( + range: RangeInclusive, + value: f32, +) -> ProgressBar<'a> { + ProgressBar::new(range, value) +} 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.rs | 2 + 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 +- 5 files changed, 1102 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') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 8f2cf920..564f0583 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,4 +1,5 @@ pub mod image; +pub mod pane_grid; pub mod progress_bar; pub mod rule; pub mod tree; @@ -24,6 +25,7 @@ pub use column::Column; pub use container::Container; pub use element::Element; pub use image::Image; +pub use pane_grid::PaneGrid; pub use pick_list::PickList; pub use progress_bar::ProgressBar; pub use radio::Radio; 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/element.rs | 163 ++++++++++++++++++++++++++++++++++++++ pure/src/helpers.rs | 153 ++++++++++++++++++++++++++++++++++++ pure/src/lib.rs | 7 +- pure/src/overlay.rs | 2 +- pure/src/widget.rs | 169 +++------------------------------------- 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 +- 20 files changed, 362 insertions(+), 344 deletions(-) create mode 100644 pure/src/element.rs create mode 100644 pure/src/helpers.rs delete mode 100644 pure/src/widget/element.rs (limited to 'pure/src') diff --git a/pure/src/element.rs b/pure/src/element.rs new file mode 100644 index 00000000..3d5697fe --- /dev/null +++ b/pure/src/element.rs @@ -0,0 +1,163 @@ +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/helpers.rs b/pure/src/helpers.rs new file mode 100644 index 00000000..24f6dbaa --- /dev/null +++ b/pure/src/helpers.rs @@ -0,0 +1,153 @@ +use crate::widget; +use crate::Element; + +use iced_native::Length; +use std::borrow::Cow; +use std::ops::RangeInclusive; + +pub fn container<'a, Message, Renderer>( + content: impl Into>, +) -> widget::Container<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + widget::Container::new(content) +} + +pub fn column<'a, Message, Renderer>() -> widget::Column<'a, Message, Renderer> +{ + widget::Column::new() +} + +pub fn row<'a, Message, Renderer>() -> widget::Row<'a, Message, Renderer> { + widget::Row::new() +} + +pub fn scrollable<'a, Message, Renderer>( + content: impl Into>, +) -> widget::Scrollable<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + widget::Scrollable::new(content) +} + +pub fn button<'a, Message, Renderer>( + content: impl Into>, +) -> widget::Button<'a, Message, Renderer> { + widget::Button::new(content) +} + +pub fn text(text: impl Into) -> widget::Text +where + Renderer: iced_native::text::Renderer, +{ + widget::Text::new(text) +} + +pub fn checkbox<'a, Message, Renderer>( + label: impl Into, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> widget::Checkbox<'a, Message, Renderer> +where + Renderer: iced_native::text::Renderer, +{ + widget::Checkbox::new(is_checked, label, f) +} + +pub fn radio<'a, Message, Renderer, V>( + label: impl Into, + value: V, + selected: Option, + on_click: impl FnOnce(V) -> Message, +) -> widget::Radio<'a, Message, Renderer> +where + Message: Clone, + Renderer: iced_native::text::Renderer, + V: Copy + Eq, +{ + widget::Radio::new(value, label, selected, on_click) +} + +pub fn toggler<'a, Message, Renderer>( + label: impl Into>, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> widget::Toggler<'a, Message, Renderer> +where + Renderer: iced_native::text::Renderer, +{ + widget::Toggler::new(is_checked, label, f) +} + +pub fn text_input<'a, Message, Renderer>( + placeholder: &str, + value: &str, + on_change: impl Fn(String) -> Message + 'a, +) -> widget::TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: iced_native::text::Renderer, +{ + widget::TextInput::new(placeholder, value, on_change) +} + +pub fn slider<'a, Message, T>( + range: std::ops::RangeInclusive, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> widget::Slider<'a, T, Message> +where + Message: Clone, + T: Copy + From + std::cmp::PartialOrd, +{ + widget::Slider::new(range, value, on_change) +} + +pub fn pick_list<'a, Message, Renderer, T>( + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'a, +) -> widget::PickList<'a, T, Message, Renderer> +where + T: ToString + Eq + 'static, + [T]: ToOwned>, + Renderer: iced_native::text::Renderer, +{ + widget::PickList::new(options, selected, on_selected) +} + +pub fn image(handle: impl Into) -> widget::Image { + widget::Image::new(handle.into()) +} + +pub fn horizontal_space(width: Length) -> widget::Space { + widget::Space::with_width(width) +} + +pub fn vertical_space(height: Length) -> widget::Space { + widget::Space::with_height(height) +} + +/// Creates a horizontal [`Rule`] with the given height. +pub fn horizontal_rule<'a>(height: u16) -> widget::Rule<'a> { + widget::Rule::horizontal(height) +} + +/// Creates a vertical [`Rule`] with the given width. +pub fn vertical_rule<'a>(width: u16) -> widget::Rule<'a> { + widget::Rule::horizontal(width) +} + +/// Creates a new [`ProgressBar`]. +/// +/// It expects: +/// * an inclusive range of possible values +/// * the current value of the [`ProgressBar`] +pub fn progress_bar<'a>( + range: RangeInclusive, + value: f32, +) -> widget::ProgressBar<'a> { + widget::ProgressBar::new(range, value) +} diff --git a/pure/src/lib.rs b/pure/src/lib.rs index 1b51d55b..ec2f29f8 100644 --- a/pure/src/lib.rs +++ b/pure/src/lib.rs @@ -1,9 +1,14 @@ +pub mod helpers; pub mod overlay; pub mod widget; pub(crate) mod flex; -pub use widget::*; +mod element; + +pub use element::Element; +pub use helpers::*; +pub use widget::Widget; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; diff --git a/pure/src/overlay.rs b/pure/src/overlay.rs index 72415634..c87dfce8 100644 --- a/pure/src/overlay.rs +++ b/pure/src/overlay.rs @@ -1,4 +1,4 @@ -use crate::Tree; +use crate::widget::Tree; use iced_native::Layout; diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 564f0583..be73c5fa 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,29 +1,27 @@ +pub mod button; +pub mod checkbox; +pub mod container; pub mod image; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; +pub mod radio; pub mod rule; +pub mod scrollable; +pub mod slider; +pub mod text_input; +pub mod toggler; pub mod tree; -mod button; -mod checkbox; mod column; -mod container; -mod element; -mod pick_list; -mod radio; mod row; -mod scrollable; -mod slider; mod space; mod text; -mod text_input; -mod toggler; pub use button::Button; pub use checkbox::Checkbox; pub use column::Column; pub use container::Container; -pub use element::Element; pub use image::Image; pub use pane_grid::PaneGrid; pub use pick_list::PickList; @@ -46,9 +44,6 @@ use iced_native::overlay; use iced_native::renderer; use iced_native::{Clipboard, Length, Point, Rectangle, Shell}; -use std::borrow::Cow; -use std::ops::RangeInclusive; - pub trait Widget { fn width(&self) -> Length; @@ -117,149 +112,3 @@ pub trait Widget { None } } - -pub fn container<'a, Message, Renderer>( - content: impl Into>, -) -> Container<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - Container::new(content) -} - -pub fn column<'a, Message, Renderer>() -> Column<'a, Message, Renderer> { - Column::new() -} - -pub fn row<'a, Message, Renderer>() -> Row<'a, Message, Renderer> { - Row::new() -} - -pub fn scrollable<'a, Message, Renderer>( - content: impl Into>, -) -> Scrollable<'a, Message, Renderer> -where - Renderer: iced_native::Renderer, -{ - Scrollable::new(content) -} - -pub fn button<'a, Message, Renderer>( - content: impl Into>, -) -> Button<'a, Message, Renderer> { - Button::new(content) -} - -pub fn text(text: impl Into) -> Text -where - Renderer: iced_native::text::Renderer, -{ - Text::new(text) -} - -pub fn checkbox<'a, Message, Renderer>( - label: impl Into, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> Checkbox<'a, Message, Renderer> -where - Renderer: iced_native::text::Renderer, -{ - Checkbox::new(is_checked, label, f) -} - -pub fn radio<'a, Message, Renderer, V>( - label: impl Into, - value: V, - selected: Option, - on_click: impl FnOnce(V) -> Message, -) -> Radio<'a, Message, Renderer> -where - Message: Clone, - Renderer: iced_native::text::Renderer, - V: Copy + Eq, -{ - Radio::new(value, label, selected, on_click) -} - -pub fn toggler<'a, Message, Renderer>( - label: impl Into>, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> Toggler<'a, Message, Renderer> -where - Renderer: iced_native::text::Renderer, -{ - Toggler::new(is_checked, label, f) -} - -pub fn text_input<'a, Message, Renderer>( - placeholder: &str, - value: &str, - on_change: impl Fn(String) -> Message + 'a, -) -> TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: iced_native::text::Renderer, -{ - TextInput::new(placeholder, value, on_change) -} - -pub fn slider<'a, Message, T>( - range: std::ops::RangeInclusive, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> Slider<'a, T, Message> -where - Message: Clone, - T: Copy + From + std::cmp::PartialOrd, -{ - Slider::new(range, value, on_change) -} - -pub fn pick_list<'a, Message, Renderer, T>( - options: impl Into>, - selected: Option, - on_selected: impl Fn(T) -> Message + 'a, -) -> PickList<'a, T, Message, Renderer> -where - T: ToString + Eq + 'static, - [T]: ToOwned>, - Renderer: iced_native::text::Renderer, -{ - PickList::new(options, selected, on_selected) -} - -pub fn image(handle: impl Into) -> Image { - Image::new(handle.into()) -} - -pub fn horizontal_space(width: Length) -> Space { - Space::with_width(width) -} - -pub fn vertical_space(height: Length) -> Space { - Space::with_height(height) -} - -/// Creates a horizontal [`Rule`] with the given height. -pub fn horizontal_rule<'a>(height: u16) -> Rule<'a> { - Rule::horizontal(height) -} - -/// Creates a vertical [`Rule`] with the given width. -pub fn vertical_rule<'a>(width: u16) -> Rule<'a> { - Rule::horizontal(width) -} - -/// Creates a new [`ProgressBar`]. -/// -/// It expects: -/// * an inclusive range of possible values -/// * the current value of the [`ProgressBar`] -pub fn progress_bar<'a>( - range: RangeInclusive, - value: f32, -) -> ProgressBar<'a> { - ProgressBar::new(range, value) -} 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') 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.rs | 2 ++ pure/src/widget/svg.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 pure/src/widget/svg.rs (limited to 'pure/src') diff --git a/pure/src/widget.rs b/pure/src/widget.rs index be73c5fa..8200f9a7 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -9,6 +9,7 @@ pub mod radio; pub mod rule; pub mod scrollable; pub mod slider; +pub mod svg; pub mod text_input; pub mod toggler; pub mod tree; @@ -32,6 +33,7 @@ pub use rule::Rule; pub use scrollable::Scrollable; pub use slider::Slider; pub use space::Space; +pub use svg::Svg; pub use text::Text; pub use text_input::TextInput; pub use toggler::Toggler; 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