diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/application.rs | 190 | ||||
| -rw-r--r-- | src/lib.rs | 255 | ||||
| -rw-r--r-- | src/native.rs | 113 | ||||
| -rw-r--r-- | src/sandbox.rs | 155 | ||||
| -rw-r--r-- | src/winit.rs | 13 | 
5 files changed, 644 insertions, 82 deletions
| diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 00000000..5ecb901e --- /dev/null +++ b/src/application.rs @@ -0,0 +1,190 @@ +use crate::{Command, Element}; + +/// An interactive cross-platform application. +/// +/// This trait is the main entrypoint of Iced. Once implemented, you can run +/// your GUI application by simply calling [`run`](#method.run). +/// +/// - On native platforms, it will run in its own window. +/// - On the web, it will take control of the `<title>` and the `<body>` of the +///   document. +/// +/// An [`Application`](trait.Application.html) can execute asynchronous actions +/// by returning a [`Command`](struct.Command.html) in some of its methods. If +/// you do not intend to perform any background work in your program, the +/// [`Sandbox`](trait.Sandbox.html) trait offers a simplified interface. +/// +/// # Example +/// Let's say we want to run the [`Counter` example we implemented +/// before](index.html#overview). We just need to fill in the gaps: +/// +/// ```no_run +/// use iced::{button, Application, Button, Column, Command, Element, Text}; +/// +/// pub fn main() { +///     Counter::run() +/// } +/// +/// #[derive(Default)] +/// struct Counter { +///     value: i32, +///     increment_button: button::State, +///     decrement_button: button::State, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// enum Message { +///     IncrementPressed, +///     DecrementPressed, +/// } +/// +/// impl Application for Counter { +///     type Message = Message; +/// +///     fn new() -> (Self, Command<Message>) { +///         (Self::default(), Command::none()) +///     } +/// +///     fn title(&self) -> String { +///         String::from("A simple counter") +///     } +/// +///     fn update(&mut self, message: Message) -> Command<Message> { +///         match message { +///             Message::IncrementPressed => { +///                 self.value += 1; +///             } +///             Message::DecrementPressed => { +///                 self.value -= 1; +///             } +///         } +/// +///         Command::none() +///     } +/// +///     fn view(&mut self) -> Element<Message> { +///         Column::new() +///             .push( +///                 Button::new(&mut self.increment_button, Text::new("Increment")) +///                     .on_press(Message::IncrementPressed), +///             ) +///             .push( +///                 Text::new(self.value.to_string()).size(50), +///             ) +///             .push( +///                 Button::new(&mut self.decrement_button, Text::new("Decrement")) +///                     .on_press(Message::DecrementPressed), +///             ) +///             .into() +///     } +/// } +/// ``` +pub trait Application: Sized { +    /// The type of __messages__ your [`Application`] will produce. +    /// +    /// [`Application`]: trait.Application.html +    type Message: std::fmt::Debug + Send; + +    /// Initializes the [`Application`]. +    /// +    /// Here is where you should return the initial state of your app. +    /// +    /// Additionally, you can return a [`Command`](struct.Command.html) 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. +    /// +    /// [`Application`]: trait.Application.html +    fn new() -> (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. +    /// +    /// [`Application`]: trait.Application.html +    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. +    /// +    /// [`Application`]: trait.Application.html +    /// [`Command`]: struct.Command.html +    fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + +    /// Returns the widgets to display in the [`Application`]. +    /// +    /// These widgets can produce __messages__ based on user interaction. +    /// +    /// [`Application`]: trait.Application.html +    fn view(&mut self) -> Element<'_, Self::Message>; + +    /// Runs the [`Application`]. +    /// +    /// 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. +    /// +    /// [`Application`]: trait.Application.html +    fn run() +    where +        Self: 'static, +    { +        #[cfg(not(target_arch = "wasm32"))] +        <Instance<Self> as iced_winit::Application>::run(); + +        #[cfg(target_arch = "wasm32")] +        iced_web::Application::run(Instance(self)); +    } +} + +struct Instance<A: Application>(A); + +#[cfg(not(target_arch = "wasm32"))] +impl<A> iced_winit::Application for Instance<A> +where +    A: Application, +{ +    type Renderer = iced_wgpu::Renderer; +    type Message = A::Message; + +    fn new() -> (Self, Command<A::Message>) { +        let (app, command) = A::new(); + +        (Instance(app), command) +    } + +    fn title(&self) -> String { +        self.0.title() +    } + +    fn update(&mut self, message: Self::Message) -> Command<Self::Message> { +        self.0.update(message) +    } + +    fn view(&mut self) -> Element<'_, Self::Message> { +        self.0.view() +    } +} + +#[cfg(target_arch = "wasm32")] +impl<A> iced_web::Application for Instance<A> +where +    A: Application, +{ +    type Message = A::Message; + +    fn update(&mut self, message: Self::Message) { +        self.0.update(message); +    } + +    fn view(&mut self) -> Element<Self::Message> { +        self.0.view() +    } +} @@ -1,73 +1,190 @@ +//! Iced is a cross-platform GUI library focused on simplicity and type-safety. +//! Inspired by [Elm]. +//! +//! # Features +//! * Simple, easy-to-use, batteries-included API +//! * Type-safe, reactive programming model +//! * [Cross-platform support] (Windows, macOS, Linux, and the Web) +//! * Responsive layout +//! * Built-in widgets (including [text inputs], [scrollables], and more!) +//! * Custom widget support (create your own!) +//! * [Debug overlay with performance metrics] +//! * First-class support for async actions (use futures!) +//! * [Modular ecosystem] split into reusable parts: +//!   * A [renderer-agnostic native runtime] enabling integration with existing +//!     systems +//!   * A [built-in renderer] supporting Vulkan, Metal, DX11, and DX12 +//!   * A [windowing shell] +//!   * A [web runtime] leveraging the DOM +//! +//! Check out the [repository] and the [examples] for more details! +//! +//! [Cross-platform support]: https://github.com/hecrj/iced/blob/master/docs/images/todos_desktop.jpg?raw=true +//! [text inputs]: https://gfycat.com/alertcalmcrow-rust-gui +//! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui +//! [Debug overlay with performance metrics]: https://gfycat.com/artisticdiligenthorseshoebat-rust-gui +//! [Modular ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md +//! [renderer-agnostic native runtime]: https://github.com/hecrj/iced/tree/master/native +//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs +//! [built-in renderer]: https://github.com/hecrj/iced/tree/master/wgpu +//! [windowing shell]: https://github.com/hecrj/iced/tree/master/winit +//! [`dodrio`]: https://github.com/fitzgen/dodrio +//! [web runtime]: https://github.com/hecrj/iced/tree/master/web +//! [examples]: https://github.com/hecrj/iced/tree/0.1.0/examples +//! [repository]: https://github.com/hecrj/iced +//! +//! # Overview +//! Inspired by [The Elm Architecture], Iced expects you to split user +//! interfaces into four different concepts: +//! +//!   * __State__ — the state of your application +//!   * __Messages__ — user interactions or meaningful events that you care +//!   about +//!   * __View logic__ — a way to display your __state__ as widgets that +//!   may produce __messages__ on user interaction +//!   * __Update logic__ — a way to react to __messages__ and update your +//!   __state__ +//! +//! We can build something to see how this works! Let's say we want a simple +//! counter that can be incremented and decremented using two buttons. +//! +//! We start by modelling the __state__ of our application: +//! +//! ``` +//! use iced::button; +//! +//! struct Counter { +//!     // The counter value +//!     value: i32, +//! +//!     // The local state of the two buttons +//!     increment_button: button::State, +//!     decrement_button: button::State, +//! } +//! ``` +//! +//! Next, we need to define the possible user interactions of our counter: +//! the button presses. These interactions are our __messages__: +//! +//! ``` +//! #[derive(Debug, Clone, Copy)] +//! pub enum Message { +//!     IncrementPressed, +//!     DecrementPressed, +//! } +//! ``` +//! +//! Now, let's show the actual counter by putting it all together in our +//! __view logic__: +//! +//! ``` +//! # use iced::button; +//! # +//! # struct Counter { +//! #     // The counter value +//! #     value: i32, +//! # +//! #     // The local state of the two buttons +//! #     increment_button: button::State, +//! #     decrement_button: button::State, +//! # } +//! # +//! # #[derive(Debug, Clone, Copy)] +//! # pub enum Message { +//! #     IncrementPressed, +//! #     DecrementPressed, +//! # } +//! # +//! use iced::{Button, Column, Text}; +//! +//! impl Counter { +//!     pub fn view(&mut self) -> Column<Message> { +//!         // We use a column: a simple vertical layout +//!         Column::new() +//!             .push( +//!                 // The increment button. We tell it to produce an +//!                 // `IncrementPressed` message when pressed +//!                 Button::new(&mut self.increment_button, Text::new("+")) +//!                     .on_press(Message::IncrementPressed), +//!             ) +//!             .push( +//!                 // We show the value of the counter here +//!                 Text::new(self.value.to_string()).size(50), +//!             ) +//!             .push( +//!                 // The decrement button. We tell it to produce a +//!                 // `DecrementPressed` message when pressed +//!                 Button::new(&mut self.decrement_button, Text::new("-")) +//!                     .on_press(Message::DecrementPressed), +//!             ) +//!     } +//! } +//! ``` +//! +//! Finally, we need to be able to react to any produced __messages__ and change +//! our __state__ accordingly in our __update logic__: +//! +//! ``` +//! # use iced::button; +//! # +//! # struct Counter { +//! #     // The counter value +//! #     value: i32, +//! # +//! #     // The local state of the two buttons +//! #     increment_button: button::State, +//! #     decrement_button: button::State, +//! # } +//! # +//! # #[derive(Debug, Clone, Copy)] +//! # pub enum Message { +//! #     IncrementPressed, +//! #     DecrementPressed, +//! # } +//! impl Counter { +//!     // ... +//! +//!     pub fn update(&mut self, message: Message) { +//!         match message { +//!             Message::IncrementPressed => { +//!                 self.value += 1; +//!             } +//!             Message::DecrementPressed => { +//!                 self.value -= 1; +//!             } +//!         } +//!     } +//! } +//! ``` +//! +//! And that's everything! We just wrote a whole user interface. Iced is now +//! able to: +//! +//!   1. Take the result of our __view logic__ and layout its widgets. +//!   1. Process events from our system and produce __messages__ for our +//!      __update logic__. +//!   1. Draw the resulting user interface. +//! +//! # Usage +//! Take a look at the [`Application`] trait, which streamlines all the process +//! described above for you! +//! +//! [Elm]: https://elm-lang.org/ +//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ +//! [documentation]: https://docs.rs/iced +//! [examples]: https://github.com/hecrj/iced/tree/master/examples +//! [`Application`]: trait.Application.html +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![deny(rust_2018_idioms)] +mod application;  #[cfg_attr(target_arch = "wasm32", path = "web.rs")] -#[cfg_attr(not(target_arch = "wasm32"), path = "winit.rs")] +#[cfg_attr(not(target_arch = "wasm32"), path = "native.rs")]  mod platform; +mod sandbox; +pub use application::Application;  pub use platform::*; - -pub trait Application: Sized { -    type Message: std::fmt::Debug + Send; - -    fn new() -> (Self, Command<Self::Message>); - -    fn title(&self) -> String; - -    fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - -    fn view(&mut self) -> Element<Self::Message>; - -    fn run() -    where -        Self: 'static + Sized, -    { -        #[cfg(not(target_arch = "wasm32"))] -        <Instance<Self> as iced_winit::Application>::run(); - -        #[cfg(target_arch = "wasm32")] -        iced_web::Application::run(Instance(self)); -    } -} - -struct Instance<A: Application>(A); - -#[cfg(not(target_arch = "wasm32"))] -impl<A> iced_winit::Application for Instance<A> -where -    A: Application, -{ -    type Renderer = Renderer; -    type Message = A::Message; - -    fn new() -> (Self, Command<A::Message>) { -        let (app, command) = A::new(); - -        (Instance(app), command) -    } - -    fn title(&self) -> String { -        self.0.title() -    } - -    fn update(&mut self, message: Self::Message) -> Command<Self::Message> { -        self.0.update(message) -    } - -    fn view(&mut self) -> Element<Self::Message> { -        self.0.view() -    } -} - -#[cfg(target_arch = "wasm32")] -impl<A> iced_web::Application for Instance<A> -where -    A: Application, -{ -    type Message = A::Message; - -    fn update(&mut self, message: Self::Message) { -        self.0.update(message); -    } - -    fn view(&mut self) -> Element<Self::Message> { -        self.0.view() -    } -} +pub use sandbox::Sandbox; diff --git a/src/native.rs b/src/native.rs new file mode 100644 index 00000000..926b2d11 --- /dev/null +++ b/src/native.rs @@ -0,0 +1,113 @@ +pub use iced_winit::{ +    Align, Background, Color, Command, Font, HorizontalAlignment, Length, +    VerticalAlignment, +}; + +pub mod widget { +    //! Display information and interactive controls in your application. +    //! +    //! # Re-exports +    //! For convenience, the contents of this module are available at the root +    //! module. Therefore, you can directly type: +    //! +    //! ``` +    //! use iced::{button, Button}; +    //! ``` +    //! +    //! # Stateful widgets +    //! Some widgets need to keep track of __local state__. +    //! +    //! These widgets have their own module with a `State` type. For instance, a +    //! [`TextInput`] has some [`text_input::State`]. +    //! +    //! [`TextInput`]: text_input/struct.TextInput.html +    //! [`text_input::State`]: text_input/struct.State.html +    pub mod button { +        //! Allow your users to perform actions by pressing a button. +        //! +        //! A [`Button`] has some local [`State`]. +        //! +        //! [`Button`]: type.Button.html +        //! [`State`]: struct.State.html + +        /// A widget that produces a message when clicked. +        /// +        /// This is an alias of an `iced_native` button with a default +        /// `Renderer`. +        pub type Button<'a, Message> = +            iced_winit::Button<'a, Message, iced_wgpu::Renderer>; + +        pub use iced_winit::button::State; +    } + +    pub mod scrollable { +        //! Navigate an endless amount of content with a scrollbar. + +        /// A widget that can vertically display an infinite amount of content +        /// with a scrollbar. +        /// +        /// This is an alias of an `iced_native` scrollable with a default +        /// `Renderer`. +        pub type Scrollable<'a, Message> = +            iced_winit::Scrollable<'a, Message, iced_wgpu::Renderer>; + +        pub use iced_winit::scrollable::State; +    } + +    pub mod text_input { +        //! Ask for information using text fields. +        //! +        //! A [`TextInput`] has some local [`State`]. +        //! +        //! [`TextInput`]: struct.TextInput.html +        //! [`State`]: struct.State.html +        pub use iced_winit::text_input::{State, TextInput}; +    } + +    pub mod slider { +        //! Display an interactive selector of a single value from a range of +        //! values. +        //! +        //! A [`Slider`] has some local [`State`]. +        //! +        //! [`Slider`]: struct.Slider.html +        //! [`State`]: struct.State.html +        pub use iced_winit::slider::{Slider, State}; +    } + +    pub use iced_winit::{Checkbox, Image, Radio, Text}; + +    #[doc(no_inline)] +    pub use { +        button::Button, scrollable::Scrollable, slider::Slider, +        text_input::TextInput, +    }; + +    /// A container that distributes its contents vertically. +    /// +    /// This is an alias of an `iced_native` column with a default `Renderer`. +    pub type Column<'a, Message> = +        iced_winit::Column<'a, Message, iced_wgpu::Renderer>; + +    /// A container that distributes its contents horizontally. +    /// +    /// This is an alias of an `iced_native` row with a default `Renderer`. +    pub type Row<'a, Message> = +        iced_winit::Row<'a, Message, iced_wgpu::Renderer>; + +    /// An element decorating some content. +    /// +    /// This is an alias of an `iced_native` container with a default +    /// `Renderer`. +    pub type Container<'a, Message> = +        iced_winit::Container<'a, Message, iced_wgpu::Renderer>; +} + +#[doc(no_inline)] +pub use widget::*; + +/// A generic widget. +/// +/// This is an alias of an `iced_native` element with a default `Renderer`. +pub type Element<'a, Message> = +    iced_winit::Element<'a, Message, iced_wgpu::Renderer>; diff --git a/src/sandbox.rs b/src/sandbox.rs new file mode 100644 index 00000000..698578f4 --- /dev/null +++ b/src/sandbox.rs @@ -0,0 +1,155 @@ +use crate::{Application, Command, Element}; + +/// A sandboxed [`Application`]. +/// +/// A [`Sandbox`] is just an [`Application`] that cannot run any asynchronous +/// actions. +/// +/// If you do not need to leverage a [`Command`], you can use a [`Sandbox`] +/// instead of returning a [`Command::none`] everywhere. +/// +/// [`Application`]: trait.Application.html +/// [`Sandbox`]: trait.Sandbox.html +/// [`Command`]: struct.Command.html +/// [`Command::none`]: struct.Command.html#method.none +/// +/// # Example +/// We can use a [`Sandbox`] to run the [`Counter` example we implemented +/// before](index.html#overview), instead of an [`Application`]. We just need +/// to remove the use of [`Command`]: +/// +/// ```no_run +/// use iced::{button, Button, Column, Element, Sandbox, Text}; +/// +/// pub fn main() { +///     Counter::run() +/// } +/// +/// #[derive(Default)] +/// struct Counter { +///     value: i32, +///     increment_button: button::State, +///     decrement_button: button::State, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// enum Message { +///     IncrementPressed, +///     DecrementPressed, +/// } +/// +/// impl Sandbox for Counter { +///     type Message = Message; +/// +///     fn new() -> Self { +///         Self::default() +///     } +/// +///     fn title(&self) -> String { +///         String::from("A simple counter") +///     } +/// +///     fn update(&mut self, message: Message) { +///         match message { +///             Message::IncrementPressed => { +///                 self.value += 1; +///             } +///             Message::DecrementPressed => { +///                 self.value -= 1; +///             } +///         } +///     } +/// +///     fn view(&mut self) -> Element<Message> { +///         Column::new() +///             .push( +///                 Button::new(&mut self.increment_button, Text::new("Increment")) +///                     .on_press(Message::IncrementPressed), +///             ) +///             .push( +///                 Text::new(self.value.to_string()).size(50), +///             ) +///             .push( +///                 Button::new(&mut self.decrement_button, Text::new("Decrement")) +///                     .on_press(Message::DecrementPressed), +///             ) +///             .into() +///     } +/// } +/// ``` +pub trait Sandbox { +    /// The type of __messages__ your [`Sandbox`] will produce. +    /// +    /// [`Sandbox`]: trait.Sandbox.html +    type Message: std::fmt::Debug + Send; + +    /// Initializes the [`Sandbox`]. +    /// +    /// Here is where you should return the initial state of your app. +    /// +    /// [`Sandbox`]: trait.Sandbox.html +    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. +    /// +    /// [`Sandbox`]: trait.Sandbox.html +    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. +    /// +    /// [`Sandbox`]: trait.Sandbox.html +    fn update(&mut self, message: Self::Message); + +    /// Returns the widgets to display in the [`Sandbox`]. +    /// +    /// These widgets can produce __messages__ based on user interaction. +    /// +    /// [`Sandbox`]: trait.Sandbox.html +    fn view(&mut self) -> Element<'_, Self::Message>; + +    /// Runs the [`Sandbox`]. +    /// +    /// 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. +    /// +    /// [`Sandbox`]: trait.Sandbox.html +    fn run() +    where +        Self: 'static + Sized, +    { +        <Self as Application>::run() +    } +} + +impl<T> Application for T +where +    T: Sandbox, +{ +    type Message = T::Message; + +    fn new() -> (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 view(&mut self) -> Element<'_, T::Message> { +        T::view(self) +    } +} diff --git a/src/winit.rs b/src/winit.rs deleted file mode 100644 index c869a269..00000000 --- a/src/winit.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub use iced_wgpu::{Primitive, Renderer}; - -pub use iced_winit::{ -    button, scrollable, slider, text, text_input, winit, Align, Background, -    Checkbox, Color, Command, Font, Image, Length, Radio, Scrollable, Slider, -    Text, TextInput, -}; - -pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; -pub type Container<'a, Message> = iced_winit::Container<'a, Message, Renderer>; -pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; -pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; -pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; | 
