Allow custom renderers in Program and Application

This commit is contained in:
Héctor Ramón Jiménez 2024-03-22 07:09:51 +01:00
parent 4f2f40c68b
commit 5137d655e6
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
19 changed files with 162 additions and 122 deletions

View file

@ -2,16 +2,16 @@
use crate::core::image;
use crate::core::svg;
use crate::core::Size;
use crate::Mesh;
use crate::{Compositor, Mesh, Renderer};
use std::borrow::Cow;
/// The graphics backend of a [`Renderer`].
///
/// [`Renderer`]: crate::Renderer
pub trait Backend {
pub trait Backend: Sized {
/// The compositor of this [`Backend`].
type Compositor;
type Compositor: Compositor<Renderer = Renderer<Self>>;
/// The custom kind of primitives this [`Backend`] supports.
type Primitive: TryFrom<Mesh, Error = &'static str>;

View file

@ -1,9 +1,9 @@
//! A compositor is responsible for initializing a renderer and managing window
//! surfaces.
use crate::{Error, Viewport};
use crate::core;
use crate::core::Color;
use crate::futures::{MaybeSend, MaybeSync};
use crate::{Error, Settings, Viewport};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::future::Future;
@ -11,9 +11,6 @@ use thiserror::Error;
/// A graphics compositor that can draw to windows.
pub trait Compositor: Sized {
/// The settings of the backend.
type Settings: Default;
/// The iced renderer of the backend.
type Renderer;
@ -22,7 +19,7 @@ pub trait Compositor: Sized {
/// Creates a new [`Compositor`].
fn new<W: Window + Clone>(
settings: Self::Settings,
settings: Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>>;
@ -93,6 +90,12 @@ impl<T> Window for T where
{
}
/// A renderer that supports composition.
pub trait Renderer: core::Renderer {
/// The compositor of the renderer.
type Compositor: Compositor<Renderer = Self>;
}
/// Result of an unsuccessful call to [`Compositor::present`].
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum SurfaceError {
@ -123,13 +126,13 @@ pub struct Information {
pub backend: String,
}
#[cfg(debug_assertions)]
impl Compositor for () {
type Settings = ();
type Renderer = ();
type Surface = ();
async fn new<W: Window + Clone>(
_settings: Self::Settings,
_settings: Settings,
_compatible_window: W,
) -> Result<Self, Error> {
Ok(())
@ -182,3 +185,8 @@ impl Compositor for () {
vec![]
}
}
#[cfg(debug_assertions)]
impl Renderer for () {
type Compositor = ();
}

View file

@ -20,6 +20,7 @@ mod antialiasing;
mod cached;
mod error;
mod primitive;
mod settings;
mod viewport;
pub mod backend;
@ -47,6 +48,7 @@ pub use gradient::Gradient;
pub use mesh::Mesh;
pub use primitive::Primitive;
pub use renderer::Renderer;
pub use settings::Settings;
pub use viewport::Viewport;
pub use iced_core as core;

View file

@ -1,5 +1,6 @@
//! Create a renderer from a [`Backend`].
use crate::backend::{self, Backend};
use crate::compositor;
use crate::core;
use crate::core::image;
use crate::core::renderer;
@ -259,3 +260,10 @@ where
self.draw_primitive(geometry);
}
}
impl<B> compositor::Renderer for Renderer<B>
where
B: Backend,
{
type Compositor = B::Compositor;
}

29
graphics/src/settings.rs Normal file
View file

@ -0,0 +1,29 @@
use crate::core::{Font, Pixels};
use crate::Antialiasing;
/// The settings of a Backend.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The default [`Font`] to use.
pub default_font: Font,
/// The default size of text.
///
/// By default, it will be set to `16.0`.
pub default_text_size: Pixels,
/// The antialiasing strategy that will be used for triangle primitives.
///
/// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
default_font: Font::default(),
default_text_size: Pixels(16.0),
antialiasing: None,
}
}
}

View file

@ -200,15 +200,12 @@ impl<L, R> graphics::Compositor for Compositor<L, R>
where
L: graphics::Compositor,
R: graphics::Compositor,
L::Settings: From<crate::Settings>,
R::Settings: From<crate::Settings>,
{
type Settings = crate::Settings;
type Renderer = Renderer<L::Renderer, R::Renderer>;
type Surface = Surface<L::Surface, R::Surface>;
async fn new<W: compositor::Window + Clone>(
settings: Self::Settings,
settings: graphics::Settings,
compatible_window: W,
) -> Result<Self, graphics::Error> {
if let Ok(left) = L::new(settings.into(), compatible_window.clone())
@ -528,3 +525,11 @@ mod geometry {
}
}
}
impl<L, R> compositor::Renderer for Renderer<L, R>
where
L: compositor::Renderer,
R: compositor::Renderer,
{
type Compositor = Compositor<L::Compositor, R::Compositor>;
}

View file

@ -6,16 +6,12 @@ pub use iced_wgpu as wgpu;
pub mod fallback;
mod settings;
pub use iced_graphics as graphics;
pub use iced_graphics::core;
#[cfg(feature = "geometry")]
pub use iced_graphics::geometry;
pub use settings::Settings;
/// The default graphics renderer for [`iced`].
///
/// [`iced`]: https://github.com/iced-rs/iced

View file

@ -1,55 +0,0 @@
use crate::core::{Font, Pixels};
use crate::graphics::Antialiasing;
/// The settings of a Backend.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The default [`Font`] to use.
pub default_font: Font,
/// The default size of text.
///
/// By default, it will be set to `16.0`.
pub default_text_size: Pixels,
/// The antialiasing strategy that will be used for triangle primitives.
///
/// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
default_font: Font::default(),
default_text_size: Pixels(16.0),
antialiasing: None,
}
}
}
#[cfg(feature = "tiny-skia")]
impl From<Settings> for iced_tiny_skia::Settings {
fn from(settings: Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
}
#[cfg(feature = "wgpu")]
impl From<Settings> for iced_wgpu::Settings {
fn from(settings: Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: settings.antialiasing,
..iced_wgpu::Settings::default()
}
}
}
impl From<Settings> for () {
fn from(_settings: Settings) -> Self {}
}

View file

@ -2,7 +2,7 @@
use crate::Command;
use iced_core::text;
use iced_core::{Element, Renderer};
use iced_core::Element;
mod state;
@ -11,7 +11,7 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`].
type Renderer: Renderer + text::Renderer;
type Renderer: text::Renderer;
/// The theme used to draw the [`Program`].
type Theme;

View file

@ -1,4 +1,6 @@
//! Build interactive cross-platform applications.
use crate::core::text;
use crate::graphics::compositor;
use crate::shell::application;
use crate::{Command, Element, Executor, Settings, Subscription};
@ -60,7 +62,7 @@ pub use application::{Appearance, DefaultStyle};
/// ```no_run
/// use iced::advanced::Application;
/// use iced::executor;
/// use iced::{Command, Element, Settings, Theme};
/// use iced::{Command, Element, Settings, Theme, Renderer};
///
/// pub fn main() -> iced::Result {
/// Hello::run(Settings::default())
@ -73,6 +75,7 @@ pub use application::{Appearance, DefaultStyle};
/// type Flags = ();
/// type Message = ();
/// type Theme = Theme;
/// type Renderer = Renderer;
///
/// fn new(_flags: ()) -> (Hello, Command<Self::Message>) {
/// (Hello, Command::none())
@ -109,6 +112,9 @@ where
/// The theme of your [`Application`].
type Theme: Default;
/// The renderer of your [`Application`].
type Renderer: text::Renderer + compositor::Renderer;
/// The data needed to initialize your [`Application`].
type Flags;
@ -142,7 +148,7 @@ where
/// 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, crate::Renderer>;
fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>;
/// Returns the current [`Theme`] of the [`Application`].
///
@ -195,7 +201,7 @@ where
Self: 'static,
{
#[allow(clippy::needless_update)]
let renderer_settings = crate::renderer::Settings {
let renderer_settings = crate::graphics::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
@ -203,13 +209,13 @@ where
} else {
None
},
..crate::renderer::Settings::default()
..crate::graphics::Settings::default()
};
let run = crate::shell::application::run::<
Instance<Self>,
Self::Executor,
crate::renderer::Compositor,
<Self::Renderer as compositor::Renderer>::Compositor,
>(settings.into(), renderer_settings);
#[cfg(target_arch = "wasm32")]
@ -241,7 +247,7 @@ where
{
type Message = A::Message;
type Theme = A::Theme;
type Renderer = crate::Renderer;
type Renderer = A::Renderer;
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
self.0.update(message)

View file

@ -372,15 +372,16 @@ pub type Result = std::result::Result<(), Error>;
/// ]
/// }
/// ```
pub fn run<State, Message, Theme>(
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> + 'static,
view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static,
) -> Result
where
State: Default + 'static,
Message: std::fmt::Debug + Send + 'static,
Theme: Default + program::DefaultStyle + 'static,
Renderer: graphics::compositor::Renderer + core::text::Renderer + 'static,
{
program(title, update, view).run()
}

View file

@ -174,7 +174,7 @@ where
Self: 'static,
{
#[allow(clippy::needless_update)]
let renderer_settings = crate::renderer::Settings {
let renderer_settings = crate::graphics::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
@ -182,7 +182,7 @@ where
} else {
None
},
..crate::renderer::Settings::default()
..crate::graphics::Settings::default()
};
Ok(crate::shell::multi_window::run::<

View file

@ -32,6 +32,7 @@
//! ```
use crate::application::Application;
use crate::executor::{self, Executor};
use crate::graphics::compositor;
use crate::window;
use crate::{Command, Element, Font, Result, Settings, Size, Subscription};
@ -67,37 +68,41 @@ use std::borrow::Cow;
/// ]
/// }
/// ```
pub fn program<State, Message, Theme>(
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>,
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,
Theme: Default + DefaultStyle,
Renderer: compositor::Renderer + crate::core::text::Renderer,
{
use std::marker::PhantomData;
struct Application<State, Message, Theme, Update, View> {
struct Application<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, Update, View> Definition
for Application<State, Message, Theme, Update, View>
impl<State, Message, Theme, Renderer, Update, View> Definition
for Application<State, Message, Theme, Renderer, Update, View>
where
Message: Send + std::fmt::Debug,
Theme: Default + DefaultStyle,
Renderer: compositor::Renderer + crate::core::text::Renderer,
Update: self::Update<State, Message>,
View: for<'a> self::View<'a, State, Message, Theme>,
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;
fn load(&self) -> Command<Self::Message> {
@ -115,7 +120,7 @@ where
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.view.view(state).into()
}
}
@ -127,6 +132,7 @@ where
_state: PhantomData,
_message: PhantomData,
_theme: PhantomData,
_renderer: PhantomData,
},
settings: Settings::default(),
}
@ -184,6 +190,7 @@ impl<P: Definition> Program<P> {
impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> {
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Flags = (P, I);
type Executor = P::Executor;
@ -216,7 +223,7 @@ impl<P: Definition> Program<P> {
fn view(
&self,
) -> crate::Element<'_, Self::Message, Self::Theme, crate::Renderer>
) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer>
{
self.program.view(&self.state)
}
@ -417,6 +424,9 @@ pub trait Definition: Sized {
/// The theme of the program.
type Theme: Default + DefaultStyle;
/// The renderer of the program.
type Renderer: compositor::Renderer + crate::core::text::Renderer;
/// The executor of the program.
type Executor: Executor;
@ -431,7 +441,7 @@ pub trait Definition: Sized {
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme>;
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>;
fn title(&self, _state: &Self::State) -> String {
String::from("A cool iced application!")
@ -470,6 +480,7 @@ fn with_title<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = P::Executor;
fn load(&self) -> Command<Self::Message> {
@ -491,7 +502,7 @@ fn with_title<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -534,6 +545,7 @@ fn with_load<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = executor::Default;
fn load(&self) -> Command<Self::Message> {
@ -551,7 +563,7 @@ fn with_load<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -598,6 +610,7 @@ fn with_subscription<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = executor::Default;
fn subscription(
@ -622,7 +635,7 @@ fn with_subscription<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -665,6 +678,7 @@ fn with_theme<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = P::Executor;
fn theme(&self, state: &Self::State) -> Self::Theme {
@ -690,7 +704,7 @@ fn with_theme<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -729,6 +743,7 @@ fn with_style<P: Definition>(
type State = P::State;
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Executor = P::Executor;
fn style(
@ -758,7 +773,7 @@ fn with_style<P: Definition>(
fn view<'a>(
&self,
state: &'a Self::State,
) -> Element<'a, Self::Message, Self::Theme> {
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
self.program.view(state)
}
@ -834,18 +849,25 @@ where
///
/// This trait allows the [`program`] builder to take any closure that
/// returns any `Into<Element<'_, Message>>`.
pub trait View<'a, State, Message, Theme> {
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>>;
fn view(
&self,
state: &'a State,
) -> impl Into<Element<'a, Message, Theme, Renderer>>;
}
impl<'a, T, State, Message, Theme, Widget> View<'a, State, Message, Theme> for T
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>>,
Widget: Into<Element<'a, Message, Theme, Renderer>>,
{
fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>> {
fn view(
&self,
state: &'a State,
) -> impl Into<Element<'a, Message, Theme, Renderer>> {
self(state)
}
}

View file

@ -1,4 +1,5 @@
use crate::core::{Font, Pixels};
use crate::graphics;
/// The settings of a [`Backend`].
///
@ -22,3 +23,12 @@ impl Default for Settings {
}
}
}
impl From<graphics::Settings> for Settings {
fn from(settings: graphics::Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
}

View file

@ -1,7 +1,7 @@
use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::{Error, Viewport};
use crate::graphics::{self, Error, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use std::collections::VecDeque;
@ -25,15 +25,14 @@ pub struct Surface {
}
impl crate::graphics::Compositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
type Surface = Surface;
fn new<W: compositor::Window>(
settings: Self::Settings,
settings: graphics::Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> {
future::ready(Ok(new(settings, compatible_window)))
future::ready(Ok(new(settings.into(), compatible_window)))
}
fn create_renderer(&self) -> Self::Renderer {

View file

@ -1,6 +1,6 @@
//! Configure a renderer.
use crate::core::{Font, Pixels};
use crate::graphics::Antialiasing;
use crate::graphics::{self, Antialiasing};
/// The settings of a [`Backend`].
///
@ -64,3 +64,14 @@ impl Default for Settings {
}
}
}
impl From<graphics::Settings> for Settings {
fn from(settings: graphics::Settings) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: settings.antialiasing,
..Settings::default()
}
}
}

View file

@ -1,9 +1,8 @@
//! Connect a window with a renderer.
use crate::core::{Color, Size};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::{Error, Viewport};
use crate::graphics::{self, Error, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use std::future::Future;
@ -225,15 +224,14 @@ pub fn present<T: AsRef<str>>(
}
impl graphics::Compositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
type Surface = wgpu::Surface<'static>;
fn new<W: compositor::Window>(
settings: Self::Settings,
settings: graphics::Settings,
compatible_window: W,
) -> impl Future<Output = Result<Self, Error>> {
new(settings, compatible_window)
new(settings.into(), compatible_window)
}
fn create_renderer(&self) -> Self::Renderer {

View file

@ -13,6 +13,7 @@ use crate::core::window;
use crate::core::{Color, Event, Point, Size, Theme};
use crate::futures::futures;
use crate::futures::{Executor, Runtime, Subscription};
use crate::graphics;
use crate::graphics::compositor::{self, Compositor};
use crate::runtime::clipboard;
use crate::runtime::program::Program;
@ -130,7 +131,7 @@ pub fn default(theme: &Theme) -> Appearance {
/// settings.
pub async fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: impl Into<C::Settings>,
graphics_settings: graphics::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
@ -219,7 +220,7 @@ where
};
}
let compositor = C::new(compositor_settings.into(), window.clone()).await?;
let compositor = C::new(graphics_settings, window.clone()).await?;
let mut renderer = compositor.create_renderer();
for font in settings.fonts {

View file

@ -16,6 +16,7 @@ use crate::futures::futures::executor;
use crate::futures::futures::task;
use crate::futures::futures::{Future, StreamExt};
use crate::futures::{Executor, Runtime, Subscription};
use crate::graphics;
use crate::graphics::{compositor, Compositor};
use crate::multi_window::window_manager::WindowManager;
use crate::runtime::command::{self, Command};
@ -105,7 +106,7 @@ where
/// settings.
pub fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: impl Into<C::Settings>,
graphics_settings: graphics::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
@ -186,10 +187,8 @@ where
};
}
let mut compositor = executor::block_on(C::new(
compositor_settings.into(),
main_window.clone(),
))?;
let mut compositor =
executor::block_on(C::new(graphics_settings, main_window.clone()))?;
let mut window_manager = WindowManager::new();
let _ = window_manager.insert(