diff options
author | 2022-03-23 17:11:14 +0700 | |
---|---|---|
committer | 2022-03-23 17:11:14 +0700 | |
commit | 0eef527fa5b04be74141c75b076677473320e321 (patch) | |
tree | 5062a9ce2c370632de87a01471526da1176e0a60 /src | |
parent | 4aece6b77617f4a37af8208d8ddb1618bf9052d3 (diff) | |
parent | ef4c79ea23e86fec9a8ad0fb27463296c14400e5 (diff) | |
download | iced-0eef527fa5b04be74141c75b076677473320e321.tar.gz iced-0eef527fa5b04be74141c75b076677473320e321.tar.bz2 iced-0eef527fa5b04be74141c75b076677473320e321.zip |
Merge pull request #1284 from iced-rs/virtual-widgets
Stateless widgets
Diffstat (limited to '')
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/pure.rs | 33 | ||||
-rw-r--r-- | src/pure/application.rs | 186 | ||||
-rw-r--r-- | src/pure/sandbox.rs | 119 | ||||
-rw-r--r-- | src/pure/widget.rs | 167 | ||||
-rw-r--r-- | src/widget.rs | 219 |
6 files changed, 688 insertions, 40 deletions
@@ -195,6 +195,9 @@ pub mod time; pub mod widget; pub mod window; +#[cfg(feature = "pure")] +pub mod pure; + #[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_winit as runtime; @@ -214,6 +217,7 @@ pub use application::Application; pub use element::Element; pub use error::Error; pub use executor::Executor; +pub use renderer::Renderer; pub use result::Result; pub use sandbox::Sandbox; pub use settings::Settings; diff --git a/src/pure.rs b/src/pure.rs new file mode 100644 index 00000000..948183f1 --- /dev/null +++ b/src/pure.rs @@ -0,0 +1,33 @@ +//! Leverage pure, virtual widgets in your application. +//! +//! The widgets found in this module are completely stateless versions of +//! [the original widgets]. +//! +//! Effectively, this means that, as a user of the library, you do not need to +//! keep track of the local state of each widget (e.g. [`button::State`]). +//! Instead, the runtime will keep track of everything for you! +//! +//! You can embed pure widgets anywhere in your [impure `Application`] using the +//! [`Pure`] widget and some [`State`]. +//! +//! In case you want to only use pure widgets in your application, this module +//! offers an alternate [`Application`] trait with a completely pure `view` +//! method. +//! +//! [the original widgets]: crate::widget +//! [`button::State`]: crate::widget::button::State +//! [impure `Application`]: crate::Application +pub mod widget; + +mod application; +mod sandbox; + +pub use application::Application; +pub use sandbox::Sandbox; + +pub use iced_pure::helpers::*; +pub use iced_pure::{Pure, State}; + +/// A generic, pure [`Widget`]. +pub type Element<'a, Message> = + iced_pure::Element<'a, Message, crate::Renderer>; diff --git a/src/pure/application.rs b/src/pure/application.rs new file mode 100644 index 00000000..5f400bea --- /dev/null +++ b/src/pure/application.rs @@ -0,0 +1,186 @@ +use crate::pure::{self, Pure}; +use crate::window; +use crate::{Color, Command, Executor, Settings, Subscription}; + +/// A pure version of [`Application`]. +/// +/// Unlike the impure version, the `view` method of this trait takes an +/// immutable reference to `self` and returns a pure [`Element`]. +/// +/// [`Application`]: crate::Application +/// [`Element`]: pure::Element +pub trait Application: Sized { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [default executor] can be a good starting point! + /// + /// [`Executor`]: Self::Executor + /// [default executor]: crate::executor::Default + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + type Message: std::fmt::Debug + Send; + + /// The data needed to initialize your [`Application`]. + type Flags; + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + /// + /// [`run`]: Self::run + fn new(flags: Self::Flags) -> (Self, Command<Self::Message>); + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + fn subscription(&self) -> Subscription<Self::Message> { + Subscription::none() + } + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view(&self) -> pure::Element<'_, Self::Message>; + + /// Returns the current [`Application`] mode. + /// + /// The runtime will automatically transition your application if a new mode + /// is returned. + /// + /// Currently, the mode only has an effect in native platforms. + /// + /// By default, an application will run in windowed mode. + fn mode(&self) -> window::Mode { + window::Mode::Windowed + } + + /// Returns the background color of the [`Application`]. + /// + /// By default, it returns [`Color::WHITE`]. + fn background_color(&self) -> Color { + Color::WHITE + } + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self) -> f64 { + 1.0 + } + + /// Returns whether the [`Application`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } + + /// Runs the [`Application`]. + /// + /// On native platforms, this method will take control of the current thread + /// until the [`Application`] exits. + /// + /// On the web platform, this method __will NOT return__ unless there is an + /// [`Error`] during startup. + /// + /// [`Error`]: crate::Error + fn run(settings: Settings<Self::Flags>) -> crate::Result + where + Self: 'static, + { + <Instance<Self> as crate::Application>::run(settings) + } +} + +struct Instance<A: Application> { + application: A, + state: pure::State, +} + +impl<A> crate::Application for Instance<A> +where + A: Application, + A::Message: 'static, +{ + type Executor = A::Executor; + type Message = A::Message; + type Flags = A::Flags; + + fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) { + let (application, command) = A::new(flags); + + ( + Instance { + application, + state: pure::State::new(), + }, + command, + ) + } + + fn title(&self) -> String { + A::title(&self.application) + } + + fn update(&mut self, message: Self::Message) -> Command<Self::Message> { + A::update(&mut self.application, message) + } + + fn subscription(&self) -> Subscription<Self::Message> { + A::subscription(&self.application) + } + + fn view(&mut self) -> crate::Element<'_, Self::Message> { + let content = A::view(&self.application); + + Pure::new(&mut self.state, content).into() + } + + fn mode(&self) -> window::Mode { + A::mode(&self.application) + } + + fn background_color(&self) -> Color { + A::background_color(&self.application) + } + + fn scale_factor(&self) -> f64 { + A::scale_factor(&self.application) + } + + fn should_exit(&self) -> bool { + A::should_exit(&self.application) + } +} diff --git a/src/pure/sandbox.rs b/src/pure/sandbox.rs new file mode 100644 index 00000000..fbd1d7a8 --- /dev/null +++ b/src/pure/sandbox.rs @@ -0,0 +1,119 @@ +use crate::pure; +use crate::{Color, Command, Error, Settings, Subscription}; + +/// A pure version of [`Sandbox`]. +/// +/// Unlike the impure version, the `view` method of this trait takes an +/// immutable reference to `self` and returns a pure [`Element`]. +/// +/// [`Sandbox`]: crate::Sandbox +/// [`Element`]: pure::Element +pub trait Sandbox { + /// The type of __messages__ your [`Sandbox`] will produce. + type Message: std::fmt::Debug + Send; + + /// Initializes the [`Sandbox`]. + /// + /// Here is where you should return the initial state of your app. + fn new() -> Self; + + /// Returns the current title of the [`Sandbox`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Sandbox`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by user interactions, will be handled by this method. + fn update(&mut self, message: Self::Message); + + /// Returns the widgets to display in the [`Sandbox`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view(&self) -> pure::Element<'_, Self::Message>; + + /// Returns the background color of the [`Sandbox`]. + /// + /// By default, it returns [`Color::WHITE`]. + fn background_color(&self) -> Color { + Color::WHITE + } + + /// Returns the scale factor of the [`Sandbox`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self) -> f64 { + 1.0 + } + + /// Returns whether the [`Sandbox`] should be terminated. + /// + /// By default, it returns `false`. + fn should_exit(&self) -> bool { + false + } + + /// Runs the [`Sandbox`]. + /// + /// On native platforms, this method will take control of the current thread + /// and __will NOT return__. + /// + /// It should probably be that last thing you call in your `main` function. + fn run(settings: Settings<()>) -> Result<(), Error> + where + Self: 'static + Sized, + { + <Self as pure::Application>::run(settings) + } +} + +impl<T> pure::Application for T +where + T: Sandbox, +{ + type Executor = iced_futures::backend::null::Executor; + type Flags = (); + type Message = T::Message; + + fn new(_flags: ()) -> (Self, Command<T::Message>) { + (T::new(), Command::none()) + } + + fn title(&self) -> String { + T::title(self) + } + + fn update(&mut self, message: T::Message) -> Command<T::Message> { + T::update(self, message); + + Command::none() + } + + fn subscription(&self) -> Subscription<T::Message> { + Subscription::none() + } + + fn view(&self) -> pure::Element<'_, T::Message> { + T::view(self) + } + + fn background_color(&self) -> Color { + T::background_color(self) + } + + fn scale_factor(&self) -> f64 { + T::scale_factor(self) + } + + fn should_exit(&self) -> bool { + T::should_exit(self) + } +} diff --git a/src/pure/widget.rs b/src/pure/widget.rs new file mode 100644 index 00000000..6628b1fb --- /dev/null +++ b/src/pure/widget.rs @@ -0,0 +1,167 @@ +//! Pure versions of the widgets. + +/// A container that distributes its contents vertically. +pub type Column<'a, Message> = + iced_pure::widget::Column<'a, Message, crate::Renderer>; + +/// A container that distributes its contents horizontally. +pub type Row<'a, Message> = + iced_pure::widget::Row<'a, Message, crate::Renderer>; + +/// A paragraph of text. +pub type Text = iced_pure::widget::Text<crate::Renderer>; + +pub mod button { + //! Allow your users to perform actions by pressing a button. + pub use iced_pure::widget::button::{Style, StyleSheet}; + + /// A widget that produces a message when clicked. + pub type Button<'a, Message> = + iced_pure::widget::Button<'a, Message, crate::Renderer>; +} + +pub mod checkbox { + //! Show toggle controls using checkboxes. + pub use iced_pure::widget::checkbox::{Style, StyleSheet}; + + /// A box that can be checked. + pub type Checkbox<'a, Message> = + iced_native::widget::Checkbox<'a, Message, crate::Renderer>; +} + +pub mod container { + //! Decorate content and apply alignment. + pub use iced_pure::widget::container::{Style, StyleSheet}; + + /// An element decorating some content. + pub type Container<'a, Message> = + iced_pure::widget::Container<'a, Message, crate::Renderer>; +} + +pub mod pane_grid { + //! Let your users split regions of your application and organize layout dynamically. + //! + //! [](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 + pub use iced_pure::widget::pane_grid::{ + Axis, Configuration, Direction, DragEvent, Line, Node, Pane, + ResizeEvent, Split, State, StyleSheet, + }; + + /// A collection of panes distributed using either vertical or horizontal splits + /// to completely fill the space available. + /// + /// [](https://gfycat.com/mixedflatjellyfish) + pub type PaneGrid<'a, Message> = + iced_pure::widget::PaneGrid<'a, Message, crate::Renderer>; + + /// The content of a [`Pane`]. + pub type Content<'a, Message> = + iced_pure::widget::pane_grid::Content<'a, Message, crate::Renderer>; + + /// The title bar of a [`Pane`]. + pub type TitleBar<'a, Message> = + iced_pure::widget::pane_grid::TitleBar<'a, Message, crate::Renderer>; +} + +pub mod pick_list { + //! Display a dropdown list of selectable values. + pub use iced_pure::overlay::menu::Style as Menu; + pub use iced_pure::widget::pick_list::{Style, StyleSheet}; + + /// A widget allowing the selection of a single value from a list of options. + pub type PickList<'a, T, Message> = + iced_pure::widget::PickList<'a, T, Message, crate::Renderer>; +} + +pub mod radio { + //! Create choices using radio buttons. + pub use iced_pure::widget::radio::{Style, StyleSheet}; + + /// A circular button representing a choice. + pub type Radio<'a, Message> = + iced_pure::widget::Radio<'a, Message, crate::Renderer>; +} + +pub mod scrollable { + //! Navigate an endless amount of content with a scrollbar. + pub use iced_pure::widget::scrollable::{Scrollbar, Scroller, StyleSheet}; + + /// A widget that can vertically display an infinite amount of content + /// with a scrollbar. + pub type Scrollable<'a, Message> = + iced_pure::widget::Scrollable<'a, Message, crate::Renderer>; +} + +pub mod toggler { + //! Show toggle controls using togglers. + pub use iced_pure::widget::toggler::{Style, StyleSheet}; + + /// A toggler widget. + pub type Toggler<'a, Message> = + iced_pure::widget::Toggler<'a, Message, crate::Renderer>; +} + +pub mod text_input { + //! Display fields that can be filled with text. + use crate::Renderer; + + pub use iced_pure::widget::text_input::{Style, StyleSheet}; + + /// A field that can be filled with text. + pub type TextInput<'a, Message> = + iced_pure::widget::TextInput<'a, Message, Renderer>; +} + +pub use iced_pure::widget::progress_bar; +pub use iced_pure::widget::rule; +pub use iced_pure::widget::slider; +pub use iced_pure::widget::Space; + +pub use button::Button; +pub use checkbox::Checkbox; +pub use container::Container; +pub use pane_grid::PaneGrid; +pub use pick_list::PickList; +pub use progress_bar::ProgressBar; +pub use radio::Radio; +pub use rule::Rule; +pub use scrollable::Scrollable; +pub use slider::Slider; +pub use text_input::TextInput; +pub use toggler::Toggler; + +#[cfg(feature = "canvas")] +pub use iced_graphics::widget::pure::canvas; + +#[cfg(feature = "qr_code")] +pub use iced_graphics::widget::pure::qr_code; + +#[cfg(feature = "image")] +pub mod image { + //! Display images in your user interface. + pub use iced_native::image::Handle; + + /// A frame that displays an image. + pub type Image = iced_pure::widget::Image<Handle>; +} + +#[cfg(feature = "svg")] +pub use iced_pure::widget::svg; + +#[cfg(feature = "canvas")] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub use qr_code::QRCode; + +#[cfg(feature = "image")] +pub use image::Image; + +#[cfg(feature = "svg")] +pub use svg::Svg; diff --git a/src/widget.rs b/src/widget.rs index c619bcfa..9cc0832f 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -13,53 +13,192 @@ //! //! These widgets have their own module with a `State` type. For instance, a //! [`TextInput`] has some [`text_input::State`]. -pub use crate::renderer::widget::{ - button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, Space, - Text, -}; - -#[cfg(any(feature = "canvas", feature = "glow_canvas"))] -#[cfg_attr( - docsrs, - doc(cfg(any(feature = "canvas", feature = "glow_canvas"))) -)] -pub use crate::renderer::widget::canvas; - -#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] -#[cfg_attr( - docsrs, - doc(cfg(any(feature = "qr_code", feature = "glow_qr_code"))) -)] -pub use crate::renderer::widget::qr_code; - -#[cfg_attr(docsrs, doc(cfg(feature = "image")))] + +/// A container that distributes its contents vertically. +pub type Column<'a, Message> = + iced_native::widget::Column<'a, Message, crate::Renderer>; + +/// A container that distributes its contents horizontally. +pub type Row<'a, Message> = + iced_native::widget::Row<'a, Message, crate::Renderer>; + +/// A paragraph of text. +pub type Text = iced_native::widget::Text<crate::Renderer>; + +pub mod button { + //! Allow your users to perform actions by pressing a button. + //! + //! A [`Button`] has some local [`State`]. + pub use iced_native::widget::button::{State, Style, StyleSheet}; + + /// A widget that produces a message when clicked. + pub type Button<'a, Message> = + iced_native::widget::Button<'a, Message, crate::Renderer>; +} + +pub mod checkbox { + //! Show toggle controls using checkboxes. + pub use iced_native::widget::checkbox::{Style, StyleSheet}; + + /// A box that can be checked. + pub type Checkbox<'a, Message> = + iced_native::widget::Checkbox<'a, Message, crate::Renderer>; +} + +pub mod container { + //! Decorate content and apply alignment. + pub use iced_native::widget::container::{Style, StyleSheet}; + + /// An element decorating some content. + pub type Container<'a, Message> = + iced_native::widget::Container<'a, Message, crate::Renderer>; +} + +pub mod pane_grid { + //! Let your users split regions of your application and organize layout dynamically. + //! + //! [](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 + pub use iced_native::widget::pane_grid::{ + Axis, Configuration, Direction, DragEvent, Line, Node, Pane, + ResizeEvent, Split, State, StyleSheet, + }; + + /// A collection of panes distributed using either vertical or horizontal splits + /// to completely fill the space available. + /// + /// [](https://gfycat.com/mixedflatjellyfish) + pub type PaneGrid<'a, Message> = + iced_native::widget::PaneGrid<'a, Message, crate::Renderer>; + + /// The content of a [`Pane`]. + pub type Content<'a, Message> = + iced_native::widget::pane_grid::Content<'a, Message, crate::Renderer>; + + /// The title bar of a [`Pane`]. + pub type TitleBar<'a, Message> = + iced_native::widget::pane_grid::TitleBar<'a, Message, crate::Renderer>; +} + +pub mod pick_list { + //! Display a dropdown list of selectable values. + pub use iced_native::overlay::menu::Style as Menu; + pub use iced_native::widget::pick_list::{State, Style, StyleSheet}; + + /// A widget allowing the selection of a single value from a list of options. + pub type PickList<'a, T, Message> = + iced_native::widget::PickList<'a, T, Message, crate::Renderer>; +} + +pub mod radio { + //! Create choices using radio buttons. + pub use iced_native::widget::radio::{Style, StyleSheet}; + + /// A circular button representing a choice. + pub type Radio<'a, Message> = + iced_native::widget::Radio<'a, Message, crate::Renderer>; +} + +pub mod scrollable { + //! Navigate an endless amount of content with a scrollbar. + pub use iced_native::widget::scrollable::{ + style::Scrollbar, style::Scroller, State, StyleSheet, + }; + + /// A widget that can vertically display an infinite amount of content + /// with a scrollbar. + pub type Scrollable<'a, Message> = + iced_native::widget::Scrollable<'a, Message, crate::Renderer>; +} + +pub mod toggler { + //! Show toggle controls using togglers. + pub use iced_native::widget::toggler::{Style, StyleSheet}; + + /// A toggler widget. + pub type Toggler<'a, Message> = + iced_native::widget::Toggler<'a, Message, crate::Renderer>; +} + +pub mod text_input { + //! Display fields that can be filled with text. + //! + //! A [`TextInput`] has some local [`State`]. + use crate::Renderer; + + pub use iced_native::widget::text_input::{State, Style, StyleSheet}; + + /// A field that can be filled with text. + pub type TextInput<'a, Message> = + iced_native::widget::TextInput<'a, Message, Renderer>; +} + +pub mod tooltip { + //! Display a widget over another. + pub use iced_native::widget::tooltip::Position; + + /// A widget allowing the selection of a single value from a list of options. + pub type Tooltip<'a, Message> = + iced_native::widget::Tooltip<'a, Message, crate::Renderer>; +} + +pub use iced_native::widget::progress_bar; +pub use iced_native::widget::rule; +pub use iced_native::widget::slider; +pub use iced_native::widget::Space; + +pub use button::Button; +pub use checkbox::Checkbox; +pub use container::Container; +pub use pane_grid::PaneGrid; +pub use pick_list::PickList; +pub use progress_bar::ProgressBar; +pub use radio::Radio; +pub use rule::Rule; +pub use scrollable::Scrollable; +pub use slider::Slider; +pub use text_input::TextInput; +pub use toggler::Toggler; +pub use tooltip::Tooltip; + +#[cfg(feature = "canvas")] +pub use iced_graphics::widget::canvas; + +#[cfg(feature = "image")] pub mod image { //! Display images in your user interface. - pub use crate::runtime::image::Handle; - pub use crate::runtime::widget::image::viewer; - pub use crate::runtime::widget::image::{Image, Viewer}; + pub use iced_native::image::Handle; + + /// A frame that displays an image. + pub type Image = iced_native::widget::Image<Handle>; + + pub use iced_native::widget::image::viewer; + pub use viewer::Viewer; } -#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +#[cfg(feature = "qr_code")] +pub use iced_graphics::widget::qr_code; + +#[cfg(feature = "svg")] pub mod svg { - //! Display vector graphics in your user interface. - pub use crate::runtime::svg::Handle; - pub use crate::runtime::widget::svg::Svg; + //! Display vector graphics in your application. + pub use iced_native::svg::Handle; + pub use iced_native::widget::Svg; } -#[doc(no_inline)] -pub use { - button::Button, checkbox::Checkbox, container::Container, image::Image, - pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, - radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, svg::Svg, - text_input::TextInput, toggler::Toggler, tooltip::Tooltip, -}; - -#[cfg(any(feature = "canvas", feature = "glow_canvas"))] -#[doc(no_inline)] +#[cfg(feature = "canvas")] pub use canvas::Canvas; -#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] -#[doc(no_inline)] +#[cfg(feature = "image")] +pub use image::Image; + +#[cfg(feature = "qr_code")] pub use qr_code::QRCode; + +#[cfg(feature = "svg")] +pub use svg::Svg; |