diff options
author | 2024-06-20 01:23:01 +0200 | |
---|---|---|
committer | 2024-06-20 01:23:01 +0200 | |
commit | 714d4503154a6224c26f2eed6e399c73d57b4bf8 (patch) | |
tree | 9456eb2d40f2761c29dfa51f370efe11dc22f5e5 /src | |
parent | 19db068bbbebcda1756720525da247f35bd3a5e0 (diff) | |
parent | c5f4bebeda8d6ef10efade7933a5ee58f06b62d1 (diff) | |
download | iced-714d4503154a6224c26f2eed6e399c73d57b4bf8.tar.gz iced-714d4503154a6224c26f2eed6e399c73d57b4bf8.tar.bz2 iced-714d4503154a6224c26f2eed6e399c73d57b4bf8.zip |
Merge pull request #2469 from iced-rs/unify-shell-runtimes
`Daemon` API and Shell Runtime Unification
Diffstat (limited to 'src')
-rw-r--r-- | src/advanced.rs | 1 | ||||
-rw-r--r-- | src/application.rs | 590 | ||||
-rw-r--r-- | src/daemon.rs | 298 | ||||
-rw-r--r-- | src/lib.rs | 33 | ||||
-rw-r--r-- | src/multi_window.rs | 254 | ||||
-rw-r--r-- | src/program.rs | 754 | ||||
-rw-r--r-- | src/settings.rs | 49 |
7 files changed, 949 insertions, 1030 deletions
diff --git a/src/advanced.rs b/src/advanced.rs index 5826ba0f..8d06e805 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -1,5 +1,4 @@ //! Leverage advanced concepts like custom widgets. -pub use crate::application::Application; pub use crate::core::clipboard::{self, Clipboard}; pub use crate::core::image; pub use crate::core::layout::{self, Layout}; diff --git a/src/application.rs b/src/application.rs index 4cd4a87d..edca6e79 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,278 +1,426 @@ -//! Build interactive cross-platform applications. -use crate::core::text; -use crate::graphics::compositor; -use crate::shell::application; -use crate::{Element, Executor, Settings, Subscription, Task}; +//! Create and run iced applications step by step. +//! +//! # Example +//! ```no_run +//! use iced::widget::{button, column, text, Column}; +//! use iced::Theme; +//! +//! pub fn main() -> iced::Result { +//! iced::application("A counter", update, view) +//! .theme(|_| Theme::Dark) +//! .centered() +//! .run() +//! } +//! +//! #[derive(Debug, Clone)] +//! enum Message { +//! Increment, +//! } +//! +//! fn update(value: &mut u64, message: Message) { +//! match message { +//! Message::Increment => *value += 1, +//! } +//! } +//! +//! fn view(value: &u64) -> Column<Message> { +//! column![ +//! text(value), +//! button("+").on_press(Message::Increment), +//! ] +//! } +//! ``` +use crate::program::{self, Program}; +use crate::window; +use crate::{Element, Font, Result, Settings, Size, Subscription, Task}; -pub use application::{Appearance, DefaultStyle}; +use std::borrow::Cow; -/// 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`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using an [`Application`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -/// -/// # Examples -/// [The repository has a bunch of examples] that use the [`Application`] trait: -/// -/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock -/// and its hands to display the current time. -/// - [`download_progress`], a basic application that asynchronously downloads -/// a dummy file of 100 MB and tracks the download progress. -/// - [`events`], a log of native events displayed using a conditional -/// [`Subscription`]. -/// - [`game_of_life`], an interactive version of the [Game of Life], invented -/// by [John Horton Conway]. -/// - [`pokedex`], an application that displays a random Pokédex entry (sprite -/// included!) by using the [PokéAPI]. -/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget -/// and showcasing how to compose different transforms. -/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how -/// to listen to time. -/// - [`todos`], a todos tracker inspired by [TodoMVC]. -/// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.12/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.12/examples/clock -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.12/examples/download_progress -/// [`events`]: https://github.com/iced-rs/iced/tree/0.12/examples/events -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.12/examples/game_of_life -/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.12/examples/pokedex -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.12/examples/solar_system -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.12/examples/stopwatch -/// [`todos`]: https://github.com/iced-rs/iced/tree/0.12/examples/todos -/// [`Sandbox`]: crate::Sandbox -/// [`Canvas`]: crate::widget::Canvas -/// [PokéAPI]: https://pokeapi.co/ -/// [TodoMVC]: http://todomvc.com/ -/// -/// ## A simple "Hello, world!" -/// -/// If you just want to get started, here is a simple [`Application`] that -/// says "Hello, world!": +pub use crate::shell::program::{Appearance, DefaultStyle}; + +/// Creates an iced [`Application`] given its title, update, and view logic. /// +/// # Example /// ```no_run -/// use iced::advanced::Application; -/// use iced::executor; -/// use iced::{Task, Element, Settings, Theme, Renderer}; +/// use iced::widget::{button, column, text, Column}; /// /// pub fn main() -> iced::Result { -/// Hello::run(Settings::default()) +/// iced::application("A counter", update, view).run() /// } /// -/// struct Hello; -/// -/// impl Application for Hello { -/// type Executor = executor::Default; -/// type Flags = (); -/// type Message = (); -/// type Theme = Theme; -/// type Renderer = Renderer; -/// -/// fn new(_flags: ()) -> (Hello, Task<Self::Message>) { -/// (Hello, Task::none()) -/// } -/// -/// fn title(&self) -> String { -/// String::from("A cool application") -/// } +/// #[derive(Debug, Clone)] +/// enum Message { +/// Increment, +/// } /// -/// fn update(&mut self, _message: Self::Message) -> Task<Self::Message> { -/// Task::none() +/// fn update(value: &mut u64, message: Message) { +/// match message { +/// Message::Increment => *value += 1, /// } +/// } /// -/// fn view(&self) -> Element<Self::Message> { -/// "Hello, world!".into() -/// } +/// fn view(value: &u64) -> Column<Message> { +/// column![ +/// text(value), +/// button("+").on_press(Message::Increment), +/// ] /// } /// ``` -pub trait Application: Sized +pub fn application<State, Message, Theme, Renderer>( + title: impl Title<State>, + update: impl Update<State, Message>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> Application<impl Program<State = State, Message = Message, Theme = Theme>> where - Self::Theme: DefaultStyle, + State: 'static, + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, { - /// 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; + use std::marker::PhantomData; - /// The type of __messages__ your [`Application`] will produce. - type Message: std::fmt::Debug + Send; + struct Instance<State, Message, Theme, Renderer, Update, View> { + update: Update, + view: View, + _state: PhantomData<State>, + _message: PhantomData<Message>, + _theme: PhantomData<Theme>, + _renderer: PhantomData<Renderer>, + } - /// The theme of your [`Application`]. - type Theme: Default; + impl<State, Message, Theme, Renderer, Update, View> Program + for Instance<State, Message, Theme, Renderer, Update, View> + where + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, + Update: self::Update<State, Message>, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Message = Message; + type Theme = Theme; + type Renderer = Renderer; + type Executor = iced_futures::backend::default::Executor; - /// The renderer of your [`Application`]. - type Renderer: text::Renderer + compositor::Default; + fn load(&self) -> Task<Self::Message> { + Task::none() + } - /// The data needed to initialize your [`Application`]. - type Flags; + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message> { + self.update.update(state, message).into() + } - /// 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 [`Task`] 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, Task<Self::Message>); + fn view<'a>( + &self, + state: &'a Self::State, + _window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state).into() + } + } - /// 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; + Application { + raw: Instance { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: Settings::default(), + window: window::Settings::default(), + } + .title(title) +} - /// Handles a __message__ and updates the state of the [`Application`]. +/// The underlying definition and configuration of an iced application. +/// +/// You can use this API to create and run iced applications +/// step by step—without coupling your logic to a trait +/// or a specific type. +/// +/// You can create an [`Application`] with the [`application`] helper. +#[derive(Debug)] +pub struct Application<P: Program> { + raw: P, + settings: Settings, + window: window::Settings, +} + +impl<P: Program> Application<P> { + /// Runs 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. + /// The state of the [`Application`] must implement [`Default`]. + /// If your state does not implement [`Default`], use [`run_with`] + /// instead. /// - /// Any [`Task`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Task<Self::Message>; + /// [`run_with`]: Self::run_with + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.run_with(P::State::default) + } - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; + /// Runs the [`Application`] with a closure that creates the initial state. + pub fn run_with<I>(self, initialize: I) -> Result + where + Self: 'static, + I: Fn() -> P::State + Clone + 'static, + { + self.raw + .run_with(self.settings, Some(self.window), initialize) + } - /// Returns the current [`Theme`] of the [`Application`]. - /// - /// [`Theme`]: Self::Theme - fn theme(&self) -> Self::Theme { - Self::Theme::default() + /// Sets the [`Settings`] that will be used to run the [`Application`]. + pub fn settings(self, settings: Settings) -> Self { + Self { settings, ..self } } - /// Returns the current [`Appearance`] of the [`Application`]. - fn style(&self, theme: &Self::Theme) -> Appearance { - theme.default_style() + /// Sets the [`Settings::antialiasing`] of the [`Application`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: Settings { + antialiasing, + ..self.settings + }, + ..self + } } - /// 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() + /// Sets the default [`Font`] of the [`Application`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: Settings { + default_font, + ..self.settings + }, + ..self + } } - /// 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 + /// Adds a font to the list of fonts that will be loaded at the start of the [`Application`]. + pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self { + self.settings.fonts.push(font.into()); + self } - /// 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, - { - #[allow(clippy::needless_update)] - let renderer_settings = crate::graphics::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: if settings.antialiasing { - Some(crate::graphics::Antialiasing::MSAAx4) - } else { - None + /// Sets the [`window::Settings::position`] to [`window::Position::Centered`] in the [`Application`]. + pub fn centered(self) -> Self { + Self { + window: window::Settings { + position: window::Position::Centered, + ..self.window }, - ..crate::graphics::Settings::default() - }; - - Ok(crate::shell::application::run::< - Instance<Self>, - Self::Executor, - <Self::Renderer as compositor::Default>::Compositor, - >(settings.into(), renderer_settings)?) + ..self + } } -} -struct Instance<A>(A) -where - A: Application, - A::Theme: DefaultStyle; + /// Sets the [`window::Settings::exit_on_close_request`] of the [`Application`]. + pub fn exit_on_close_request(self, exit_on_close_request: bool) -> Self { + Self { + window: window::Settings { + exit_on_close_request, + ..self.window + }, + ..self + } + } -impl<A> crate::runtime::Program for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Message = A::Message; - type Theme = A::Theme; - type Renderer = A::Renderer; + /// Sets the [`window::Settings::size`] of the [`Application`]. + pub fn window_size(self, size: impl Into<Size>) -> Self { + Self { + window: window::Settings { + size: size.into(), + ..self.window + }, + ..self + } + } - fn update(&mut self, message: Self::Message) -> Task<Self::Message> { - self.0.update(message) + /// Sets the [`window::Settings::transparent`] of the [`Application`]. + pub fn transparent(self, transparent: bool) -> Self { + Self { + window: window::Settings { + transparent, + ..self.window + }, + ..self + } } - fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> { - self.0.view() + /// Sets the [`Title`] of the [`Application`]. + pub(crate) fn title( + self, + title: impl Title<P::State>, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_title(self.raw, move |state, _window| { + title.title(state) + }), + settings: self.settings, + window: self.window, + } } -} -impl<A> application::Application for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Flags = A::Flags; + /// Runs the [`Task`] produced by the closure at startup. + pub fn load( + self, + f: impl Fn() -> Task<P::Message>, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_load(self.raw, f), + settings: self.settings, + window: self.window, + } + } - fn new(flags: Self::Flags) -> (Self, Task<A::Message>) { - let (app, command) = A::new(flags); + /// Sets the subscription logic of the [`Application`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> Subscription<P::Message>, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_subscription(self.raw, f), + settings: self.settings, + window: self.window, + } + } - (Instance(app), command) + /// Sets the theme logic of the [`Application`]. + pub fn theme( + self, + f: impl Fn(&P::State) -> P::Theme, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_theme(self.raw, move |state, _window| f(state)), + settings: self.settings, + window: self.window, + } } - fn title(&self) -> String { - self.0.title() + /// Sets the style logic of the [`Application`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> Appearance, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_style(self.raw, f), + settings: self.settings, + window: self.window, + } } - fn theme(&self) -> A::Theme { - self.0.theme() + /// Sets the scale factor of the [`Application`]. + pub fn scale_factor( + self, + f: impl Fn(&P::State) -> f64, + ) -> Application< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Application { + raw: program::with_scale_factor(self.raw, move |state, _window| { + f(state) + }), + settings: self.settings, + window: self.window, + } } +} + +/// The title logic of some [`Application`]. +/// +/// This trait is implemented both for `&static str` and +/// any closure `Fn(&State) -> String`. +/// +/// This trait allows the [`application`] builder to take any of them. +pub trait Title<State> { + /// Produces the title of the [`Application`]. + fn title(&self, state: &State) -> String; +} - fn style(&self, theme: &A::Theme) -> Appearance { - self.0.style(theme) +impl<State> Title<State> for &'static str { + fn title(&self, _state: &State) -> String { + self.to_string() } +} - fn subscription(&self) -> Subscription<Self::Message> { - self.0.subscription() +impl<T, State> Title<State> for T +where + T: Fn(&State) -> String, +{ + fn title(&self, state: &State) -> String { + self(state) } +} - fn scale_factor(&self) -> f64 { - self.0.scale_factor() +/// The update logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into<Task<Message>>`. +pub trait Update<State, Message> { + /// Processes the message and updates the state of the [`Application`]. + fn update( + &self, + state: &mut State, + message: Message, + ) -> impl Into<Task<Message>>; +} + +impl<T, State, Message, C> Update<State, Message> for T +where + T: Fn(&mut State, Message) -> C, + C: Into<Task<Message>>, +{ + fn update( + &self, + state: &mut State, + message: Message, + ) -> impl Into<Task<Message>> { + self(state, message) + } +} + +/// The view logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into<Element<'_, Message>>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Application`]. + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State) -> Widget, + State: 'static, + Widget: Into<Element<'a, Message, Theme, Renderer>>, +{ + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>> { + self(state) } } diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 00000000..58293949 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,298 @@ +//! Create and run daemons that run in the background. +use crate::application; +use crate::program::{self, Program}; +use crate::window; +use crate::{Element, Font, Result, Settings, Subscription, Task}; + +use std::borrow::Cow; + +pub use crate::shell::program::{Appearance, DefaultStyle}; + +/// Creates an iced [`Daemon`] given its title, update, and view logic. +/// +/// A [`Daemon`] will not open a window by default, but will run silently +/// instead until a [`Task`] from [`window::open`] is returned by its update logic. +/// +/// Furthermore, a [`Daemon`] will not stop running when all its windows are closed. +/// In order to completely terminate a [`Daemon`], its process must be interrupted or +/// its update logic must produce a [`Task`] from [`exit`]. +/// +/// [`exit`]: crate::exit +pub fn daemon<State, Message, Theme, Renderer>( + title: impl Title<State>, + update: impl application::Update<State, Message>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>> +where + State: 'static, + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, +{ + use std::marker::PhantomData; + + struct Instance<State, Message, Theme, Renderer, Update, View> { + update: Update, + view: View, + _state: PhantomData<State>, + _message: PhantomData<Message>, + _theme: PhantomData<Theme>, + _renderer: PhantomData<Renderer>, + } + + impl<State, Message, Theme, Renderer, Update, View> Program + for Instance<State, Message, Theme, Renderer, Update, View> + where + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: program::Renderer, + Update: application::Update<State, Message>, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Message = Message; + type Theme = Theme; + type Renderer = Renderer; + type Executor = iced_futures::backend::default::Executor; + + fn load(&self) -> Task<Self::Message> { + Task::none() + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message> { + self.update.update(state, message).into() + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state, window).into() + } + } + + Daemon { + raw: Instance { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: Settings::default(), + } + .title(title) +} + +/// The underlying definition and configuration of an iced daemon. +/// +/// You can use this API to create and run iced applications +/// step by step—without coupling your logic to a trait +/// or a specific type. +/// +/// You can create a [`Daemon`] with the [`daemon`] helper. +#[derive(Debug)] +pub struct Daemon<P: Program> { + raw: P, + settings: Settings, +} + +impl<P: Program> Daemon<P> { + /// Runs the [`Daemon`]. + /// + /// The state of the [`Daemon`] must implement [`Default`]. + /// If your state does not implement [`Default`], use [`run_with`] + /// instead. + /// + /// [`run_with`]: Self::run_with + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.run_with(P::State::default) + } + + /// Runs the [`Daemon`] with a closure that creates the initial state. + pub fn run_with<I>(self, initialize: I) -> Result + where + Self: 'static, + I: Fn() -> P::State + Clone + 'static, + { + self.raw.run_with(self.settings, None, initialize) + } + + /// Sets the [`Settings`] that will be used to run the [`Daemon`]. + pub fn settings(self, settings: Settings) -> Self { + Self { settings, ..self } + } + + /// Sets the [`Settings::antialiasing`] of the [`Daemon`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: Settings { + antialiasing, + ..self.settings + }, + ..self + } + } + + /// Sets the default [`Font`] of the [`Daemon`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: Settings { + default_font, + ..self.settings + }, + ..self + } + } + + /// Adds a font to the list of fonts that will be loaded at the start of the [`Daemon`]. + pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self { + self.settings.fonts.push(font.into()); + self + } + + /// Sets the [`Title`] of the [`Daemon`]. + pub(crate) fn title( + self, + title: impl Title<P::State>, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_title(self.raw, move |state, window| { + title.title(state, window) + }), + settings: self.settings, + } + } + + /// Runs the [`Task`] produced by the closure at startup. + pub fn load( + self, + f: impl Fn() -> Task<P::Message>, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_load(self.raw, f), + settings: self.settings, + } + } + + /// Sets the subscription logic of the [`Daemon`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> Subscription<P::Message>, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_subscription(self.raw, f), + settings: self.settings, + } + } + + /// Sets the theme logic of the [`Daemon`]. + pub fn theme( + self, + f: impl Fn(&P::State, window::Id) -> P::Theme, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_theme(self.raw, f), + settings: self.settings, + } + } + + /// Sets the style logic of the [`Daemon`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> Appearance, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_style(self.raw, f), + settings: self.settings, + } + } + + /// Sets the scale factor of the [`Daemon`]. + pub fn scale_factor( + self, + f: impl Fn(&P::State, window::Id) -> f64, + ) -> Daemon< + impl Program<State = P::State, Message = P::Message, Theme = P::Theme>, + > { + Daemon { + raw: program::with_scale_factor(self.raw, f), + settings: self.settings, + } + } +} + +/// The title logic of some [`Daemon`]. +/// +/// This trait is implemented both for `&static str` and +/// any closure `Fn(&State, window::Id) -> String`. +/// +/// This trait allows the [`daemon`] builder to take any of them. +pub trait Title<State> { + /// Produces the title of the [`Daemon`]. + fn title(&self, state: &State, window: window::Id) -> String; +} + +impl<State> Title<State> for &'static str { + fn title(&self, _state: &State, _window: window::Id) -> String { + self.to_string() + } +} + +impl<T, State> Title<State> for T +where + T: Fn(&State, window::Id) -> String, +{ + fn title(&self, state: &State, window: window::Id) -> String { + self(state, window) + } +} + +/// The view logic of some [`Daemon`]. +/// +/// This trait allows the [`daemon`] builder to take any closure that +/// returns any `Into<Element<'_, Message>>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Daemon`]. + fn view( + &self, + state: &'a State, + window: window::Id, + ) -> impl Into<Element<'a, Message, Theme, Renderer>>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State, window::Id) -> Widget, + State: 'static, + Widget: Into<Element<'a, Message, Theme, Renderer>>, +{ + fn view( + &self, + state: &'a State, + window: window::Id, + ) -> impl Into<Element<'a, Message, Theme, Renderer>> { + self(state, window) + } +} @@ -158,11 +158,11 @@ //! 1. Draw the resulting user interface. //! //! # Usage -//! Use [`run`] or the [`program`] builder. +//! Use [`run`] or the [`application`] builder. //! //! [Elm]: https://elm-lang.org/ //! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ -//! [`program`]: program() +//! [`application`]: application() #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] @@ -179,10 +179,11 @@ pub use iced_futures::futures; #[cfg(feature = "highlighter")] pub use iced_highlighter as highlighter; -mod application; mod error; +mod program; -pub mod program; +pub mod application; +pub mod daemon; pub mod settings; pub mod time; pub mod window; @@ -190,9 +191,6 @@ pub mod window; #[cfg(feature = "advanced")] pub mod advanced; -#[cfg(feature = "multi-window")] -pub mod multi_window; - pub use crate::core::alignment; pub use crate::core::border; pub use crate::core::color; @@ -203,7 +201,7 @@ pub use crate::core::{ Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size, Theme, Transformation, Vector, }; -pub use crate::runtime::Task; +pub use crate::runtime::{exit, Task}; pub mod clipboard { //! Access the clipboard. @@ -308,11 +306,12 @@ pub mod widget { mod runtime {} } +pub use application::{application, Application}; +pub use daemon::{daemon, Daemon}; pub use error::Error; pub use event::Event; pub use executor::Executor; pub use font::Font; -pub use program::Program; pub use renderer::Renderer; pub use settings::Settings; pub use subscription::Subscription; @@ -327,13 +326,13 @@ pub type Element< Renderer = crate::Renderer, > = crate::core::Element<'a, Message, Theme, Renderer>; -/// The result of running a [`Program`]. +/// The result of running an iced program. pub type Result = std::result::Result<(), Error>; /// Runs a basic iced application with default [`Settings`] given its title, /// update, and view logic. /// -/// This is equivalent to chaining [`program`] with [`Program::run`]. +/// This is equivalent to chaining [`application()`] with [`Application::run`]. /// /// [`program`]: program() /// @@ -364,9 +363,10 @@ pub type Result = std::result::Result<(), Error>; /// } /// ``` pub fn run<State, Message, Theme, Renderer>( - title: impl program::Title<State> + 'static, - update: impl program::Update<State, Message> + 'static, - view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static, + title: impl application::Title<State> + 'static, + update: impl application::Update<State, Message> + 'static, + view: impl for<'a> application::View<'a, State, Message, Theme, Renderer> + + 'static, ) -> Result where State: Default + 'static, @@ -374,8 +374,5 @@ where Theme: Default + program::DefaultStyle + 'static, Renderer: program::Renderer + 'static, { - program(title, update, view).run() + application(title, update, view).run() } - -#[doc(inline)] -pub use program::program; diff --git a/src/multi_window.rs b/src/multi_window.rs deleted file mode 100644 index 4900bb85..00000000 --- a/src/multi_window.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Leverage multi-window support in your application. -use crate::window; -use crate::{Element, Executor, Settings, Subscription, Task}; - -pub use crate::application::{Appearance, DefaultStyle}; - -/// An interactive cross-platform multi-window 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 windows. -/// - On the web, it will take control of the `<title>` and the `<body>` of the -/// document and display only the contents of the `window::Id::MAIN` window. -/// -/// An [`Application`] can execute asynchronous actions by returning a -/// [`Task`] in some of its methods. -/// -/// When using an [`Application`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -/// -/// # Examples -/// See the `examples/multi-window` example to see this multi-window `Application` trait in action. -/// -/// ## A simple "Hello, world!" -/// -/// If you just want to get started, here is a simple [`Application`] that -/// says "Hello, world!": -/// -/// ```no_run -/// use iced::{executor, window}; -/// use iced::{Task, Element, Settings, Theme}; -/// use iced::multi_window::{self, Application}; -/// -/// pub fn main() -> iced::Result { -/// Hello::run(Settings::default()) -/// } -/// -/// struct Hello; -/// -/// impl multi_window::Application for Hello { -/// type Executor = executor::Default; -/// type Flags = (); -/// type Message = (); -/// type Theme = Theme; -/// -/// fn new(_flags: ()) -> (Hello, Task<Self::Message>) { -/// (Hello, Task::none()) -/// } -/// -/// fn title(&self, _window: window::Id) -> String { -/// String::from("A cool application") -/// } -/// -/// fn update(&mut self, _message: Self::Message) -> Task<Self::Message> { -/// Task::none() -/// } -/// -/// fn view(&self, _window: window::Id) -> Element<Self::Message> { -/// "Hello, world!".into() -/// } -/// } -/// ``` -/// -/// [`Sandbox`]: crate::Sandbox -pub trait Application: Sized -where - Self::Theme: DefaultStyle, -{ - /// 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 theme of your [`Application`]. - type Theme: Default; - - /// 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 [`Task`] 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, Task<Self::Message>); - - /// Returns the current title of the `window` of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your window when necessary. - fn title(&self, window: window::Id) -> 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 [`Task`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Task<Self::Message>; - - /// Returns the widgets to display in the `window` of the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view( - &self, - window: window::Id, - ) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>; - - /// Returns the current [`Theme`] of the `window` of the [`Application`]. - /// - /// [`Theme`]: Self::Theme - #[allow(unused_variables)] - fn theme(&self, window: window::Id) -> Self::Theme { - Self::Theme::default() - } - - /// Returns the current `Style` of the [`Theme`]. - /// - /// [`Theme`]: Self::Theme - fn style(&self, theme: &Self::Theme) -> Appearance { - Self::Theme::default_style(theme) - } - - /// 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 scale factor of the `window` 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`. - #[allow(unused_variables)] - fn scale_factor(&self, window: window::Id) -> f64 { - 1.0 - } - - /// Runs the multi-window [`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, - { - #[allow(clippy::needless_update)] - let renderer_settings = crate::graphics::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: if settings.antialiasing { - Some(crate::graphics::Antialiasing::MSAAx4) - } else { - None - }, - ..crate::graphics::Settings::default() - }; - - Ok(crate::shell::multi_window::run::< - Instance<Self>, - Self::Executor, - crate::renderer::Compositor, - >(settings.into(), renderer_settings)?) - } -} - -struct Instance<A>(A) -where - A: Application, - A::Theme: DefaultStyle; - -impl<A> crate::runtime::multi_window::Program for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Message = A::Message; - type Theme = A::Theme; - type Renderer = crate::Renderer; - - fn update(&mut self, message: Self::Message) -> Task<Self::Message> { - self.0.update(message) - } - - fn view( - &self, - window: window::Id, - ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> { - self.0.view(window) - } -} - -impl<A> crate::shell::multi_window::Application for Instance<A> -where - A: Application, - A::Theme: DefaultStyle, -{ - type Flags = A::Flags; - - fn new(flags: Self::Flags) -> (Self, Task<A::Message>) { - let (app, command) = A::new(flags); - - (Instance(app), command) - } - - fn title(&self, window: window::Id) -> String { - self.0.title(window) - } - - fn theme(&self, window: window::Id) -> A::Theme { - self.0.theme(window) - } - - fn style(&self, theme: &Self::Theme) -> Appearance { - self.0.style(theme) - } - - fn subscription(&self) -> Subscription<Self::Message> { - self.0.subscription() - } - - fn scale_factor(&self, window: window::Id) -> f64 { - self.0.scale_factor(window) - } -} diff --git a/src/program.rs b/src/program.rs index ea6b0e8e..3f9d2d0c 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,194 +1,108 @@ -//! Create and run iced applications step by step. -//! -//! # Example -//! ```no_run -//! use iced::widget::{button, column, text, Column}; -//! use iced::Theme; -//! -//! pub fn main() -> iced::Result { -//! iced::program("A counter", update, view) -//! .theme(|_| Theme::Dark) -//! .centered() -//! .run() -//! } -//! -//! #[derive(Debug, Clone)] -//! enum Message { -//! Increment, -//! } -//! -//! fn update(value: &mut u64, message: Message) { -//! match message { -//! Message::Increment => *value += 1, -//! } -//! } -//! -//! fn view(value: &u64) -> Column<Message> { -//! column![ -//! text(value), -//! button("+").on_press(Message::Increment), -//! ] -//! } -//! ``` -use crate::application::Application; use crate::core::text; -use crate::executor::{self, Executor}; use crate::graphics::compositor; +use crate::shell; use crate::window; -use crate::{Element, Font, Result, Settings, Size, Subscription, Task}; +use crate::{Element, Executor, Result, Settings, Subscription, Task}; -pub use crate::application::{Appearance, DefaultStyle}; +pub use crate::shell::program::{Appearance, DefaultStyle}; -use std::borrow::Cow; - -/// Creates an iced [`Program`] given its title, update, and view logic. -/// -/// # Example -/// ```no_run -/// use iced::widget::{button, column, text, Column}; -/// -/// pub fn main() -> iced::Result { -/// iced::program("A counter", update, view).run() -/// } -/// -/// #[derive(Debug, Clone)] -/// enum Message { -/// Increment, -/// } -/// -/// fn update(value: &mut u64, message: Message) { -/// match message { -/// Message::Increment => *value += 1, -/// } -/// } +/// The internal definition of a [`Program`]. /// -/// fn view(value: &u64) -> Column<Message> { -/// column![ -/// text(value), -/// button("+").on_press(Message::Increment), -/// ] -/// } -/// ``` -pub fn program<State, Message, Theme, Renderer>( - title: impl Title<State>, - update: impl Update<State, Message>, - view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, -) -> Program<impl Definition<State = State, Message = Message, Theme = Theme>> -where - State: 'static, - Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, - Renderer: self::Renderer, -{ - use std::marker::PhantomData; - - struct Application<State, Message, Theme, Renderer, Update, View> { - update: Update, - view: View, - _state: PhantomData<State>, - _message: PhantomData<Message>, - _theme: PhantomData<Theme>, - _renderer: PhantomData<Renderer>, - } +/// You should not need to implement this trait directly. Instead, use the +/// methods available in the [`Program`] struct. +#[allow(missing_docs)] +pub trait Program: Sized { + /// The state of the program. + type State; - impl<State, Message, Theme, Renderer, Update, View> Definition - for Application<State, Message, Theme, Renderer, Update, View> - where - Message: Send + std::fmt::Debug + 'static, - Theme: Default + DefaultStyle, - Renderer: self::Renderer, - Update: self::Update<State, Message>, - View: for<'a> self::View<'a, State, Message, Theme, Renderer>, - { - type State = State; - type Message = Message; - type Theme = Theme; - type Renderer = Renderer; - type Executor = executor::Default; + /// The message of the program. + type Message: Send + std::fmt::Debug + 'static; - fn load(&self) -> Task<Self::Message> { - Task::none() - } + /// The theme of the program. + type Theme: Default + DefaultStyle; - fn update( - &self, - state: &mut Self::State, - message: Self::Message, - ) -> Task<Self::Message> { - self.update.update(state, message).into() - } + /// The renderer of the program. + type Renderer: Renderer; - fn view<'a>( - &self, - state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.view.view(state).into() - } + /// The executor of the program. + type Executor: Executor; + + fn load(&self) -> Task<Self::Message>; + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message>; + + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; + + fn title(&self, _state: &Self::State, _window: window::Id) -> String { + String::from("A cool iced application!") } - Program { - raw: Application { - update, - view, - _state: PhantomData, - _message: PhantomData, - _theme: PhantomData, - _renderer: PhantomData, - }, - settings: Settings::default(), + fn subscription( + &self, + _state: &Self::State, + ) -> Subscription<Self::Message> { + Subscription::none() } - .title(title) -} -/// The underlying definition and configuration of an iced application. -/// -/// You can use this API to create and run iced applications -/// step by step—without coupling your logic to a trait -/// or a specific type. -/// -/// You can create a [`Program`] with the [`program`] helper. -/// -/// [`run`]: Program::run -#[derive(Debug)] -pub struct Program<P: Definition> { - raw: P, - settings: Settings, -} + fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme { + Self::Theme::default() + } + + fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance { + DefaultStyle::default_style(theme) + } -impl<P: Definition> Program<P> { - /// Runs the underlying [`Application`] of the [`Program`]. + fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 { + 1.0 + } + + /// Runs the [`Program`]. /// /// The state of the [`Program`] must implement [`Default`]. /// If your state does not implement [`Default`], use [`run_with`] /// instead. /// /// [`run_with`]: Self::run_with - pub fn run(self) -> Result + fn run( + self, + settings: Settings, + window_settings: Option<window::Settings>, + ) -> Result where Self: 'static, - P::State: Default, + Self::State: Default, { - self.run_with(P::State::default) + self.run_with(settings, window_settings, Self::State::default) } - /// Runs the underlying [`Application`] of the [`Program`] with a - /// closure that creates the initial state. - pub fn run_with( + /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. + fn run_with<I>( self, - initialize: impl Fn() -> P::State + Clone + 'static, + settings: Settings, + window_settings: Option<window::Settings>, + initialize: I, ) -> Result where Self: 'static, + I: Fn() -> Self::State + Clone + 'static, { use std::marker::PhantomData; - struct Instance<P: Definition, I> { + struct Instance<P: Program, I> { program: P, state: P::State, _initialize: PhantomData<I>, } - impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> { + impl<P: Program, I: Fn() -> P::State> shell::Program for Instance<P, I> { type Message = P::Message; type Theme = P::Theme; type Renderer = P::Renderer; @@ -211,8 +125,8 @@ impl<P: Definition> Program<P> { ) } - fn title(&self) -> String { - self.program.title(&self.state) + fn title(&self, window: window::Id) -> String { + self.program.title(&self.state, window) } fn update( @@ -224,259 +138,73 @@ impl<P: Definition> Program<P> { fn view( &self, + window: window::Id, ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(&self.state) + self.program.view(&self.state, window) } fn subscription(&self) -> Subscription<Self::Message> { self.program.subscription(&self.state) } - fn theme(&self) -> Self::Theme { - self.program.theme(&self.state) + fn theme(&self, window: window::Id) -> Self::Theme { + self.program.theme(&self.state, window) } fn style(&self, theme: &Self::Theme) -> Appearance { self.program.style(&self.state, theme) } - } - let Self { raw, settings } = self; + fn scale_factor(&self, window: window::Id) -> f64 { + self.program.scale_factor(&self.state, window) + } + } - Instance::run(Settings { - flags: (raw, initialize), - id: settings.id, - window: settings.window, - fonts: settings.fonts, + #[allow(clippy::needless_update)] + let renderer_settings = crate::graphics::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - }) - } - - /// Sets the [`Settings`] that will be used to run the [`Program`]. - pub fn settings(self, settings: Settings) -> Self { - Self { settings, ..self } - } - - /// Sets the [`Settings::antialiasing`] of the [`Program`]. - pub fn antialiasing(self, antialiasing: bool) -> Self { - Self { - settings: Settings { - antialiasing, - ..self.settings - }, - ..self - } - } - - /// Sets the default [`Font`] of the [`Program`]. - pub fn default_font(self, default_font: Font) -> Self { - Self { - settings: Settings { - default_font, - ..self.settings - }, - ..self - } - } - - /// Adds a font to the list of fonts that will be loaded at the start of the [`Program`]. - pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self { - self.settings.fonts.push(font.into()); - self - } - - /// Sets the [`window::Settings::position`] to [`window::Position::Centered`] in the [`Program`]. - pub fn centered(self) -> Self { - Self { - settings: Settings { - window: window::Settings { - position: window::Position::Centered, - ..self.settings.window - }, - ..self.settings - }, - ..self - } - } - - /// Sets the [`window::Settings::exit_on_close_request`] of the [`Program`]. - pub fn exit_on_close_request(self, exit_on_close_request: bool) -> Self { - Self { - settings: Settings { - window: window::Settings { - exit_on_close_request, - ..self.settings.window - }, - ..self.settings - }, - ..self - } - } - - /// Sets the [`window::Settings::size`] of the [`Program`]. - pub fn window_size(self, size: impl Into<Size>) -> Self { - Self { - settings: Settings { - window: window::Settings { - size: size.into(), - ..self.settings.window - }, - ..self.settings - }, - ..self - } - } - - /// Sets the [`window::Settings::transparent`] of the [`Program`]. - pub fn transparent(self, transparent: bool) -> Self { - Self { - settings: Settings { - window: window::Settings { - transparent, - ..self.settings.window - }, - ..self.settings + antialiasing: if settings.antialiasing { + Some(crate::graphics::Antialiasing::MSAAx4) + } else { + None }, - ..self - } - } - - /// Sets the [`Title`] of the [`Program`]. - pub(crate) fn title( - self, - title: impl Title<P::State>, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_title(self.raw, title), - settings: self.settings, - } - } - - /// Runs the [`Task`] produced by the closure at startup. - pub fn load( - self, - f: impl Fn() -> Task<P::Message>, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_load(self.raw, f), - settings: self.settings, - } - } - - /// Sets the subscription logic of the [`Program`]. - pub fn subscription( - self, - f: impl Fn(&P::State) -> Subscription<P::Message>, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_subscription(self.raw, f), - settings: self.settings, - } - } - - /// Sets the theme logic of the [`Program`]. - pub fn theme( - self, - f: impl Fn(&P::State) -> P::Theme, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_theme(self.raw, f), - settings: self.settings, - } - } - - /// Sets the style logic of the [`Program`]. - pub fn style( - self, - f: impl Fn(&P::State, &P::Theme) -> Appearance, - ) -> Program< - impl Definition<State = P::State, Message = P::Message, Theme = P::Theme>, - > { - Program { - raw: with_style(self.raw, f), - settings: self.settings, - } - } -} - -/// The internal definition of a [`Program`]. -/// -/// You should not need to implement this trait directly. Instead, use the -/// methods available in the [`Program`] struct. -#[allow(missing_docs)] -pub trait Definition: Sized { - /// The state of the program. - type State; - - /// The message of the program. - type Message: Send + std::fmt::Debug + 'static; - - /// The theme of the program. - type Theme: Default + DefaultStyle; - - /// The renderer of the program. - type Renderer: Renderer; - - /// The executor of the program. - type Executor: Executor; - - fn load(&self) -> Task<Self::Message>; - - fn update( - &self, - state: &mut Self::State, - message: Self::Message, - ) -> Task<Self::Message>; - - fn view<'a>( - &self, - state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; - - fn title(&self, _state: &Self::State) -> String { - String::from("A cool iced application!") - } - - fn subscription( - &self, - _state: &Self::State, - ) -> Subscription<Self::Message> { - Subscription::none() - } - - fn theme(&self, _state: &Self::State) -> Self::Theme { - Self::Theme::default() - } - - fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance { - DefaultStyle::default_style(theme) + ..crate::graphics::Settings::default() + }; + + Ok(shell::program::run::< + Instance<Self, I>, + <Self::Renderer as compositor::Default>::Compositor, + >( + Settings { + id: settings.id, + fonts: settings.fonts, + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + } + .into(), + renderer_settings, + window_settings, + (self, initialize), + )?) } } -fn with_title<P: Definition>( +pub fn with_title<P: Program>( program: P, - title: impl Title<P::State>, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { + title: impl Fn(&P::State, window::Id) -> String, +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithTitle<P, Title> { program: P, title: Title, } - impl<P, Title> Definition for WithTitle<P, Title> + impl<P, Title> Program for WithTitle<P, Title> where - P: Definition, - Title: self::Title<P::State>, + P: Program, + Title: Fn(&P::State, window::Id) -> String, { type State = P::State; type Message = P::Message; @@ -488,8 +216,8 @@ fn with_title<P: Definition>( self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.title.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + (self.title)(state, window) } fn update( @@ -503,12 +231,17 @@ fn with_title<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) } fn subscription( @@ -525,21 +258,25 @@ fn with_title<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithTitle { program, title } } -fn with_load<P: Definition>( +pub fn with_load<P: Program>( program: P, f: impl Fn() -> Task<P::Message>, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithLoad<P, F> { program: P, load: F, } - impl<P: Definition, F> Definition for WithLoad<P, F> + impl<P: Program, F> Program for WithLoad<P, F> where F: Fn() -> Task<P::Message>, { @@ -547,7 +284,7 @@ fn with_load<P: Definition>( type Message = P::Message; type Theme = P::Theme; type Renderer = P::Renderer; - type Executor = executor::Default; + type Executor = P::Executor; fn load(&self) -> Task<Self::Message> { Task::batch([self.program.load(), (self.load)()]) @@ -564,12 +301,13 @@ fn with_load<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } fn subscription( @@ -579,8 +317,12 @@ fn with_load<P: Definition>( self.program.subscription(state) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) } fn style( @@ -590,21 +332,25 @@ fn with_load<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithLoad { program, load: f } } -fn with_subscription<P: Definition>( +pub fn with_subscription<P: Program>( program: P, f: impl Fn(&P::State) -> Subscription<P::Message>, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithSubscription<P, F> { program: P, subscription: F, } - impl<P: Definition, F> Definition for WithSubscription<P, F> + impl<P: Program, F> Program for WithSubscription<P, F> where F: Fn(&P::State) -> Subscription<P::Message>, { @@ -612,7 +358,7 @@ fn with_subscription<P: Definition>( type Message = P::Message; type Theme = P::Theme; type Renderer = P::Renderer; - type Executor = executor::Default; + type Executor = P::Executor; fn subscription( &self, @@ -636,16 +382,21 @@ fn with_subscription<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) } fn style( @@ -655,6 +406,10 @@ fn with_subscription<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithSubscription { @@ -663,18 +418,18 @@ fn with_subscription<P: Definition>( } } -fn with_theme<P: Definition>( +pub fn with_theme<P: Program>( program: P, - f: impl Fn(&P::State) -> P::Theme, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { + f: impl Fn(&P::State, window::Id) -> P::Theme, +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithTheme<P, F> { program: P, theme: F, } - impl<P: Definition, F> Definition for WithTheme<P, F> + impl<P: Program, F> Program for WithTheme<P, F> where - F: Fn(&P::State) -> P::Theme, + F: Fn(&P::State, window::Id) -> P::Theme, { type State = P::State; type Message = P::Message; @@ -682,16 +437,20 @@ fn with_theme<P: Definition>( type Renderer = P::Renderer; type Executor = P::Executor; - fn theme(&self, state: &Self::State) -> Self::Theme { - (self.theme)(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + (self.theme)(state, window) } fn load(&self) -> Task<Self::Message> { self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } fn update( @@ -705,8 +464,9 @@ fn with_theme<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } fn subscription( @@ -723,21 +483,25 @@ fn with_theme<P: Definition>( ) -> Appearance { self.program.style(state, theme) } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) + } } WithTheme { program, theme: f } } -fn with_style<P: Definition>( +pub fn with_style<P: Program>( program: P, f: impl Fn(&P::State, &P::Theme) -> Appearance, -) -> impl Definition<State = P::State, Message = P::Message, Theme = P::Theme> { +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { struct WithStyle<P, F> { program: P, style: F, } - impl<P: Definition, F> Definition for WithStyle<P, F> + impl<P: Program, F> Program for WithStyle<P, F> where F: Fn(&P::State, &P::Theme) -> Appearance, { @@ -759,8 +523,8 @@ fn with_style<P: Definition>( self.program.load() } - fn title(&self, state: &Self::State) -> String { - self.program.title(state) + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) } fn update( @@ -774,8 +538,9 @@ fn with_style<P: Definition>( fn view<'a>( &self, state: &'a Self::State, + window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - self.program.view(state) + self.program.view(state, window) } fn subscription( @@ -785,91 +550,96 @@ fn with_style<P: Definition>( self.program.subscription(state) } - fn theme(&self, state: &Self::State) -> Self::Theme { - self.program.theme(state) + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) + } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + self.program.scale_factor(state, window) } } WithStyle { program, style: f } } -/// The title logic of some [`Program`]. -/// -/// This trait is implemented both for `&static str` and -/// any closure `Fn(&State) -> String`. -/// -/// This trait allows the [`program`] builder to take any of them. -pub trait Title<State> { - /// Produces the title of the [`Program`]. - fn title(&self, state: &State) -> String; -} - -impl<State> Title<State> for &'static str { - fn title(&self, _state: &State) -> String { - self.to_string() +pub fn with_scale_factor<P: Program>( + program: P, + f: impl Fn(&P::State, window::Id) -> f64, +) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> { + struct WithScaleFactor<P, F> { + program: P, + scale_factor: F, } -} -impl<T, State> Title<State> for T -where - T: Fn(&State) -> String, -{ - fn title(&self, state: &State) -> String { - self(state) - } -} + impl<P: Program, F> Program for WithScaleFactor<P, F> + where + F: Fn(&P::State, window::Id) -> f64, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; -/// The update logic of some [`Program`]. -/// -/// This trait allows the [`program`] builder to take any closure that -/// returns any `Into<Task<Message>>`. -pub trait Update<State, Message> { - /// Processes the message and updates the state of the [`Program`]. - fn update( - &self, - state: &mut State, - message: Message, - ) -> impl Into<Task<Message>>; -} + fn load(&self) -> Task<Self::Message> { + self.program.load() + } -impl<T, State, Message, C> Update<State, Message> for T -where - T: Fn(&mut State, Message) -> C, - C: Into<Task<Message>>, -{ - fn update( - &self, - state: &mut State, - message: Message, - ) -> impl Into<Task<Message>> { - self(state, message) - } -} + fn title(&self, state: &Self::State, window: window::Id) -> String { + self.program.title(state, window) + } -/// The view logic of some [`Program`]. -/// -/// This trait allows the [`program`] builder to take any closure that -/// returns any `Into<Element<'_, Message>>`. -pub trait View<'a, State, Message, Theme, Renderer> { - /// Produces the widget of the [`Program`]. - fn view( - &self, - state: &'a State, - ) -> impl Into<Element<'a, Message, Theme, Renderer>>; -} + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task<Self::Message> { + self.program.update(state, message) + } -impl<'a, T, State, Message, Theme, Renderer, Widget> - View<'a, State, Message, Theme, Renderer> for T -where - T: Fn(&'a State) -> Widget, - State: 'static, - Widget: Into<Element<'a, Message, Theme, Renderer>>, -{ - fn view( - &self, - state: &'a State, - ) -> impl Into<Element<'a, Message, Theme, Renderer>> { - self(state) + fn view<'a>( + &self, + state: &'a Self::State, + window: window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription<Self::Message> { + self.program.subscription(state) + } + + fn theme( + &self, + state: &Self::State, + window: window::Id, + ) -> Self::Theme { + self.program.theme(state, window) + } + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 { + (self.scale_factor)(state, window) + } + } + + WithScaleFactor { + program, + scale_factor: f, } } diff --git a/src/settings.rs b/src/settings.rs index f7947841..ebac7a86 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,30 +1,17 @@ //! Configure your application. -use crate::window; use crate::{Font, Pixels}; use std::borrow::Cow; -/// The settings of an iced [`Program`]. -/// -/// [`Program`]: crate::Program +/// The settings of an iced program. #[derive(Debug, Clone)] -pub struct Settings<Flags = ()> { +pub struct Settings { /// The identifier of the application. /// /// If provided, this identifier may be used to identify the application or /// communicate with it through the windowing system. pub id: Option<String>, - /// The window settings. - /// - /// They will be ignored on the Web. - pub window: window::Settings, - - /// The data needed to initialize the [`Program`]. - /// - /// [`Program`]: crate::Program - pub flags: Flags, - /// The fonts to load on boot. pub fonts: Vec<Cow<'static, [u8]>>, @@ -50,34 +37,10 @@ pub struct Settings<Flags = ()> { pub antialiasing: bool, } -impl<Flags> Settings<Flags> { - /// Initialize [`Program`] settings using the given data. - /// - /// [`Program`]: crate::Program - pub fn with_flags(flags: Flags) -> Self { - let default_settings = Settings::<()>::default(); - - Self { - flags, - id: default_settings.id, - window: default_settings.window, - fonts: default_settings.fonts, - default_font: default_settings.default_font, - default_text_size: default_settings.default_text_size, - antialiasing: default_settings.antialiasing, - } - } -} - -impl<Flags> Default for Settings<Flags> -where - Flags: Default, -{ +impl Default for Settings { fn default() -> Self { Self { id: None, - window: window::Settings::default(), - flags: Default::default(), fonts: Vec::new(), default_font: Font::default(), default_text_size: Pixels(16.0), @@ -86,12 +49,10 @@ where } } -impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> { - fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> { +impl From<Settings> for iced_winit::Settings { + fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { id: settings.id, - window: settings.window, - flags: settings.flags, fonts: settings.fonts, } } |