diff options
author | 2022-12-02 18:53:21 +0100 | |
---|---|---|
committer | 2022-12-02 18:53:21 +0100 | |
commit | 4029a1cdaaac1abbdcc141b20469a49670cd99b6 (patch) | |
tree | 71fa9d9c4aa1f02ce05771db43a4bb7bc6570e77 /native/src | |
parent | 676d8efe03ebdbeeb95aef96b8097395b788b1ab (diff) | |
parent | 8b55e9b9e6ba0b83038dd491dd34d95b4f9a381b (diff) | |
download | iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.gz iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.bz2 iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.zip |
Merge branch 'master' into non-uniform-border-radius-for-quads
Diffstat (limited to 'native/src')
38 files changed, 845 insertions, 266 deletions
diff --git a/native/src/element.rs b/native/src/element.rs index 955e1bee..2f1adeff 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -316,6 +316,22 @@ where ) { self.operation.focusable(state, id); } + + fn scrollable( + &mut self, + state: &mut dyn widget::operation::Scrollable, + id: Option<&widget::Id>, + ) { + self.operation.scrollable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn widget::operation::TextInput, + id: Option<&widget::Id>, + ) { + self.operation.text_input(state, id); + } } self.widget @@ -389,7 +405,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -544,7 +560,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, diff --git a/native/src/image.rs b/native/src/image.rs index 516eb2db..06fd7ae6 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -1,6 +1,7 @@ //! Load and draw raster graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size}; +use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -21,15 +22,19 @@ impl Handle { } /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec<u8>` of BGRA + /// function expects the input data to be provided as a `Vec<u8>` of RGBA /// pixels. /// /// This is useful if you have already decoded your image. - pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle { - Self::from_data(Data::Pixels { + pub fn from_pixels( + width: u32, + height: u32, + pixels: impl Into<Cow<'static, [u8]>>, + ) -> Handle { + Self::from_data(Data::Rgba { width, height, - pixels, + pixels: pixels.into(), }) } @@ -39,8 +44,8 @@ impl Handle { /// /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: Vec<u8>) -> Handle { - Self::from_data(Data::Bytes(bytes)) + pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle { + Self::from_data(Data::Bytes(bytes.into())) } fn from_data(data: Data) -> Handle { @@ -86,16 +91,16 @@ pub enum Data { Path(PathBuf), /// In-memory data - Bytes(Vec<u8>), + Bytes(Cow<'static, [u8]>), - /// Decoded image pixels in BGRA format. - Pixels { + /// Decoded image pixels in RGBA format. + Rgba { /// The width of the image. width: u32, /// The height of the image. height: u32, /// The pixels. - pixels: Vec<u8>, + pixels: Cow<'static, [u8]>, }, } @@ -104,7 +109,7 @@ impl std::fmt::Debug for Data { match self { Data::Path(path) => write!(f, "Path({:?})", path), Data::Bytes(_) => write!(f, "Bytes(...)"), - Data::Pixels { width, height, .. } => { + Data::Rgba { width, height, .. } => { write!(f, "Pixels({} * {})", width, height) } } @@ -121,7 +126,7 @@ pub trait Renderer: crate::Renderer { type Handle: Clone + Hash; /// Returns the dimensions of an image for the given [`Handle`]. - fn dimensions(&self, handle: &Self::Handle) -> (u32, u32); + fn dimensions(&self, handle: &Self::Handle) -> Size<u32>; /// Draws an image with the given [`Handle`] and inside the provided /// `bounds`. diff --git a/native/src/lib.rs b/native/src/lib.rs index 02269265..62b8f372 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -23,8 +23,8 @@ //! - Build a new renderer, see the [renderer] module. //! - Build a custom widget, start at the [`Widget`] trait. //! -//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.4/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.4/winit +//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.5/core +//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.5/winit //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [renderer]: crate::renderer diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 905d3389..0b05b058 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -11,7 +11,7 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; -use crate::widget::tree::{self, Tree}; +use crate::widget::Tree; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; /// An interactive component that can be displayed on top of other widgets. @@ -42,31 +42,9 @@ where cursor_position: Point, ); - /// Returns the [`Tag`] of the [`Widget`]. - /// - /// [`Tag`]: tree::Tag - fn tag(&self) -> tree::Tag { - tree::Tag::stateless() - } - - /// Returns the [`State`] of the [`Widget`]. - /// - /// [`State`]: tree::State - fn state(&self) -> tree::State { - tree::State::None - } - - /// Returns the state [`Tree`] of the children of the [`Widget`]. - fn children(&self) -> Vec<Tree> { - Vec::new() - } - - /// Reconciliates the [`Widget`] with the provided [`Tree`]. - fn diff(&self, _tree: &mut Tree) {} - - /// Applies an [`Operation`] to the [`Widget`]. + /// Applies a [`widget::Operation`] to the [`Overlay`]. fn operate( - &self, + &mut self, _layout: Layout<'_>, _operation: &mut dyn widget::Operation<Message>, ) { @@ -115,7 +93,7 @@ where /// This method will generally only be used by advanced users that are /// implementing the [`Widget`](crate::Widget) trait. pub fn from_children<'a, Message, Renderer>( - children: &'a [crate::Element<'_, Message, Renderer>], + children: &'a mut [crate::Element<'_, Message, Renderer>], tree: &'a mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -124,11 +102,11 @@ where Renderer: crate::Renderer, { children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { - child.as_widget().overlay(state, layout, renderer) + child.as_widget_mut().overlay(state, layout, renderer) }) .next() } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index b919c221..4f5ef32a 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -104,9 +104,9 @@ where .draw(renderer, theme, style, layout, cursor_position) } - /// Applies an [`Operation`] to the [`Element`]. + /// Applies a [`widget::Operation`] to the [`Element`]. pub fn operate( - &self, + &mut self, layout: Layout<'_>, operation: &mut dyn widget::Operation<Message>, ) { @@ -141,6 +141,57 @@ where self.content.layout(renderer, bounds, position) } + fn operate( + &mut self, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<B>, + ) { + struct MapOperation<'a, B> { + operation: &'a mut dyn widget::Operation<B>, + } + + impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> { + fn container( + &mut self, + id: Option<&widget::Id>, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation<T>, + ), + ) { + self.operation.container(id, &mut |operation| { + operate_on_children(&mut MapOperation { operation }); + }); + } + + fn focusable( + &mut self, + state: &mut dyn widget::operation::Focusable, + id: Option<&widget::Id>, + ) { + self.operation.focusable(state, id); + } + + fn scrollable( + &mut self, + state: &mut dyn widget::operation::Scrollable, + id: Option<&widget::Id>, + ) { + self.operation.scrollable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn widget::operation::TextInput, + id: Option<&widget::Id>, + ) { + self.operation.text_input(state, id) + } + } + + self.content + .operate(layout, &mut MapOperation { operation }); + } + fn on_event( &mut self, event: Event, diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index dbcf23d4..099b1a97 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -9,7 +9,7 @@ use crate::text::{self, Text}; use crate::touch; use crate::widget::container::{self, Container}; use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::tree::{self, Tree}; +use crate::widget::Tree; use crate::{ Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, Widget, @@ -178,7 +178,7 @@ where font, text_size, padding, - style, + style: style.clone(), })); state.tree.diff(&container as &dyn Widget<_, _>); @@ -199,18 +199,6 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { - fn tag(&self) -> tree::Tag { - self.container.tag() - } - - fn state(&self) -> tree::State { - self.container.state() - } - - fn children(&self) -> Vec<Tree> { - self.container.children() - } - fn layout( &self, renderer: &Renderer, @@ -288,7 +276,7 @@ where layout: Layout<'_>, cursor_position: Point, ) { - let appearance = theme.appearance(self.style); + let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); renderer.fill_quad( @@ -460,7 +448,7 @@ where _cursor_position: Point, viewport: &Rectangle, ) { - let appearance = theme.appearance(self.style); + let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); let text_size = diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 9775c84b..d24801d4 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -155,7 +155,7 @@ where /// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket /// connection open. /// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.4/examples/websocket +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.5/examples/websocket pub fn unfold<I, T, Fut, Message>( id: I, initial: T, diff --git a/native/src/svg.rs b/native/src/svg.rs index f86fec5b..a8e481d2 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,6 +1,7 @@ //! Load and draw vector graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size}; +use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -24,7 +25,7 @@ impl Handle { /// /// This is useful if you already have your SVG data in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle { + pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle { Self::from_data(Data::Bytes(bytes.into())) } @@ -64,7 +65,7 @@ pub enum Data { /// In-memory data /// /// Can contain an SVG string or a gzip compressed data. - Bytes(Vec<u8>), + Bytes(Cow<'static, [u8]>), } impl std::fmt::Debug for Data { @@ -81,7 +82,7 @@ impl std::fmt::Debug for Data { /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. - fn dimensions(&self, handle: &Handle) -> (u32, u32); + fn dimensions(&self, handle: &Handle) -> Size<u32>; /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`. fn draw(&mut self, handle: Handle, bounds: Rectangle); diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 344ba4d6..6b853da8 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -18,8 +18,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// The [`integration_opengl`] & [`integration_wgpu`] examples use a /// [`UserInterface`] to integrate Iced in an existing graphical application. /// -/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_wgpu +/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_opengl +/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_wgpu #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, @@ -190,7 +190,7 @@ where let mut state = State::Updated; let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget().overlay( + ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -226,7 +226,7 @@ where ); manual_overlay = - ManuallyDrop::new(self.root.as_widget().overlay( + ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -285,6 +285,10 @@ where &mut shell, ); + if matches!(event_status, event::Status::Captured) { + self.overlay = None; + } + shell.revalidate_layout(|| { self.base = renderer.layout( &self.root, @@ -391,11 +395,11 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = self.root.as_widget().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { + let base_cursor = if let Some(overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + { let overlay_layout = self .overlay .take() @@ -448,7 +452,7 @@ where overlay .as_ref() .and_then(|layout| { - root.as_widget() + root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( @@ -492,14 +496,19 @@ where operation, ); - if let Some(layout) = self.overlay.as_ref() { - if let Some(overlay) = self.root.as_widget().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { - overlay.operate(Layout::new(layout), operation); + if let Some(mut overlay) = self.root.as_widget_mut().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + if self.overlay.is_none() { + self.overlay = Some(overlay.layout(renderer, self.bounds)); } + + overlay.operate( + Layout::new(self.overlay.as_ref().unwrap()), + operation, + ); } } diff --git a/native/src/widget.rs b/native/src/widget.rs index 8890b8e7..36d679a4 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -107,12 +107,12 @@ use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; /// - [`geometry`], a custom widget showcasing how to draw geometry with the /// `Mesh2D` primitive in [`iced_wgpu`]. /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry +/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu pub trait Widget<Message, Renderer> where Renderer: crate::Renderer, @@ -208,7 +208,7 @@ where /// Returns the overlay of the [`Widget`], if there is any. fn overlay<'a>( - &'a self, + &'a mut self, _state: &'a mut Tree, _layout: Layout<'_>, _renderer: &Renderer, diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 766e902b..9aa79dec 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,8 +1,10 @@ -use crate::widget::operation::{self, Operation}; +use crate::widget::operation::{self, Focusable, Operation, Scrollable}; use crate::widget::Id; use iced_futures::MaybeSend; +use std::rc::Rc; + /// An operation to be performed on the widget tree. #[allow(missing_debug_implementations)] pub struct Action<T>(Box<dyn Operation<T>>); @@ -24,7 +26,7 @@ impl<T> Action<T> { { Action(Box::new(Map { operation: self.0, - f: Box::new(f), + f: Rc::new(f), })) } @@ -37,7 +39,7 @@ impl<T> Action<T> { #[allow(missing_debug_implementations)] struct Map<A, B> { operation: Box<dyn Operation<A>>, - f: Box<dyn Fn(A) -> B>, + f: Rc<dyn Fn(A) -> B>, } impl<A, B> Operation<B> for Map<A, B> @@ -50,30 +52,44 @@ where id: Option<&Id>, operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), ) { - struct MapRef<'a, A, B> { + struct MapRef<'a, A> { operation: &'a mut dyn Operation<A>, - f: &'a dyn Fn(A) -> B, } - impl<'a, A, B> Operation<B> for MapRef<'a, A, B> { + impl<'a, A, B> Operation<B> for MapRef<'a, A> { fn container( &mut self, id: Option<&Id>, operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), ) { - let Self { operation, f } = self; + let Self { operation, .. } = self; operation.container(id, &mut |operation| { - operate_on_children(&mut MapRef { operation, f }); + operate_on_children(&mut MapRef { operation }); }); } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + ) { + self.operation.scrollable(state, id); + } + + fn focusable( + &mut self, + state: &mut dyn Focusable, + id: Option<&Id>, + ) { + self.operation.focusable(state, id); + } } - let Self { operation, f } = self; + let Self { operation, .. } = self; MapRef { operation: operation.as_mut(), - f, } .container(id, operate_on_children); } @@ -85,4 +101,35 @@ where ) { self.operation.focusable(state, id); } + + fn scrollable( + &mut self, + state: &mut dyn operation::Scrollable, + id: Option<&Id>, + ) { + self.operation.scrollable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn operation::TextInput, + id: Option<&Id>, + ) { + self.operation.text_input(state, id); + } + + fn finish(&self) -> operation::Outcome<B> { + match self.operation.finish() { + operation::Outcome::None => operation::Outcome::None, + operation::Outcome::Some(output) => { + operation::Outcome::Some((self.f)(output)) + } + operation::Outcome::Chain(next) => { + operation::Outcome::Chain(Box::new(Map { + operation: next, + f: self.f.clone(), + })) + } + } + } } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 14759cfd..bbd9451c 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -231,7 +231,7 @@ where cursor_position, self.on_press.is_some(), theme, - self.style, + &self.style, || tree.state.downcast_ref::<State>(), ); @@ -260,12 +260,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -361,7 +361,7 @@ pub fn draw<'a, Renderer: crate::Renderer>( style_sheet: &dyn StyleSheet< Style = <Renderer::Theme as StyleSheet>::Style, >, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, state: impl FnOnce() -> &'a State, ) -> Appearance where @@ -426,12 +426,13 @@ pub fn layout<Renderer>( padding: Padding, layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.width(width).height(height).pad(padding); + let limits = limits.width(width).height(height); - let mut content = layout_content(renderer, &limits); - content.move_to(Point::new(padding.left.into(), padding.top.into())); + let mut content = layout_content(renderer, &limits.pad(padding)); + let padding = padding.fit(content.size(), limits.max()); + let size = limits.pad(padding).resolve(content.size()).pad(padding); - let size = limits.resolve(content.size()).pad(padding); + content.move_to(Point::new(padding.left.into(), padding.top.into())); layout::Node::with_children(size, vec![content]) } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 8f30037a..bec5c448 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -224,9 +224,9 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(self.style, self.is_checked) + theme.hovered(&self.style, self.is_checked) } else { - theme.active(self.style, self.is_checked) + theme.active(&self.style, self.is_checked) }; { diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index a8b0f183..8030778b 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -242,12 +242,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index b0c4938a..16d0cb61 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -228,7 +228,7 @@ where cursor_position: Point, viewport: &Rectangle, ) { - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); draw_background(renderer, &style, layout.bounds()); @@ -248,12 +248,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -293,11 +293,11 @@ pub fn layout<Renderer>( .max_width(max_width) .max_height(max_height) .width(width) - .height(height) - .pad(padding); + .height(height); - let mut content = layout_content(renderer, &limits.loose()); - let size = limits.resolve(content.size()); + let mut content = layout_content(renderer, &limits.pad(padding).loose()); + let padding = padding.fit(content.size(), limits.max()); + let size = limits.pad(padding).resolve(content.size()); content.move_to(Point::new(padding.left.into(), padding.top.into())); content.align( @@ -309,7 +309,7 @@ pub fn layout<Renderer>( layout::Node::with_children(size.pad(padding), vec![content]) } -/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`. +/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`. pub fn draw_background<Renderer>( renderer: &mut Renderer, appearance: &Appearance, diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 79751878..3bce9e60 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -1,4 +1,5 @@ //! Helper functions to create pure widgets. +use crate::overlay; use crate::widget; use crate::{Element, Length}; @@ -18,7 +19,7 @@ macro_rules! column { ); } -/// Creates a [Row`] with the given children. +/// Creates a [`Row`] with the given children. /// /// [`Row`]: widget::Row #[macro_export] @@ -84,6 +85,7 @@ pub fn button<'a, Message, Renderer>( where Renderer: crate::Renderer, Renderer::Theme: widget::button::StyleSheet, + <Renderer::Theme as widget::button::StyleSheet>::Style: Default, { widget::Button::new(content) } @@ -208,7 +210,12 @@ where T: ToString + Eq + 'static, [T]: ToOwned<Owned = Vec<T>>, Renderer: crate::text::Renderer, - Renderer::Theme: widget::pick_list::StyleSheet, + Renderer::Theme: widget::pick_list::StyleSheet + + widget::scrollable::StyleSheet + + overlay::menu::StyleSheet + + widget::container::StyleSheet, + <Renderer::Theme as overlay::menu::StyleSheet>::Style: + From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>, { widget::PickList::new(options, selected, on_selected) } diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 91d68e34..8bd8ca1e 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -85,7 +85,7 @@ where { // The raw w/h of the underlying image let image_size = { - let (width, height) = renderer.dimensions(handle); + let Size { width, height } = renderer.dimensions(handle); Size::new(width as f32, height as f32) }; @@ -149,7 +149,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index b1fe596c..9c83287e 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -108,7 +108,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let mut size = limits .width(self.width) @@ -409,7 +409,7 @@ pub fn image_size<Renderer>( where Renderer: image::Renderer, { - let (width, height) = renderer.dimensions(handle); + let Size { width, height } = renderer.dimensions(handle); let (width, height) = { let dimensions = (width as f32, height as f32); diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index ef636aa2..a0aa4117 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -1,9 +1,11 @@ //! Query or update internal widget state. pub mod focusable; pub mod scrollable; +pub mod text_input; pub use focusable::Focusable; pub use scrollable::Scrollable; +pub use text_input::TextInput; use crate::widget::Id; @@ -28,6 +30,9 @@ pub trait Operation<T> { /// Operates on a widget that can be scrolled. fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} + /// Operates on a widget that has text input. + fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} + /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome<T> { Outcome::None @@ -58,3 +63,46 @@ where } } } + +/// Produces an [`Operation`] that applies the given [`Operation`] to the +/// children of a container with the given [`Id`]. +pub fn scoped<T: 'static>( + target: Id, + operation: impl Operation<T> + 'static, +) -> impl Operation<T> { + struct ScopedOperation<Message> { + target: Id, + operation: Box<dyn Operation<Message>>, + } + + impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), + ) { + if id == Some(&self.target) { + operate_on_children(self.operation.as_mut()); + } else { + operate_on_children(self); + } + } + + fn finish(&self) -> Outcome<Message> { + match self.operation.finish() { + Outcome::Chain(next) => { + Outcome::Chain(Box::new(ScopedOperation { + target: self.target.clone(), + operation: next, + })) + } + outcome => outcome, + } + } + } + + ScopedOperation { + target, + operation: Box::new(operation), + } +} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index f17bf178..0067006b 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -167,3 +167,37 @@ pub fn focus_next<T>() -> impl Operation<T> { count(|count| FocusNext { count, current: 0 }) } + +/// Produces an [`Operation`] that searches for the current focused widget +/// and stores its ID. This ignores widgets that do not have an ID. +pub fn find_focused() -> impl Operation<Id> { + struct FindFocused { + focused: Option<Id>, + } + + impl Operation<Id> for FindFocused { + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + if state.is_focused() && id.is_some() { + self.focused = id.cloned(); + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome<Id> { + if let Some(id) = &self.focused { + Outcome::Some(id.clone()) + } else { + Outcome::None + } + } + } + + FindFocused { focused: None } +} diff --git a/native/src/widget/operation/text_input.rs b/native/src/widget/operation/text_input.rs new file mode 100644 index 00000000..4c773e99 --- /dev/null +++ b/native/src/widget/operation/text_input.rs @@ -0,0 +1,131 @@ +//! Operate on widgets that have text input. +use crate::widget::operation::Operation; +use crate::widget::Id; + +/// The internal state of a widget that has text input. +pub trait TextInput { + /// Moves the cursor of the text input to the front of the input text. + fn move_cursor_to_front(&mut self); + /// Moves the cursor of the text input to the end of the input text. + fn move_cursor_to_end(&mut self); + /// Moves the cursor of the text input to an arbitrary location. + fn move_cursor_to(&mut self, position: usize); + /// Selects all the content of the text input. + fn select_all(&mut self); +} + +/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the +/// front. +pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> { + struct MoveCursor { + target: Id, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.move_cursor_to_front(); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target } +} + +/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the +/// end. +pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> { + struct MoveCursor { + target: Id, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.move_cursor_to_end(); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target } +} + +/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the +/// provided position. +pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> { + struct MoveCursor { + target: Id, + position: usize, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.move_cursor_to(self.position); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target, position } +} + +/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`]. +pub fn select_all<T>(target: Id) -> impl Operation<T> { + struct MoveCursor { + target: Id, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.select_all(); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target } +} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 45090ecf..edad993e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -6,7 +6,7 @@ //! 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.4/examples/pane_grid +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid mod axis; mod configuration; mod content; @@ -38,6 +38,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::touch; +use crate::widget; use crate::widget::container; use crate::widget::tree::{self, Tree}; use crate::{ @@ -85,7 +86,7 @@ use crate::{ /// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); /// /// let pane_grid = -/// PaneGrid::new(&state, |pane, state| { +/// PaneGrid::new(&state, |pane, state, is_maximized| { /// pane_grid::Content::new(match state { /// PaneState::SomePane => text("This is some pane"), /// PaneState::AnotherKindOfPane => text("This is another kind of pane"), @@ -100,8 +101,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { - state: &'a state::Internal, - elements: Vec<(Pane, Content<'a, Message, Renderer>)>, + contents: Contents<'a, Content<'a, Message, Renderer>>, width: Length, height: Length, spacing: u16, @@ -119,22 +119,35 @@ where /// Creates a [`PaneGrid`] with the given [`State`] and view function. /// /// The view function will be called to display each [`Pane`] present in the - /// [`State`]. + /// [`State`]. [`bool`] is set if the pane is maximized. pub fn new<T>( state: &'a State<T>, - view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>, + view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, ) -> Self { - let elements = { - state - .panes - .iter() - .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) - .collect() + let contents = if let Some((pane, pane_state)) = + state.maximized.and_then(|pane| { + state.panes.get(&pane).map(|pane_state| (pane, pane_state)) + }) { + Contents::Maximized( + pane, + view(pane, pane_state, true), + Node::Pane(pane), + ) + } else { + Contents::All( + state + .panes + .iter() + .map(|(pane, pane_state)| { + (*pane, view(*pane, pane_state, false)) + }) + .collect(), + &state.internal, + ) }; Self { - elements, - state: &state.internal, + contents, width: Length::Fill, height: Length::Fill, spacing: 0, @@ -208,6 +221,12 @@ where self.style = style.into(); self } + + fn drag_enabled(&self) -> bool { + (!self.contents.is_maximized()) + .then(|| self.on_drag.is_some()) + .unwrap_or_default() + } } impl<'a, Message, Renderer> Widget<Message, Renderer> @@ -225,18 +244,25 @@ where } fn children(&self) -> Vec<Tree> { - self.elements + self.contents .iter() .map(|(_, content)| content.state()) .collect() } fn diff(&self, tree: &mut Tree) { - tree.diff_children_custom( - &self.elements, - |state, (_, content)| content.diff(state), - |(_, content)| content.state(), - ) + match &self.contents { + Contents::All(contents, _) => tree.diff_children_custom( + contents, + |state, (_, content)| content.diff(state), + |(_, content)| content.state(), + ), + Contents::Maximized(_, content, _) => tree.diff_children_custom( + &[content], + |state, content| content.diff(state), + |content| content.state(), + ), + } } fn width(&self) -> Length { @@ -255,15 +281,32 @@ where layout( renderer, limits, - self.state, + self.contents.layout(), self.width, self.height, self.spacing, - self.elements.iter().map(|(pane, content)| (*pane, content)), - |element, renderer, limits| element.layout(renderer, limits), + self.contents.iter(), + |content, renderer, limits| content.layout(renderer, limits), ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + operation.container(None, &mut |operation| { + self.contents + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|(((_pane, content), state), layout)| { + content.operate(state, layout, operation); + }) + }); + } + fn on_event( &mut self, tree: &mut Tree, @@ -276,28 +319,34 @@ where ) -> event::Status { let action = tree.state.downcast_mut::<state::Action>(); + let on_drag = if self.drag_enabled() { + &self.on_drag + } else { + &None + }; + let event_status = update( action, - self.state, + self.contents.layout(), &event, layout, cursor_position, shell, self.spacing, - self.elements.iter().map(|(pane, content)| (*pane, content)), + self.contents.iter(), &self.on_click, - &self.on_drag, + on_drag, &self.on_resize, ); let picked_pane = action.picked_pane().map(|(pane, _)| pane); - self.elements + self.contents .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .map(|(((pane, content), tree), layout)| { - let is_picked = picked_pane == Some(*pane); + let is_picked = picked_pane == Some(pane); content.on_event( tree, @@ -323,14 +372,14 @@ where ) -> mouse::Interaction { mouse_interaction( tree.state.downcast_ref(), - self.state, + self.contents.layout(), layout, cursor_position, self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), ) .unwrap_or_else(|| { - self.elements + self.contents .iter() .zip(&tree.children) .zip(layout.children()) @@ -341,7 +390,7 @@ where cursor_position, viewport, renderer, - self.on_drag.is_some(), + self.drag_enabled(), ) }) .max() @@ -361,7 +410,7 @@ where ) { draw( tree.state.downcast_ref(), - self.state, + self.contents.layout(), layout, cursor_position, renderer, @@ -370,11 +419,11 @@ where viewport, self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), - self.style, - self.elements + &self.style, + self.contents .iter() .zip(&tree.children) - .map(|((pane, content), tree)| (*pane, (content, tree))), + .map(|((pane, content), tree)| (pane, (content, tree))), |(content, tree), renderer, style, @@ -395,13 +444,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'_, Message, Renderer>> { - self.elements - .iter() + self.contents + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|(((_, pane), tree), layout)| { @@ -429,24 +478,24 @@ where pub fn layout<Renderer, T>( renderer: &Renderer, limits: &layout::Limits, - state: &state::Internal, + node: &Node, width: Length, height: Length, spacing: u16, - elements: impl Iterator<Item = (Pane, T)>, - layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, + contents: impl Iterator<Item = (Pane, T)>, + layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); - let regions = state.pane_regions(f32::from(spacing), size); - let children = elements - .filter_map(|(pane, element)| { + let regions = node.pane_regions(f32::from(spacing), size); + let children = contents + .filter_map(|(pane, content)| { let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); - let mut node = layout_element( - element, + let mut node = layout_content( + content, renderer, &layout::Limits::new(size, size), ); @@ -464,13 +513,13 @@ pub fn layout<Renderer, T>( /// accordingly. pub fn update<'a, Message, T: Draggable>( action: &mut state::Action, - state: &state::Internal, + node: &Node, event: &Event, layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, spacing: u16, - elements: impl Iterator<Item = (Pane, T)>, + contents: impl Iterator<Item = (Pane, T)>, on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, @@ -492,7 +541,7 @@ pub fn update<'a, Message, T: Draggable>( cursor_position.y - bounds.y, ); - let splits = state.split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -514,7 +563,7 @@ pub fn update<'a, Message, T: Draggable>( layout, cursor_position, shell, - elements, + contents, on_click, on_drag, ); @@ -526,7 +575,7 @@ pub fn update<'a, Message, T: Draggable>( layout, cursor_position, shell, - elements, + contents, on_click, on_drag, ); @@ -539,7 +588,7 @@ pub fn update<'a, Message, T: Draggable>( | Event::Touch(touch::Event::FingerLost { .. }) => { if let Some((pane, _)) = action.picked_pane() { if let Some(on_drag) = on_drag { - let mut dropped_region = elements + let mut dropped_region = contents .zip(layout.children()) .filter(|(_, layout)| { layout.bounds().contains(cursor_position) @@ -570,7 +619,7 @@ pub fn update<'a, Message, T: Draggable>( if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); - let splits = state.split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -609,13 +658,13 @@ fn click_pane<'a, Message, T>( layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, - elements: impl Iterator<Item = (Pane, T)>, + contents: impl Iterator<Item = (Pane, T)>, on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, ) where T: Draggable, { - let mut clicked_region = elements + let mut clicked_region = contents .zip(layout.children()) .filter(|(_, layout)| layout.bounds().contains(cursor_position)); @@ -642,7 +691,7 @@ fn click_pane<'a, Message, T>( /// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. pub fn mouse_interaction( action: &state::Action, - state: &state::Internal, + node: &Node, layout: Layout<'_>, cursor_position: Point, spacing: u16, @@ -658,7 +707,7 @@ pub fn mouse_interaction( let bounds = layout.bounds(); let splits = - state.split_regions(f32::from(spacing), bounds.size()); + node.split_regions(f32::from(spacing), bounds.size()); let relative_cursor = Point::new( cursor_position.x - bounds.x, @@ -687,7 +736,7 @@ pub fn mouse_interaction( /// Draws a [`PaneGrid`]. pub fn draw<Renderer, T>( action: &state::Action, - state: &state::Internal, + node: &Node, layout: Layout<'_>, cursor_position: Point, renderer: &mut Renderer, @@ -696,8 +745,8 @@ pub fn draw<Renderer, T>( viewport: &Rectangle, spacing: u16, resize_leeway: Option<u16>, - style: <Renderer::Theme as StyleSheet>::Style, - elements: impl Iterator<Item = (Pane, T)>, + style: &<Renderer::Theme as StyleSheet>::Style, + contents: impl Iterator<Item = (Pane, T)>, draw_pane: impl Fn( T, &mut Renderer, @@ -717,7 +766,7 @@ pub fn draw<Renderer, T>( .and_then(|(split, axis)| { let bounds = layout.bounds(); - let splits = state.split_regions(f32::from(spacing), bounds.size()); + let splits = node.split_regions(f32::from(spacing), bounds.size()); let (_axis, region, ratio) = splits.get(&split)?; @@ -736,7 +785,7 @@ pub fn draw<Renderer, T>( ); let splits = - state.split_regions(f32::from(spacing), bounds.size()); + node.split_regions(f32::from(spacing), bounds.size()); let (_split, axis, region) = hovered_split( splits.iter(), @@ -759,7 +808,7 @@ pub fn draw<Renderer, T>( let mut render_picked_pane = None; - for ((id, pane), layout) in elements.zip(layout.children()) { + for ((id, pane), layout) in contents.zip(layout.children()) { match picked_pane { Some((dragging, origin)) if id == dragging => { render_picked_pane = Some((pane, origin, layout)); @@ -897,3 +946,49 @@ fn hovered_split<'a>( }) .next() } + +/// The visible contents of the [`PaneGrid`] +#[derive(Debug)] +pub enum Contents<'a, T> { + /// All panes are visible + All(Vec<(Pane, T)>, &'a state::Internal), + /// A maximized pane is visible + Maximized(Pane, T, Node), +} + +impl<'a, T> Contents<'a, T> { + /// Returns the layout [`Node`] of the [`Contents`] + pub fn layout(&self) -> &Node { + match self { + Contents::All(_, state) => state.layout(), + Contents::Maximized(_, _, layout) => layout, + } + } + + /// Returns an iterator over the values of the [`Contents`] + pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> { + match self { + Contents::All(contents, _) => Box::new( + contents.iter().map(|(pane, content)| (*pane, content)), + ), + Contents::Maximized(pane, content, _) => { + Box::new(std::iter::once((*pane, content))) + } + } + } + + fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> { + match self { + Contents::All(contents, _) => Box::new( + contents.iter_mut().map(|(pane, content)| (*pane, content)), + ), + Contents::Maximized(pane, content, _) => { + Box::new(std::iter::once((*pane, content))) + } + } + } + + fn is_maximized(&self) -> bool { + matches!(self, Self::Maximized(..)) + } +} diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index c236d820..5f269d1f 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -5,7 +5,7 @@ use crate::overlay; use crate::renderer; use crate::widget::container; use crate::widget::pane_grid::{Draggable, TitleBar}; -use crate::widget::Tree; +use crate::widget::{self, Tree}; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// The content of a [`Pane`]. @@ -87,7 +87,7 @@ where /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. /// - /// [`Renderer`]: iced_native::Renderer + /// [`Renderer`]: crate::Renderer pub fn draw( &self, tree: &Tree, @@ -103,7 +103,7 @@ where let bounds = layout.bounds(); { - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); container::draw_background(renderer, &style, bounds); } @@ -183,6 +183,33 @@ where } } + pub(crate) fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + let body_layout = if let Some(title_bar) = &self.title_bar { + let mut children = layout.children(); + + title_bar.operate( + &mut tree.children[1], + children.next().unwrap(), + operation, + ); + + children.next().unwrap() + } else { + layout + }; + + self.body.as_widget().operate( + &mut tree.children[0], + body_layout, + operation, + ); + } + pub(crate) fn on_event( &mut self, tree: &mut Tree, @@ -278,12 +305,12 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - if let Some(title_bar) = self.title_bar.as_ref() { + if let Some(title_bar) = self.title_bar.as_mut() { let mut children = layout.children(); let title_bar_layout = children.next()?; @@ -294,14 +321,14 @@ where match title_bar.overlay(title_bar_state, title_bar_layout, renderer) { Some(overlay) => Some(overlay), - None => self.body.as_widget().overlay( + None => self.body.as_widget_mut().overlay( body_state, children.next()?, renderer, ), } } else { - self.body.as_widget().overlay( + self.body.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index cdca6267..c4ae0a0e 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -4,9 +4,9 @@ use crate::widget::pane_grid::{ Axis, Configuration, Direction, Node, Pane, Split, }; -use crate::{Point, Rectangle, Size}; +use crate::{Point, Size}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; /// The state of a [`PaneGrid`]. /// @@ -31,6 +31,11 @@ pub struct State<T> { /// /// [`PaneGrid`]: crate::widget::PaneGrid pub internal: Internal, + + /// The maximized [`Pane`] of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub(super) maximized: Option<Pane>, } impl<T> State<T> { @@ -52,7 +57,11 @@ impl<T> State<T> { let internal = Internal::from_configuration(&mut panes, config.into(), 0); - State { panes, internal } + State { + panes, + internal, + maximized: None, + } } /// Returns the total amount of panes in the [`State`]. @@ -153,6 +162,7 @@ impl<T> State<T> { node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); + let _ = self.maximized.take(); Some((new_pane, new_split)) } @@ -194,12 +204,39 @@ impl<T> State<T> { /// Closes the given [`Pane`] and returns its internal state and its closest /// sibling, if it exists. pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { + if self.maximized == Some(*pane) { + let _ = self.maximized.take(); + } + if let Some(sibling) = self.internal.layout.remove(pane) { self.panes.remove(pane).map(|state| (state, sibling)) } else { None } } + + /// Maximize the given [`Pane`]. Only this pane will be rendered by the + /// [`PaneGrid`] until [`Self::restore()`] is called. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn maximize(&mut self, pane: &Pane) { + self.maximized = Some(*pane); + } + + /// Restore the currently maximized [`Pane`] to it's normal size. All panes + /// will be rendered by the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn restore(&mut self) { + let _ = self.maximized.take(); + } + + /// Returns the maximized [`Pane`] of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn maximized(&self) -> Option<Pane> { + self.maximized + } } /// The internal state of a [`PaneGrid`]. @@ -226,11 +263,13 @@ impl Internal { let Internal { layout: a, last_id: next_id, + .. } = Self::from_configuration(panes, *a, next_id); let Internal { layout: b, last_id: next_id, + .. } = Self::from_configuration(panes, *b, next_id); ( @@ -304,25 +343,8 @@ impl Action { } impl Internal { - /// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn pane_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap<Pane, Rectangle> { - self.layout.pane_regions(spacing, size) - } - - /// Calculates the current [`Split`] regions from the [`PaneGrid`] layout. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn split_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap<Split, (Axis, Rectangle, f32)> { - self.layout.split_regions(spacing, size) + /// The layout [`Node`] of the [`Internal`] state + pub fn layout(&self) -> &Node { + &self.layout } } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index eb85f924..28e4670f 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -4,7 +4,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget::container; -use crate::widget::Tree; +use crate::widget::{self, Tree}; use crate::{ Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, }; @@ -114,7 +114,7 @@ where /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. /// - /// [`Renderer`]: iced_native::Renderer + /// [`Renderer`]: crate::Renderer pub fn draw( &self, tree: &Tree, @@ -129,7 +129,7 @@ where use container::StyleSheet; let bounds = layout.bounds(); - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); let inherited_style = renderer::Style { text_color: style.text_color.unwrap_or(inherited_style.text_color), }; @@ -257,6 +257,44 @@ where layout::Node::with_children(node.size().pad(self.padding), vec![node]) } + pub(crate) fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + let mut show_title = true; + + if let Some(controls) = &self.controls { + let controls_layout = children.next().unwrap(); + + if title_layout.bounds().width + controls_layout.bounds().width + > padded.bounds().width + { + show_title = false; + } + + controls.as_widget().operate( + &mut tree.children[1], + controls_layout, + operation, + ) + }; + + if show_title { + self.content.as_widget().operate( + &mut tree.children[0], + title_layout, + operation, + ) + } + } + pub(crate) fn on_event( &mut self, tree: &mut Tree, @@ -357,7 +395,7 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -377,13 +415,13 @@ where let controls_state = states.next().unwrap(); content - .as_widget() + .as_widget_mut() .overlay(title_state, title_layout, renderer) .or_else(move || { - controls.as_ref().and_then(|controls| { + controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; - controls.as_widget().overlay( + controls.as_widget_mut().overlay( controls_state, controls_layout, renderer, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index a92ea655..52cb1ad1 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -9,6 +9,8 @@ use crate::overlay::menu::{self, Menu}; use crate::renderer; use crate::text::{self, Text}; use crate::touch; +use crate::widget::container; +use crate::widget::scrollable; use crate::widget::tree::{self, Tree}; use crate::{ Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, @@ -42,7 +44,12 @@ where T: ToString + Eq, [T]: ToOwned<Owned = Vec<T>>, Renderer: text::Renderer, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { /// The default padding of a [`PickList`]. pub const DEFAULT_PADDING: Padding = Padding::new(5); @@ -114,7 +121,12 @@ where [T]: ToOwned<Owned = Vec<T>>, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { fn tag(&self) -> tree::Tag { tree::Tag::of::<State<T>>() @@ -202,12 +214,12 @@ where &self.font, self.placeholder.as_deref(), self.selected.as_ref(), - self.style, + &self.style, ) } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, @@ -221,7 +233,7 @@ where self.text_size, self.font.clone(), &self.options, - self.style, + self.style.clone(), ) } } @@ -233,7 +245,12 @@ where [T]: ToOwned<Owned = Vec<T>>, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self { Self::new(pick_list) @@ -456,7 +473,12 @@ where T: Clone + ToString, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { if state.is_open { let bounds = layout.bounds(); @@ -493,7 +515,7 @@ pub fn draw<T, Renderer>( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index 5f8892fe..f059026f 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -124,7 +124,7 @@ where / (range_end - range_start) }; - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); renderer.fill_quad( renderer::Quad { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 10e89870..b95ccc5b 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -230,9 +230,9 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(self.style, self.is_selected) + theme.hovered(&self.style, self.is_selected) } else { - theme.active(self.style, self.is_selected) + theme.active(&self.style, self.is_selected) }; { diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index eda7c2d3..c689ac13 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -229,12 +229,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index 1d1c04cf..2dc7b6f0 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -88,7 +88,7 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let style = theme.style(self.style); + let style = theme.appearance(&self.style); let bounds = if self.is_horizontal { let line_y = (bounds.y + (bounds.height / 2.0) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index b445e505..a5e97aa7 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -233,7 +233,7 @@ where self.scrollbar_width, self.scrollbar_margin, self.scroller_width, - self.style, + &self.style, |renderer, layout, cursor_position, viewport| { self.content.as_widget().draw( &tree.children[0], @@ -276,13 +276,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { self.content - .as_widget() + .as_widget_mut() .overlay( &mut tree.children[0], layout.children().next().unwrap(), @@ -334,6 +334,12 @@ impl Id { } } +impl From<Id> for widget::Id { + fn from(id: Id) -> Self { + id.0 + } +} + /// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] /// to the provided `percentage`. pub fn snap_to<Message: 'static>(id: Id, percentage: f32) -> Command<Message> { @@ -627,7 +633,7 @@ pub fn draw<Renderer>( scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), ) where Renderer: crate::Renderer, diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 011454de..87030a4d 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -222,7 +222,7 @@ where self.value, &self.range, theme, - self.style, + &self.style, ) } @@ -353,7 +353,7 @@ pub fn draw<T, R>( value: T, range: &RangeInclusive<T>, style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, - style: <R::Theme as StyleSheet>::Style, + style: &<R::Theme as StyleSheet>::Style, ) where T: Into<f64> + Copy, R: crate::Renderer, diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index aa68bfb8..1015ed0a 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -83,7 +83,7 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing @@ -120,7 +120,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index dab6e874..be9e775e 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -74,7 +74,7 @@ where self } - /// Sets the [`Color`] of the [`Text`]. + /// Sets the style of the [`Text`]. pub fn style( mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>, diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index ae0305dc..9391d1dd 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -165,7 +165,7 @@ where } /// Draws the [`TextInput`] with the given [`Renderer`], overriding its - /// [`text_input::Value`] if provided. + /// [`Value`] if provided. /// /// [`Renderer`]: text::Renderer pub fn draw( @@ -188,7 +188,7 @@ where self.size, &self.font, self.is_secure, - self.style, + &self.style, ) } } @@ -233,6 +233,7 @@ where let state = tree.state.downcast_mut::<State>(); operation.focusable(state, self.id.as_ref().map(|id| &id.0)); + operation.text_input(state, self.id.as_ref().map(|id| &id.0)); } fn on_event( @@ -284,7 +285,7 @@ where self.size, &self.font, self.is_secure, - self.style, + &self.style, ) } @@ -332,11 +333,43 @@ impl Id { } } +impl From<Id> for widget::Id { + fn from(id: Id) -> Self { + id.0 + } +} + /// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. pub fn focus<Message: 'static>(id: Id) -> Command<Message> { Command::widget(operation::focusable::focus(id.0)) } +/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// end. +pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> { + Command::widget(operation::text_input::move_cursor_to_end(id.0)) +} + +/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// front. +pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> { + Command::widget(operation::text_input::move_cursor_to_front(id.0)) +} + +/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// provided position. +pub fn move_cursor_to<Message: 'static>( + id: Id, + position: usize, +) -> Command<Message> { + Command::widget(operation::text_input::move_cursor_to(id.0, position)) +} + +/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. +pub fn select_all<Message: 'static>(id: Id) -> Command<Message> { + Command::widget(operation::text_input::select_all(id.0)) +} + /// Computes the layout of a [`TextInput`]. pub fn layout<Renderer>( renderer: &Renderer, @@ -350,6 +383,8 @@ where { let text_size = size.unwrap_or_else(|| renderer.default_size()); + let padding = padding.fit(Size::ZERO, limits.max()); + let limits = limits .pad(padding) .width(width) @@ -742,7 +777,7 @@ pub fn draw<Renderer>( size: Option<u16>, font: &Renderer::Font, is_secure: bool, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -997,6 +1032,24 @@ impl operation::Focusable for State { } } +impl operation::TextInput for State { + fn move_cursor_to_front(&mut self) { + State::move_cursor_to_front(self) + } + + fn move_cursor_to_end(&mut self) { + State::move_cursor_to_end(self) + } + + fn move_cursor_to(&mut self, position: usize) { + State::move_cursor_to(self, position) + } + + fn select_all(&mut self) { + State::select_all(self) + } +} + mod platform { use crate::keyboard; diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index c5c2d82a..37cafd92 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -260,9 +260,9 @@ where let is_mouse_over = bounds.contains(cursor_position); let style = if is_mouse_over { - theme.hovered(self.style, self.is_active) + theme.hovered(&self.style, self.is_active) } else { - theme.active(self.style, self.is_active) + theme.active(&self.style, self.is_active) }; let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 674f2ba6..084dc269 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -201,7 +201,7 @@ where self.gap, self.padding, self.snap_within_viewport, - self.style, + &self.style, |renderer, limits| { Widget::<(), Renderer>::layout(tooltip, renderer, limits) }, @@ -221,12 +221,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, @@ -275,7 +275,7 @@ pub fn draw<Renderer>( gap: u16, padding: u16, snap_within_viewport: bool, - style: <Renderer::Theme as container::StyleSheet>::Style, + style: &<Renderer::Theme as container::StyleSheet>::Style, layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, draw_text: impl FnOnce( &mut Renderer, diff --git a/native/src/widget/tree.rs b/native/src/widget/tree.rs index a8b1a185..0af40c33 100644 --- a/native/src/widget/tree.rs +++ b/native/src/widget/tree.rs @@ -30,7 +30,7 @@ impl Tree { } } - /// Creates a new [`Tree`] for the provided [`Element`]. + /// Creates a new [`Tree`] for the provided [`Widget`]. pub fn new<'a, Message, Renderer>( widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>, ) -> Self @@ -46,10 +46,10 @@ impl Tree { } } - /// Reconciliates the current tree with the provided [`Element`]. + /// Reconciliates the current tree with the provided [`Widget`]. /// - /// If the tag of the [`Element`] matches the tag of the [`Tree`], then the - /// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). + /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the + /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). /// /// Otherwise, the whole [`Tree`] is recreated. /// @@ -67,7 +67,7 @@ impl Tree { } } - /// Reconciliates the children of the tree with the provided list of [`Element`]. + /// Reconciliates the children of the tree with the provided list of widgets. pub fn diff_children<'a, Message, Renderer>( &mut self, new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>], @@ -81,7 +81,7 @@ impl Tree { ) } - /// Reconciliates the children of the tree with the provided list of [`Element`] using custom + /// Reconciliates the children of the tree with the provided list of widgets using custom /// logic both for diffing and creating new widget state. pub fn diff_children_custom<T>( &mut self, |