summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Cargo.toml6
-rw-r--r--core/Cargo.toml1
-rw-r--r--core/src/element.rs4
-rw-r--r--core/src/image.rs4
-rw-r--r--core/src/renderer.rs33
-rw-r--r--core/src/renderer/null.rs58
-rw-r--r--core/src/size.rs2
-rw-r--r--core/src/svg.rs9
-rw-r--r--core/src/widget/text.rs117
-rw-r--r--examples/bezier_tool/src/main.rs13
-rw-r--r--examples/component/src/main.rs10
-rw-r--r--examples/game_of_life/src/main.rs6
-rw-r--r--examples/geometry/src/main.rs4
-rw-r--r--examples/gradient/src/main.rs2
-rw-r--r--examples/layout/src/main.rs8
-rw-r--r--examples/loading_spinners/src/circular.rs2
-rw-r--r--examples/pane_grid/src/main.rs28
-rw-r--r--examples/scrollable/src/main.rs4
-rw-r--r--examples/sierpinski_triangle/src/main.rs4
-rw-r--r--examples/solar_system/src/main.rs4
-rw-r--r--examples/svg/src/main.rs2
-rw-r--r--examples/toast/src/main.rs24
-rw-r--r--graphics/src/backend.rs8
-rw-r--r--graphics/src/cached.rs42
-rw-r--r--graphics/src/compositor.rs94
-rw-r--r--graphics/src/error.rs27
-rw-r--r--graphics/src/geometry.rs40
-rw-r--r--graphics/src/geometry/cache.rs108
-rw-r--r--graphics/src/geometry/frame.rs251
-rw-r--r--graphics/src/lib.rs6
-rw-r--r--graphics/src/mesh.rs6
-rw-r--r--graphics/src/renderer.rs111
-rw-r--r--graphics/src/settings.rs (renamed from renderer/src/settings.rs)2
-rw-r--r--renderer/Cargo.toml9
-rw-r--r--renderer/src/compositor.rs269
-rw-r--r--renderer/src/fallback.rs570
-rw-r--r--renderer/src/geometry.rs210
-rw-r--r--renderer/src/geometry/cache.rs125
-rw-r--r--renderer/src/lib.rs306
-rw-r--r--runtime/src/program.rs4
-rw-r--r--runtime/src/user_interface.rs12
-rw-r--r--src/application.rs18
-rw-r--r--src/lib.rs5
-rw-r--r--src/multi_window.rs4
-rw-r--r--src/program.rs66
-rw-r--r--tiny_skia/src/backend.rs13
-rw-r--r--tiny_skia/src/geometry.rs58
-rw-r--r--tiny_skia/src/primitive.rs10
-rw-r--r--tiny_skia/src/settings.rs10
-rw-r--r--tiny_skia/src/window/compositor.rs24
-rw-r--r--wgpu/Cargo.toml1
-rw-r--r--wgpu/src/backend.rs13
-rw-r--r--wgpu/src/geometry.rs492
-rw-r--r--wgpu/src/primitive.rs8
-rw-r--r--wgpu/src/primitive/pipeline.rs4
-rw-r--r--wgpu/src/settings.rs37
-rw-r--r--wgpu/src/window/compositor.rs146
-rw-r--r--widget/Cargo.toml1
-rw-r--r--widget/src/button.rs143
-rw-r--r--widget/src/canvas.rs30
-rw-r--r--widget/src/canvas/program.rs5
-rw-r--r--widget/src/checkbox.rs99
-rw-r--r--widget/src/combo_box.rs130
-rw-r--r--widget/src/container.rs170
-rw-r--r--widget/src/helpers.rs39
-rw-r--r--widget/src/image.rs10
-rw-r--r--widget/src/image/viewer.rs7
-rw-r--r--widget/src/overlay/menu.rs177
-rw-r--r--widget/src/pane_grid.rs108
-rw-r--r--widget/src/pane_grid/content.rs59
-rw-r--r--widget/src/pane_grid/title_bar.rs64
-rw-r--r--widget/src/pick_list.rs106
-rw-r--r--widget/src/progress_bar.rs103
-rw-r--r--widget/src/qr_code.rs101
-rw-r--r--widget/src/radio.rs87
-rw-r--r--widget/src/rule.rs109
-rw-r--r--widget/src/scrollable.rs131
-rw-r--r--widget/src/slider.rs93
-rw-r--r--widget/src/svg.rs97
-rw-r--r--widget/src/text_editor.rs94
-rw-r--r--widget/src/text_input.rs106
-rw-r--r--widget/src/themer.rs4
-rw-r--r--widget/src/toggler.rs85
-rw-r--r--widget/src/tooltip.rs41
-rw-r--r--widget/src/vertical_slider.rs52
-rw-r--r--winit/src/application.rs5
-rw-r--r--winit/src/multi_window.rs5
87 files changed, 3221 insertions, 2494 deletions
diff --git a/Cargo.toml b/Cargo.toml
index b82c0f67..e2639944 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,9 +18,11 @@ all-features = true
maintenance = { status = "actively-developed" }
[features]
-default = ["wgpu", "fira-sans", "auto-detect-theme"]
+default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
+# Enable the `tiny-skia` software renderer backend
+tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget
image = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget
@@ -50,7 +52,7 @@ highlighter = ["iced_highlighter"]
# Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"]
# Enables the advanced module
-advanced = []
+advanced = ["iced_core/advanced", "iced_widget/advanced"]
# Enables embedding Fira Sans as the default font on Wasm builds
fira-sans = ["iced_renderer/fira-sans"]
# Enables auto-detecting light/dark mode for the built-in theme
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 29a95ad7..32d233ee 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -12,6 +12,7 @@ keywords.workspace = true
[features]
auto-detect-theme = ["dep:dark-light"]
+advanced = []
[dependencies]
bitflags.workspace = true
diff --git a/core/src/element.rs b/core/src/element.rs
index 989eaa3b..7d918a2e 100644
--- a/core/src/element.rs
+++ b/core/src/element.rs
@@ -95,7 +95,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
///
/// ```no_run
/// # mod iced {
- /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
+ /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # pub mod widget {
/// # pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> {
@@ -109,7 +109,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// # pub enum Message {}
/// # pub struct Counter;
/// #
- /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>;
+ /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # impl Counter {
/// # pub fn view(&self) -> Element<Message> {
diff --git a/core/src/image.rs b/core/src/image.rs
index e5fdcd83..32b95f03 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -186,11 +186,11 @@ pub trait Renderer: crate::Renderer {
type Handle: Clone + Hash;
/// Returns the dimensions of an image for the given [`Handle`].
- fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
+ fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
- fn draw(
+ fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 1139b41c..6712314e 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -2,26 +2,47 @@
#[cfg(debug_assertions)]
mod null;
-#[cfg(debug_assertions)]
-pub use null::Null;
-
use crate::{
Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector,
};
/// A component that can be used by widgets to draw themselves on a screen.
-pub trait Renderer: Sized {
+pub trait Renderer {
+ /// Starts recording a new layer.
+ fn start_layer(&mut self);
+
+ /// Ends recording a new layer.
+ ///
+ /// The new layer will clip its contents to the provided `bounds`.
+ fn end_layer(&mut self, bounds: Rectangle);
+
/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.
- fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
+ fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
+ self.start_layer();
+ f(self);
+ self.end_layer(bounds);
+ }
+
+ /// Starts recording with a new [`Transformation`].
+ fn start_transformation(&mut self);
+
+ /// Ends recording a new layer.
+ ///
+ /// The new layer will clip its contents to the provided `bounds`.
+ fn end_transformation(&mut self, transformation: Transformation);
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
fn with_transformation(
&mut self,
transformation: Transformation,
f: impl FnOnce(&mut Self),
- );
+ ) {
+ self.start_transformation();
+ f(self);
+ self.end_transformation(transformation);
+ }
/// Applies a translation to the primitives recorded in the given closure.
fn with_translation(
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 83688ff7..c26ce1a5 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,5 +1,7 @@
use crate::alignment;
+use crate::image;
use crate::renderer::{self, Renderer};
+use crate::svg;
use crate::text::{self, Text};
use crate::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
@@ -7,28 +9,14 @@ use crate::{
use std::borrow::Cow;
-/// A renderer that does nothing.
-///
-/// It can be useful if you are writing tests!
-#[derive(Debug, Clone, Copy, Default)]
-pub struct Null;
+impl Renderer for () {
+ fn start_layer(&mut self) {}
-impl Null {
- /// Creates a new [`Null`] renderer.
- pub fn new() -> Null {
- Null
- }
-}
+ fn end_layer(&mut self, _bounds: Rectangle) {}
-impl Renderer for Null {
- fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
+ fn start_transformation(&mut self) {}
- fn with_transformation(
- &mut self,
- _transformation: Transformation,
- _f: impl FnOnce(&mut Self),
- ) {
- }
+ fn end_transformation(&mut self, _transformation: Transformation) {}
fn clear(&mut self) {}
@@ -40,7 +28,7 @@ impl Renderer for Null {
}
}
-impl text::Renderer for Null {
+impl text::Renderer for () {
type Font = Font;
type Paragraph = ();
type Editor = ();
@@ -174,3 +162,33 @@ impl text::Editor for () {
) {
}
}
+
+impl image::Renderer for () {
+ type Handle = ();
+
+ fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
+ Size::default()
+ }
+
+ fn draw_image(
+ &mut self,
+ _handle: Self::Handle,
+ _filter_method: image::FilterMethod,
+ _bounds: Rectangle,
+ ) {
+ }
+}
+
+impl svg::Renderer for () {
+ fn measure_svg(&self, _handle: &svg::Handle) -> Size<u32> {
+ Size::default()
+ }
+
+ fn draw_svg(
+ &mut self,
+ _handle: svg::Handle,
+ _color: Option<Color>,
+ _bounds: Rectangle,
+ ) {
+ }
+}
diff --git a/core/src/size.rs b/core/src/size.rs
index 267fc90e..55db759d 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -1,7 +1,7 @@
use crate::Vector;
/// An amount of space in 2 dimensions.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Size<T = f32> {
/// The width.
pub width: T,
diff --git a/core/src/svg.rs b/core/src/svg.rs
index d63e3c95..ab207cca 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -91,8 +91,13 @@ impl std::fmt::Debug for Data {
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an SVG for the given [`Handle`].
- fn dimensions(&self, handle: &Handle) -> Size<u32>;
+ fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
- fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
+ fn draw_svg(
+ &mut self,
+ handle: Handle,
+ color: Option<Color>,
+ bounds: Rectangle,
+ );
}
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index 66e2d066..12f6956a 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -18,6 +18,7 @@ pub use text::{LineHeight, Shaping};
#[allow(missing_debug_implementations)]
pub struct Text<'a, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
content: Cow<'a, str>,
@@ -29,18 +30,16 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Create a new fragment of [`Text`] with the given contents.
- pub fn new(content: impl Into<Cow<'a, str>>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
Text {
content: content.into(),
size: None,
@@ -51,7 +50,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -75,25 +74,6 @@ where
self
}
- /// Sets the style of the [`Text`].
- pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self {
- self.style = Box::new(style);
- self
- }
-
- /// Sets the [`Color`] of the [`Text`].
- pub fn color(self, color: impl Into<Color>) -> Self {
- self.color_maybe(Some(color))
- }
-
- /// Sets the [`Color`] of the [`Text`], if `Some`.
- pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
- let color = color.map(Into::into);
-
- self.style = Box::new(move |_theme| Appearance { color });
- self
- }
-
/// Sets the width of the [`Text`] boundaries.
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
@@ -129,6 +109,42 @@ where
self.shaping = shaping;
self
}
+
+ /// Sets the style of the [`Text`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the [`Color`] of the [`Text`].
+ pub fn color(self, color: impl Into<Color>) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.color_maybe(Some(color))
+ }
+
+ /// Sets the [`Color`] of the [`Text`], if `Some`.
+ pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ let color = color.map(Into::into);
+
+ self.style(move |_theme| Style { color })
+ }
+
+ /// Sets the style class of the [`Text`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
/// The internal state of a [`Text`] widget.
@@ -138,6 +154,7 @@ pub struct State<P: Paragraph>(P);
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Text<'a, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@@ -182,15 +199,15 @@ where
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
_cursor_position: mouse::Cursor,
viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
- let appearance = (self.style)(theme);
+ let style = theme.style(&self.class);
- draw(renderer, style, layout, state, appearance, viewport);
+ draw(renderer, defaults, layout, state, style, viewport);
}
}
@@ -250,7 +267,7 @@ pub fn draw<Renderer>(
style: &renderer::Style,
layout: Layout<'_>,
state: &State<Renderer::Paragraph>,
- appearance: Appearance,
+ appearance: Style,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
@@ -281,7 +298,7 @@ pub fn draw<Renderer>(
impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -293,7 +310,7 @@ where
impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
where
- Theme: DefaultStyle + 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer,
{
fn from(content: &'a str) -> Self {
@@ -304,7 +321,7 @@ where
impl<'a, Message, Theme, Renderer> From<&'a str>
for Element<'a, Message, Theme, Renderer>
where
- Theme: DefaultStyle + 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(content: &'a str) -> Self {
@@ -314,30 +331,38 @@ where
/// The appearance of some text.
#[derive(Debug, Clone, Copy, Default)]
-pub struct Appearance {
+pub struct Style {
/// The [`Color`] of the text.
///
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}
-/// The style of some [`Text`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>;
+/// The theme catalog of a [`Text`].
+pub trait Catalog: Sized {
+ /// The item class of this [`Catalog`].
+ type Class<'a>;
-/// The default style of some [`Text`].
-pub trait DefaultStyle {
- /// Returns the default style of some [`Text`].
- fn default_style(&self) -> Appearance;
+ /// The default class produced by this [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, item: &Self::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self) -> Appearance {
- Appearance::default()
+/// A styling function for a [`Text`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(|_theme| Style::default())
}
-}
-impl DefaultStyle for Color {
- fn default_style(&self) -> Appearance {
- Appearance { color: Some(*self) }
+ fn style(&self, class: &Self::Class<'_>) -> Style {
+ class(self)
}
}
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index cf70bd40..289c919b 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -143,23 +143,18 @@ mod bezier {
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
- let content = self.state.cache.draw(
- renderer,
- bounds.size(),
- |frame: &mut Frame| {
+ let content =
+ self.state.cache.draw(renderer, bounds.size(), |frame| {
Curve::draw_all(self.curves, frame);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
Stroke::default().with_width(2.0),
);
- },
- );
+ });
if let Some(pending) = state {
- let pending_curve = pending.draw(renderer, bounds, cursor);
-
- vec![content, pending_curve]
+ vec![content, pending.draw(renderer, bounds, cursor)]
} else {
vec![content]
}
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
index 43ba3187..b2c71b3f 100644
--- a/examples/component/src/main.rs
+++ b/examples/component/src/main.rs
@@ -73,10 +73,7 @@ mod numeric_input {
impl<Message, Theme> Component<Message, Theme> for NumericInput<Message>
where
- Theme: text::DefaultStyle
- + button::DefaultStyle
- + text_input::DefaultStyle
- + 'static,
+ Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
{
type State = ();
type Event = Event;
@@ -151,10 +148,7 @@ mod numeric_input {
impl<'a, Message, Theme> From<NumericInput<Message>>
for Element<'a, Message, Theme>
where
- Theme: text::DefaultStyle
- + button::DefaultStyle
- + text_input::DefaultStyle
- + 'static,
+ Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
Message: 'a,
{
fn from(numeric_input: NumericInput<Message>) -> Self {
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index 2b0fae0b..0716b2a4 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -602,9 +602,7 @@ mod grid {
frame.into_geometry()
};
- if self.scaling < 0.2 || !self.show_lines {
- vec![life, overlay]
- } else {
+ if self.scaling >= 0.2 && self.show_lines {
let grid =
self.grid_cache.draw(renderer, bounds.size(), |frame| {
frame.translate(center);
@@ -641,6 +639,8 @@ mod grid {
});
vec![life, grid, overlay]
+ } else {
+ vec![life, overlay]
}
}
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 63efcbdd..16cdb86f 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -44,7 +44,9 @@ mod rainbow {
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
+ use iced::advanced::graphics::mesh::{
+ self, Mesh, Renderer as _, SolidVertex2D,
+ };
use iced::advanced::Renderer as _;
let bounds = layout.bounds();
diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs
index 22c21cdd..2b906c32 100644
--- a/examples/gradient/src/main.rs
+++ b/examples/gradient/src/main.rs
@@ -60,7 +60,7 @@ impl Gradient {
} = *self;
let gradient_box = container(horizontal_space())
- .style(move |_theme, _status| {
+ .style(move |_theme| {
let gradient = gradient::Linear::new(angle)
.add_stop(0.0, start)
.add_stop(1.0, end);
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index 713e2b70..66d79091 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -81,10 +81,10 @@ impl Layout {
} else {
self.example.view()
})
- .style(|theme, _status| {
+ .style(|theme| {
let palette = theme.extended_palette();
- container::Appearance::default()
+ container::Style::default()
.with_border(palette.background.strong.color, 4.0)
})
.padding(4)
@@ -245,10 +245,10 @@ fn application<'a>() -> Element<'a, Message> {
.padding(10)
.align_items(Alignment::Center),
)
- .style(|theme, _status| {
+ .style(|theme| {
let palette = theme.extended_palette();
- container::Appearance::default()
+ container::Style::default()
.with_border(palette.background.strong.color, 1)
});
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
index 12670ed1..de728af2 100644
--- a/examples/loading_spinners/src/circular.rs
+++ b/examples/loading_spinners/src/circular.rs
@@ -358,7 +358,7 @@ where
|renderer| {
use iced::advanced::graphics::geometry::Renderer as _;
- renderer.draw(vec![geometry]);
+ renderer.draw_geometry(geometry);
},
);
}
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index 829996d8..17112785 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -338,39 +338,30 @@ mod style {
use iced::widget::container;
use iced::{Border, Theme};
- pub fn title_bar_active(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ pub fn title_bar_active(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
- container::Appearance {
+ container::Style {
text_color: Some(palette.background.strong.text),
background: Some(palette.background.strong.color.into()),
..Default::default()
}
}
- pub fn title_bar_focused(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ pub fn title_bar_focused(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
- container::Appearance {
+ container::Style {
text_color: Some(palette.primary.strong.text),
background: Some(palette.primary.strong.color.into()),
..Default::default()
}
}
- pub fn pane_active(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ pub fn pane_active(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
- container::Appearance {
+ container::Style {
background: Some(palette.background.weak.color.into()),
border: Border {
width: 2.0,
@@ -381,13 +372,10 @@ mod style {
}
}
- pub fn pane_focused(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ pub fn pane_focused(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
- container::Appearance {
+ container::Style {
background: Some(palette.background.weak.color.into()),
border: Border {
width: 2.0,
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 240ae908..c02e754b 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -341,8 +341,8 @@ impl Default for ScrollableDemo {
}
}
-fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
- progress_bar::Appearance {
+fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Style {
+ progress_bar::Style {
background: theme.extended_palette().background.strong.color.into(),
bar: Color::from_rgb8(250, 85, 134).into(),
border: Border::default(),
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index 07ae05d6..9cd6237f 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -1,6 +1,6 @@
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
-use iced::widget::canvas::{self, Canvas};
+use iced::widget::canvas::{self, Canvas, Geometry};
use iced::widget::{column, row, slider, text};
use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme};
@@ -111,7 +111,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
- ) -> Vec<canvas::Geometry> {
+ ) -> Vec<Geometry> {
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
frame.stroke(
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index b5228f09..deb211d8 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -10,7 +10,7 @@ use iced::mouse;
use iced::widget::canvas;
use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::Path;
+use iced::widget::canvas::{Geometry, Path};
use iced::window;
use iced::{
Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription,
@@ -130,7 +130,7 @@ impl<Message> canvas::Program<Message> for State {
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
- ) -> Vec<canvas::Geometry> {
+ ) -> Vec<Geometry> {
use std::f32::consts::PI;
let background =
diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs
index cc686dca..0dcf9bc1 100644
--- a/examples/svg/src/main.rs
+++ b/examples/svg/src/main.rs
@@ -31,7 +31,7 @@ impl Tiger {
));
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style(
- |_theme, _status| svg::Appearance {
+ |_theme, _status| svg::Style {
color: if self.apply_color_filter {
Some(color!(0x0000ff))
} else {
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index fdae1dc1..9968962c 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -651,45 +651,33 @@ mod toast {
}
}
- fn styled(pair: theme::palette::Pair) -> container::Appearance {
- container::Appearance {
+ fn styled(pair: theme::palette::Pair) -> container::Style {
+ container::Style {
background: Some(pair.color.into()),
text_color: pair.text.into(),
..Default::default()
}
}
- fn primary(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ fn primary(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
styled(palette.primary.weak)
}
- fn secondary(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ fn secondary(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
styled(palette.secondary.weak)
}
- fn success(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ fn success(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
styled(palette.success.weak)
}
- fn danger(
- theme: &Theme,
- _status: container::Status,
- ) -> container::Appearance {
+ fn danger(theme: &Theme) -> container::Style {
let palette = theme.extended_palette();
styled(palette.danger.weak)
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index 10eb337f..7abc42c5 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -2,15 +2,19 @@
use crate::core::image;
use crate::core::svg;
use crate::core::Size;
+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 custom kind of primitives this [`Backend`] supports.
- type Primitive;
+ type Primitive: TryFrom<Mesh, Error = &'static str>;
+
+ /// The default compositor of this [`Backend`].
+ type Compositor: Compositor<Renderer = Renderer<Self>>;
}
/// A graphics backend that supports text rendering.
diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs
new file mode 100644
index 00000000..b52f9d9d
--- /dev/null
+++ b/graphics/src/cached.rs
@@ -0,0 +1,42 @@
+use crate::Primitive;
+
+use std::sync::Arc;
+
+/// A piece of data that can be cached.
+pub trait Cached: Sized {
+ /// The type of cache produced.
+ type Cache;
+
+ /// Loads the [`Cache`] into a proper instance.
+ ///
+ /// [`Cache`]: Self::Cache
+ fn load(cache: &Self::Cache) -> Self;
+
+ /// Caches this value, producing its corresponding [`Cache`].
+ ///
+ /// [`Cache`]: Self::Cache
+ fn cache(self) -> Self::Cache;
+}
+
+impl<T> Cached for Primitive<T> {
+ type Cache = Arc<Self>;
+
+ fn load(cache: &Arc<Self>) -> Self {
+ Self::Cache {
+ content: cache.clone(),
+ }
+ }
+
+ fn cache(self) -> Arc<Self> {
+ Arc::new(self)
+ }
+}
+
+#[cfg(debug_assertions)]
+impl Cached for () {
+ type Cache = ();
+
+ fn load(_cache: &Self::Cache) -> Self {}
+
+ fn cache(self) -> Self::Cache {}
+}
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs
index 91951a8e..86472a58 100644
--- a/graphics/src/compositor.rs
+++ b/graphics/src/compositor.rs
@@ -1,9 +1,8 @@
//! A compositor is responsible for initializing a renderer and managing window
//! surfaces.
-use crate::{Error, Viewport};
-
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,19 +10,28 @@ 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: iced_core::Renderer;
+ type Renderer;
/// The surface of the backend.
type Surface;
/// Creates a new [`Compositor`].
fn new<W: Window + Clone>(
- settings: Self::Settings,
+ settings: Settings,
compatible_window: W,
+ ) -> impl Future<Output = Result<Self, Error>> {
+ Self::with_backend(settings, compatible_window, None)
+ }
+
+ /// Creates a new [`Compositor`] with a backend preference.
+ ///
+ /// If the backend does not match the preference, it will return
+ /// [`Error::GraphicsAdapterNotFound`].
+ fn with_backend<W: Window + Clone>(
+ _settings: Settings,
+ _compatible_window: W,
+ _backend: Option<&str>,
) -> impl Future<Output = Result<Self, Error>>;
/// Creates a [`Self::Renderer`] for the [`Compositor`].
@@ -93,6 +101,12 @@ impl<T> Window for T where
{
}
+/// Defines the default compositor of a renderer.
+pub trait Default {
+ /// 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 {
@@ -122,3 +136,69 @@ pub struct Information {
/// Contains the graphics backend.
pub backend: String,
}
+
+#[cfg(debug_assertions)]
+impl Compositor for () {
+ type Renderer = ();
+ type Surface = ();
+
+ async fn with_backend<W: Window + Clone>(
+ _settings: Settings,
+ _compatible_window: W,
+ _preffered_backend: Option<&str>,
+ ) -> Result<Self, Error> {
+ Ok(())
+ }
+
+ fn create_renderer(&self) -> Self::Renderer {}
+
+ fn create_surface<W: Window + Clone>(
+ &mut self,
+ _window: W,
+ _width: u32,
+ _height: u32,
+ ) -> Self::Surface {
+ }
+
+ fn configure_surface(
+ &mut self,
+ _surface: &mut Self::Surface,
+ _width: u32,
+ _height: u32,
+ ) {
+ }
+
+ fn fetch_information(&self) -> Information {
+ Information {
+ adapter: String::from("Null Renderer"),
+ backend: String::from("Null"),
+ }
+ }
+
+ fn present<T: AsRef<str>>(
+ &mut self,
+ _renderer: &mut Self::Renderer,
+ _surface: &mut Self::Surface,
+ _viewport: &Viewport,
+ _background_color: Color,
+ _overlay: &[T],
+ ) -> Result<(), SurfaceError> {
+ Ok(())
+ }
+
+ fn screenshot<T: AsRef<str>>(
+ &mut self,
+ _renderer: &mut Self::Renderer,
+ _surface: &mut Self::Surface,
+ _viewport: &Viewport,
+ _background_color: Color,
+ _overlay: &[T],
+ ) -> Vec<u8> {
+ vec![]
+ }
+}
+
+#[cfg(debug_assertions)]
+impl Default for () {
+ type Compositor = ();
+}
diff --git a/graphics/src/error.rs b/graphics/src/error.rs
index c6ea98a3..6ea1d3a4 100644
--- a/graphics/src/error.rs
+++ b/graphics/src/error.rs
@@ -1,5 +1,7 @@
+//! See what can go wrong when creating graphical backends.
+
/// An error that occurred while creating an application's graphical context.
-#[derive(Debug, thiserror::Error)]
+#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Error {
/// The requested backend version is not supported.
#[error("the requested backend version is not supported")]
@@ -11,9 +13,30 @@ pub enum Error {
/// A suitable graphics adapter or device could not be found.
#[error("a suitable graphics adapter or device could not be found")]
- GraphicsAdapterNotFound,
+ GraphicsAdapterNotFound {
+ /// The name of the backend where the error happened
+ backend: &'static str,
+ /// The reason why this backend could not be used
+ reason: Reason,
+ },
/// An error occurred in the context's internal backend
#[error("an error occurred in the context's internal backend")]
BackendError(String),
+
+ /// Multiple errors occurred
+ #[error("multiple errors occurred: {0:?}")]
+ List(Vec<Self>),
+}
+
+/// The reason why a graphics adapter could not be found
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Reason {
+ /// The backend did not match the preference
+ DidNotMatch {
+ /// The preferred backend
+ preferred_backend: String,
+ },
+ /// The request to create the backend failed
+ RequestFailed(String),
}
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index d7d6a0aa..d251efb8 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -1,12 +1,16 @@
//! Build and draw geometry.
pub mod fill;
+pub mod frame;
pub mod path;
pub mod stroke;
+mod cache;
mod style;
mod text;
+pub use cache::Cache;
pub use fill::Fill;
+pub use frame::Frame;
pub use path::Path;
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use style::Style;
@@ -14,11 +18,39 @@ pub use text::Text;
pub use crate::gradient::{self, Gradient};
+use crate::core::{self, Size};
+use crate::Cached;
+
/// A renderer capable of drawing some [`Self::Geometry`].
-pub trait Renderer: crate::core::Renderer {
+pub trait Renderer: core::Renderer {
/// The kind of geometry this renderer can draw.
- type Geometry;
+ type Geometry: Cached;
+
+ /// The kind of [`Frame`] this renderer supports.
+ type Frame: frame::Backend<Geometry = Self::Geometry>;
+
+ /// Creates a new [`Self::Frame`].
+ fn new_frame(&self, size: Size) -> Self::Frame;
+
+ /// Draws the given [`Self::Geometry`].
+ fn draw_geometry(&mut self, geometry: Self::Geometry);
+}
+
+/// The graphics backend of a geometry renderer.
+pub trait Backend {
+ /// The kind of [`Frame`] this backend supports.
+ type Frame: frame::Backend;
+
+ /// Creates a new [`Self::Frame`].
+ fn new_frame(&self, size: Size) -> Self::Frame;
+}
+
+#[cfg(debug_assertions)]
+impl Renderer for () {
+ type Geometry = ();
+ type Frame = ();
+
+ fn new_frame(&self, _size: Size) -> Self::Frame {}
- /// Draws the given layers of [`Self::Geometry`].
- fn draw(&mut self, layers: Vec<Self::Geometry>);
+ fn draw_geometry(&mut self, _geometry: Self::Geometry) {}
}
diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs
new file mode 100644
index 00000000..37d433c2
--- /dev/null
+++ b/graphics/src/geometry/cache.rs
@@ -0,0 +1,108 @@
+use crate::core::Size;
+use crate::geometry::{self, Frame};
+use crate::Cached;
+
+use std::cell::RefCell;
+
+/// A simple cache that stores generated geometry to avoid recomputation.
+///
+/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
+/// change or it is explicitly cleared.
+pub struct Cache<Renderer>
+where
+ Renderer: geometry::Renderer,
+{
+ state: RefCell<State<Renderer::Geometry>>,
+}
+
+impl<Renderer> Cache<Renderer>
+where
+ Renderer: geometry::Renderer,
+{
+ /// Creates a new empty [`Cache`].
+ pub fn new() -> Self {
+ Cache {
+ state: RefCell::new(State::Empty),
+ }
+ }
+
+ /// Clears the [`Cache`], forcing a redraw the next time it is used.
+ pub fn clear(&self) {
+ *self.state.borrow_mut() = State::Empty;
+ }
+
+ /// Draws geometry using the provided closure and stores it in the
+ /// [`Cache`].
+ ///
+ /// The closure will only be called when
+ /// - the bounds have changed since the previous draw call.
+ /// - the [`Cache`] is empty or has been explicitly cleared.
+ ///
+ /// Otherwise, the previously stored geometry will be returned. The
+ /// [`Cache`] is not cleared in this case. In other words, it will keep
+ /// returning the stored geometry if needed.
+ pub fn draw(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ draw_fn: impl FnOnce(&mut Frame<Renderer>),
+ ) -> Renderer::Geometry {
+ use std::ops::Deref;
+
+ if let State::Filled {
+ bounds: cached_bounds,
+ geometry,
+ } = self.state.borrow().deref()
+ {
+ if *cached_bounds == bounds {
+ return Cached::load(geometry);
+ }
+ }
+
+ let mut frame = Frame::new(renderer, bounds);
+ draw_fn(&mut frame);
+
+ let geometry = frame.into_geometry().cache();
+ let result = Cached::load(&geometry);
+
+ *self.state.borrow_mut() = State::Filled { bounds, geometry };
+
+ result
+ }
+}
+
+impl<Renderer> std::fmt::Debug for Cache<Renderer>
+where
+ Renderer: geometry::Renderer,
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let state = self.state.borrow();
+
+ match *state {
+ State::Empty => write!(f, "Cache::Empty"),
+ State::Filled { bounds, .. } => {
+ write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
+ }
+ }
+ }
+}
+
+impl<Renderer> Default for Cache<Renderer>
+where
+ Renderer: geometry::Renderer,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+enum State<Geometry>
+where
+ Geometry: Cached,
+{
+ Empty,
+ Filled {
+ bounds: Size,
+ geometry: Geometry::Cache,
+ },
+}
diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
new file mode 100644
index 00000000..ad35e8ec
--- /dev/null
+++ b/graphics/src/geometry/frame.rs
@@ -0,0 +1,251 @@
+//! Draw and generate geometry.
+use crate::core::{Point, Radians, Rectangle, Size, Vector};
+use crate::geometry::{self, Fill, Path, Stroke, Text};
+
+/// The region of a surface that can be used to draw geometry.
+#[allow(missing_debug_implementations)]
+pub struct Frame<Renderer>
+where
+ Renderer: geometry::Renderer,
+{
+ raw: Renderer::Frame,
+}
+
+impl<Renderer> Frame<Renderer>
+where
+ Renderer: geometry::Renderer,
+{
+ /// Creates a new [`Frame`] with the given dimensions.
+ pub fn new(renderer: &Renderer, size: Size) -> Self {
+ Self {
+ raw: renderer.new_frame(size),
+ }
+ }
+
+ /// Returns the width of the [`Frame`].
+ pub fn width(&self) -> f32 {
+ self.raw.width()
+ }
+
+ /// Returns the height of the [`Frame`].
+ pub fn height(&self) -> f32 {
+ self.raw.height()
+ }
+
+ /// Returns the dimensions of the [`Frame`].
+ pub fn size(&self) -> Size {
+ self.raw.size()
+ }
+
+ /// Returns the coordinate of the center of the [`Frame`].
+ pub fn center(&self) -> Point {
+ self.raw.center()
+ }
+
+ /// Draws the given [`Path`] on the [`Frame`] by filling it with the
+ /// provided style.
+ pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+ self.raw.fill(path, fill);
+ }
+
+ /// Draws an axis-aligned rectangle given its top-left corner coordinate and
+ /// its `Size` on the [`Frame`] by filling it with the provided style.
+ pub fn fill_rectangle(
+ &mut self,
+ top_left: Point,
+ size: Size,
+ fill: impl Into<Fill>,
+ ) {
+ self.raw.fill_rectangle(top_left, size, fill);
+ }
+
+ /// Draws the stroke of the given [`Path`] on the [`Frame`] with the
+ /// provided style.
+ pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
+ self.raw.stroke(path, stroke);
+ }
+
+ /// Draws the characters of the given [`Text`] on the [`Frame`], filling
+ /// them with the given color.
+ ///
+ /// __Warning:__ All text will be rendered on top of all the layers of
+ /// a `Canvas`. Therefore, it is currently only meant to be used for
+ /// overlays, which is the most common use case.
+ pub fn fill_text(&mut self, text: impl Into<Text>) {
+ self.raw.fill_text(text);
+ }
+
+ /// Stores the current transform of the [`Frame`] and executes the given
+ /// drawing operations, restoring the transform afterwards.
+ ///
+ /// This method is useful to compose transforms and perform drawing
+ /// operations in different coordinate systems.
+ #[inline]
+ pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
+ self.push_transform();
+
+ let result = f(self);
+
+ self.pop_transform();
+
+ result
+ }
+
+ /// Pushes the current transform in the transform stack.
+ pub fn push_transform(&mut self) {
+ self.raw.push_transform();
+ }
+
+ /// Pops a transform from the transform stack and sets it as the current transform.
+ pub fn pop_transform(&mut self) {
+ self.raw.pop_transform();
+ }
+
+ /// Executes the given drawing operations within a [`Rectangle`] region,
+ /// clipping any geometry that overflows its bounds. Any transformations
+ /// performed are local to the provided closure.
+ ///
+ /// This method is useful to perform drawing operations that need to be
+ /// clipped.
+ #[inline]
+ pub fn with_clip<R>(
+ &mut self,
+ region: Rectangle,
+ f: impl FnOnce(&mut Self) -> R,
+ ) -> R {
+ let mut frame = self.draft(region.size());
+
+ let result = f(&mut frame);
+
+ let origin = Point::new(region.x, region.y);
+
+ self.paste(frame, origin);
+
+ result
+ }
+
+ /// Creates a new [`Frame`] with the given [`Size`].
+ ///
+ /// Draw its contents back to this [`Frame`] with [`paste`].
+ ///
+ /// [`paste`]: Self::paste
+ pub fn draft(&mut self, size: Size) -> Self {
+ Self {
+ raw: self.raw.draft(size),
+ }
+ }
+
+ /// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
+ pub fn paste(&mut self, frame: Self, at: Point) {
+ self.raw.paste(frame.raw, at);
+ }
+
+ /// Applies a translation to the current transform of the [`Frame`].
+ pub fn translate(&mut self, translation: Vector) {
+ self.raw.translate(translation);
+ }
+
+ /// Applies a rotation in radians to the current transform of the [`Frame`].
+ pub fn rotate(&mut self, angle: impl Into<Radians>) {
+ self.raw.rotate(angle);
+ }
+
+ /// Applies a uniform scaling to the current transform of the [`Frame`].
+ pub fn scale(&mut self, scale: impl Into<f32>) {
+ self.raw.scale(scale);
+ }
+
+ /// Applies a non-uniform scaling to the current transform of the [`Frame`].
+ pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+ self.raw.scale_nonuniform(scale);
+ }
+
+ /// Turns the [`Frame`] into its underlying geometry.
+ pub fn into_geometry(self) -> Renderer::Geometry {
+ self.raw.into_geometry()
+ }
+}
+
+/// The internal implementation of a [`Frame`].
+///
+/// Analogous to [`Frame`]. See [`Frame`] for the documentation
+/// of each method.
+#[allow(missing_docs)]
+pub trait Backend: Sized {
+ type Geometry;
+
+ fn width(&self) -> f32;
+ fn height(&self) -> f32;
+ fn size(&self) -> Size;
+ fn center(&self) -> Point;
+
+ fn push_transform(&mut self);
+ fn pop_transform(&mut self);
+
+ fn translate(&mut self, translation: Vector);
+ fn rotate(&mut self, angle: impl Into<Radians>);
+ fn scale(&mut self, scale: impl Into<f32>);
+ fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
+
+ fn draft(&mut self, size: Size) -> Self;
+ fn paste(&mut self, frame: Self, at: Point);
+
+ fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
+
+ fn fill(&mut self, path: &Path, fill: impl Into<Fill>);
+ fn fill_text(&mut self, text: impl Into<Text>);
+ fn fill_rectangle(
+ &mut self,
+ top_left: Point,
+ size: Size,
+ fill: impl Into<Fill>,
+ );
+
+ fn into_geometry(self) -> Self::Geometry;
+}
+
+#[cfg(debug_assertions)]
+impl Backend for () {
+ type Geometry = ();
+
+ fn width(&self) -> f32 {
+ 0.0
+ }
+
+ fn height(&self) -> f32 {
+ 0.0
+ }
+
+ fn size(&self) -> Size {
+ Size::ZERO
+ }
+
+ fn center(&self) -> Point {
+ Point::ORIGIN
+ }
+
+ fn push_transform(&mut self) {}
+ fn pop_transform(&mut self) {}
+
+ fn translate(&mut self, _translation: Vector) {}
+ fn rotate(&mut self, _angle: impl Into<Radians>) {}
+ fn scale(&mut self, _scale: impl Into<f32>) {}
+ fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
+
+ fn draft(&mut self, _size: Size) -> Self {}
+ fn paste(&mut self, _frame: Self, _at: Point) {}
+
+ fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
+
+ fn fill(&mut self, _path: &Path, _fill: impl Into<Fill>) {}
+ fn fill_text(&mut self, _text: impl Into<Text>) {}
+ fn fill_rectangle(
+ &mut self,
+ _top_left: Point,
+ _size: Size,
+ _fill: impl Into<Fill>,
+ ) {
+ }
+
+ fn into_geometry(self) -> Self::Geometry {}
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index aa9d00e8..d7f2f439 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -17,14 +17,16 @@
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod antialiasing;
-mod error;
+mod cached;
mod primitive;
+mod settings;
mod viewport;
pub mod backend;
pub mod color;
pub mod compositor;
pub mod damage;
+pub mod error;
pub mod gradient;
pub mod mesh;
pub mod renderer;
@@ -38,6 +40,7 @@ pub mod image;
pub use antialiasing::Antialiasing;
pub use backend::Backend;
+pub use cached::Cached;
pub use compositor::Compositor;
pub use damage::Damage;
pub use error::Error;
@@ -45,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;
diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs
index 041986cf..20692b07 100644
--- a/graphics/src/mesh.rs
+++ b/graphics/src/mesh.rs
@@ -74,3 +74,9 @@ pub struct GradientVertex2D {
/// The packed vertex data of the gradient.
pub gradient: gradient::Packed,
}
+
+/// A renderer capable of drawing a [`Mesh`].
+pub trait Renderer {
+ /// Draws the given [`Mesh`].
+ fn draw_mesh(&mut self, mesh: Mesh);
+}
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 143f348b..f517ff3e 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -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;
@@ -8,8 +9,9 @@ use crate::core::text::Text;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
+use crate::mesh;
use crate::text;
-use crate::Primitive;
+use crate::{Mesh, Primitive};
use std::borrow::Cow;
@@ -20,6 +22,7 @@ pub struct Renderer<B: Backend> {
default_font: Font,
default_text_size: Pixels,
primitives: Vec<Primitive<B::Primitive>>,
+ stack: Vec<Vec<Primitive<B::Primitive>>>,
}
impl<B: Backend> Renderer<B> {
@@ -34,6 +37,7 @@ impl<B: Backend> Renderer<B> {
default_font,
default_text_size,
primitives: Vec::new(),
+ stack: Vec::new(),
}
}
@@ -55,61 +59,35 @@ impl<B: Backend> Renderer<B> {
) -> O {
f(&mut self.backend, &self.primitives)
}
+}
- /// Starts recording a new layer.
- pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> {
- std::mem::take(&mut self.primitives)
+impl<B: Backend> iced_core::Renderer for Renderer<B> {
+ fn start_layer(&mut self) {
+ self.stack.push(std::mem::take(&mut self.primitives));
}
- /// Ends the recording of a layer.
- pub fn end_layer(
- &mut self,
- primitives: Vec<Primitive<B::Primitive>>,
- bounds: Rectangle,
- ) {
- let layer = std::mem::replace(&mut self.primitives, primitives);
+ fn end_layer(&mut self, bounds: Rectangle) {
+ let layer = std::mem::replace(
+ &mut self.primitives,
+ self.stack.pop().expect("a layer should be recording"),
+ );
self.primitives.push(Primitive::group(layer).clip(bounds));
}
- /// Starts recording a translation.
- pub fn start_transformation(&mut self) -> Vec<Primitive<B::Primitive>> {
- std::mem::take(&mut self.primitives)
+ fn start_transformation(&mut self) {
+ self.stack.push(std::mem::take(&mut self.primitives));
}
- /// Ends the recording of a translation.
- pub fn end_transformation(
- &mut self,
- primitives: Vec<Primitive<B::Primitive>>,
- transformation: Transformation,
- ) {
- let layer = std::mem::replace(&mut self.primitives, primitives);
+ fn end_transformation(&mut self, transformation: Transformation) {
+ let layer = std::mem::replace(
+ &mut self.primitives,
+ self.stack.pop().expect("a layer should be recording"),
+ );
self.primitives
.push(Primitive::group(layer).transform(transformation));
}
-}
-
-impl<B: Backend> iced_core::Renderer for Renderer<B> {
- fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
- let current = self.start_layer();
-
- f(self);
-
- self.end_layer(current, bounds);
- }
-
- fn with_transformation(
- &mut self,
- transformation: Transformation,
- f: impl FnOnce(&mut Self),
- ) {
- let current = self.start_transformation();
-
- f(self);
-
- self.end_transformation(current, transformation);
- }
fn fill_quad(
&mut self,
@@ -211,11 +189,11 @@ where
{
type Handle = image::Handle;
- fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
+ fn measure_image(&self, handle: &image::Handle) -> Size<u32> {
self.backend().dimensions(handle)
}
- fn draw(
+ fn draw_image(
&mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
@@ -233,11 +211,11 @@ impl<B> svg::Renderer for Renderer<B>
where
B: Backend + backend::Svg,
{
- fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
+ fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
self.backend().viewport_dimensions(handle)
}
- fn draw(
+ fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
@@ -250,3 +228,42 @@ where
});
}
}
+
+impl<B: Backend> mesh::Renderer for Renderer<B> {
+ fn draw_mesh(&mut self, mesh: Mesh) {
+ match B::Primitive::try_from(mesh) {
+ Ok(primitive) => {
+ self.draw_primitive(Primitive::Custom(primitive));
+ }
+ Err(error) => {
+ log::warn!("mesh primitive could not be drawn: {error:?}");
+ }
+ }
+ }
+}
+
+#[cfg(feature = "geometry")]
+impl<B> crate::geometry::Renderer for Renderer<B>
+where
+ B: Backend + crate::geometry::Backend,
+ B::Frame:
+ crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>,
+{
+ type Frame = B::Frame;
+ type Geometry = Primitive<B::Primitive>;
+
+ fn new_frame(&self, size: Size) -> Self::Frame {
+ self.backend.new_frame(size)
+ }
+
+ fn draw_geometry(&mut self, geometry: Self::Geometry) {
+ self.draw_primitive(geometry);
+ }
+}
+
+impl<B> compositor::Default for Renderer<B>
+where
+ B: Backend,
+{
+ type Compositor = B::Compositor;
+}
diff --git a/renderer/src/settings.rs b/graphics/src/settings.rs
index 432eb8a0..68673536 100644
--- a/renderer/src/settings.rs
+++ b/graphics/src/settings.rs
@@ -1,5 +1,5 @@
use crate::core::{Font, Pixels};
-use crate::graphics::Antialiasing;
+use crate::Antialiasing;
/// The settings of a Backend.
#[derive(Debug, Clone, Copy, PartialEq)]
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
index 5cce2427..39c19fa3 100644
--- a/renderer/Cargo.toml
+++ b/renderer/Cargo.toml
@@ -12,9 +12,10 @@ keywords.workspace = true
[features]
wgpu = ["iced_wgpu"]
-image = ["iced_tiny_skia/image", "iced_wgpu?/image"]
-svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"]
-geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"]
+tiny-skia = ["iced_tiny_skia"]
+image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
+svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
+geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
tracing = ["iced_wgpu?/tracing"]
web-colors = ["iced_wgpu?/web-colors"]
webgl = ["iced_wgpu?/webgl"]
@@ -22,7 +23,9 @@ fira-sans = ["iced_graphics/fira-sans"]
[dependencies]
iced_graphics.workspace = true
+
iced_tiny_skia.workspace = true
+iced_tiny_skia.optional = true
iced_wgpu.workspace = true
iced_wgpu.optional = true
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index c23a814c..8b137891 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -1,270 +1 @@
-use crate::core::Color;
-use crate::graphics::compositor::{Information, SurfaceError, Window};
-use crate::graphics::{Error, Viewport};
-use crate::{Renderer, Settings};
-use std::env;
-use std::future::Future;
-
-pub enum Compositor {
- TinySkia(iced_tiny_skia::window::Compositor),
- #[cfg(feature = "wgpu")]
- Wgpu(iced_wgpu::window::Compositor),
-}
-
-pub enum Surface {
- TinySkia(iced_tiny_skia::window::Surface),
- #[cfg(feature = "wgpu")]
- Wgpu(iced_wgpu::window::Surface<'static>),
-}
-
-impl crate::graphics::Compositor for Compositor {
- type Settings = Settings;
- type Renderer = Renderer;
- type Surface = Surface;
-
- fn new<W: Window + Clone>(
- settings: Self::Settings,
- compatible_window: W,
- ) -> impl Future<Output = Result<Self, Error>> {
- let candidates =
- Candidate::list_from_env().unwrap_or(Candidate::default_list());
-
- async move {
- let mut error = Error::GraphicsAdapterNotFound;
-
- for candidate in candidates {
- match candidate.build(settings, compatible_window.clone()).await
- {
- Ok(compositor) => return Ok(compositor),
- Err(new_error) => {
- error = new_error;
- }
- }
- }
-
- Err(error)
- }
- }
-
- fn create_renderer(&self) -> Self::Renderer {
- match self {
- Compositor::TinySkia(compositor) => {
- Renderer::TinySkia(compositor.create_renderer())
- }
- #[cfg(feature = "wgpu")]
- Compositor::Wgpu(compositor) => {
- Renderer::Wgpu(compositor.create_renderer())
- }
- }
- }
-
- fn create_surface<W: Window + Clone>(
- &mut self,
- window: W,
- width: u32,
- height: u32,
- ) -> Surface {
- match self {
- Self::TinySkia(compositor) => Surface::TinySkia(
- compositor.create_surface(window, width, height),
- ),
- #[cfg(feature = "wgpu")]
- Self::Wgpu(compositor) => {
- Surface::Wgpu(compositor.create_surface(window, width, height))
- }
- }
- }
-
- fn configure_surface(
- &mut self,
- surface: &mut Surface,
- width: u32,
- height: u32,
- ) {
- match (self, surface) {
- (Self::TinySkia(compositor), Surface::TinySkia(surface)) => {
- compositor.configure_surface(surface, width, height);
- }
- #[cfg(feature = "wgpu")]
- (Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
- compositor.configure_surface(surface, width, height);
- }
- #[allow(unreachable_patterns)]
- _ => panic!(
- "The provided surface is not compatible with the compositor."
- ),
- }
- }
-
- fn fetch_information(&self) -> Information {
- match self {
- Self::TinySkia(compositor) => compositor.fetch_information(),
- #[cfg(feature = "wgpu")]
- Self::Wgpu(compositor) => compositor.fetch_information(),
- }
- }
-
- fn present<T: AsRef<str>>(
- &mut self,
- renderer: &mut Self::Renderer,
- surface: &mut Self::Surface,
- viewport: &Viewport,
- background_color: Color,
- overlay: &[T],
- ) -> Result<(), SurfaceError> {
- match (self, renderer, surface) {
- (
- Self::TinySkia(_compositor),
- crate::Renderer::TinySkia(renderer),
- Surface::TinySkia(surface),
- ) => renderer.with_primitives(|backend, primitives| {
- iced_tiny_skia::window::compositor::present(
- backend,
- surface,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- }),
- #[cfg(feature = "wgpu")]
- (
- Self::Wgpu(compositor),
- crate::Renderer::Wgpu(renderer),
- Surface::Wgpu(surface),
- ) => renderer.with_primitives(|backend, primitives| {
- iced_wgpu::window::compositor::present(
- compositor,
- backend,
- surface,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- }),
- #[allow(unreachable_patterns)]
- _ => panic!(
- "The provided renderer or surface are not compatible \
- with the compositor."
- ),
- }
- }
-
- fn screenshot<T: AsRef<str>>(
- &mut self,
- renderer: &mut Self::Renderer,
- surface: &mut Self::Surface,
- viewport: &Viewport,
- background_color: Color,
- overlay: &[T],
- ) -> Vec<u8> {
- match (self, renderer, surface) {
- (
- Self::TinySkia(_compositor),
- Renderer::TinySkia(renderer),
- Surface::TinySkia(surface),
- ) => renderer.with_primitives(|backend, primitives| {
- iced_tiny_skia::window::compositor::screenshot(
- surface,
- backend,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- }),
- #[cfg(feature = "wgpu")]
- (
- Self::Wgpu(compositor),
- Renderer::Wgpu(renderer),
- Surface::Wgpu(_),
- ) => renderer.with_primitives(|backend, primitives| {
- iced_wgpu::window::compositor::screenshot(
- compositor,
- backend,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- }),
- #[allow(unreachable_patterns)]
- _ => panic!(
- "The provided renderer or backend are not compatible \
- with the compositor."
- ),
- }
- }
-}
-
-enum Candidate {
- Wgpu,
- TinySkia,
-}
-
-impl Candidate {
- fn default_list() -> Vec<Self> {
- vec![
- #[cfg(feature = "wgpu")]
- Self::Wgpu,
- Self::TinySkia,
- ]
- }
-
- fn list_from_env() -> Option<Vec<Self>> {
- let backends = env::var("ICED_BACKEND").ok()?;
-
- Some(
- backends
- .split(',')
- .map(str::trim)
- .map(|backend| match backend {
- "wgpu" => Self::Wgpu,
- "tiny-skia" => Self::TinySkia,
- _ => panic!("unknown backend value: \"{backend}\""),
- })
- .collect(),
- )
- }
-
- async fn build<W: Window>(
- self,
- settings: Settings,
- _compatible_window: W,
- ) -> Result<Compositor, Error> {
- match self {
- Self::TinySkia => {
- let compositor = iced_tiny_skia::window::compositor::new(
- iced_tiny_skia::Settings {
- default_font: settings.default_font,
- default_text_size: settings.default_text_size,
- },
- _compatible_window,
- );
-
- Ok(Compositor::TinySkia(compositor))
- }
- #[cfg(feature = "wgpu")]
- Self::Wgpu => {
- let compositor = iced_wgpu::window::compositor::new(
- iced_wgpu::Settings {
- default_font: settings.default_font,
- default_text_size: settings.default_text_size,
- antialiasing: settings.antialiasing,
- ..iced_wgpu::Settings::from_env()
- },
- _compatible_window,
- )
- .await?;
-
- Ok(Compositor::Wgpu(compositor))
- }
- #[cfg(not(feature = "wgpu"))]
- Self::Wgpu => {
- panic!("`wgpu` feature was not enabled in `iced_renderer`")
- }
- }
- }
-}
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
new file mode 100644
index 00000000..ef9cc9a9
--- /dev/null
+++ b/renderer/src/fallback.rs
@@ -0,0 +1,570 @@
+use crate::core::image;
+use crate::core::renderer;
+use crate::core::svg;
+use crate::core::{
+ self, Background, Color, Point, Rectangle, Size, Transformation,
+};
+use crate::graphics;
+use crate::graphics::compositor;
+use crate::graphics::mesh;
+
+pub enum Renderer<L, R> {
+ Left(L),
+ Right(R),
+}
+
+macro_rules! delegate {
+ ($renderer:expr, $name:ident, $body:expr) => {
+ match $renderer {
+ Self::Left($name) => $body,
+ Self::Right($name) => $body,
+ }
+ };
+}
+
+impl<L, R> core::Renderer for Renderer<L, R>
+where
+ L: core::Renderer,
+ R: core::Renderer,
+{
+ fn fill_quad(
+ &mut self,
+ quad: renderer::Quad,
+ background: impl Into<Background>,
+ ) {
+ delegate!(self, renderer, renderer.fill_quad(quad, background.into()));
+ }
+
+ fn clear(&mut self) {
+ delegate!(self, renderer, renderer.clear());
+ }
+
+ fn start_layer(&mut self) {
+ delegate!(self, renderer, renderer.start_layer());
+ }
+
+ fn end_layer(&mut self, bounds: Rectangle) {
+ delegate!(self, renderer, renderer.end_layer(bounds));
+ }
+
+ fn start_transformation(&mut self) {
+ delegate!(self, renderer, renderer.start_transformation());
+ }
+
+ fn end_transformation(&mut self, transformation: Transformation) {
+ delegate!(self, renderer, renderer.end_transformation(transformation));
+ }
+}
+
+impl<L, R> core::text::Renderer for Renderer<L, R>
+where
+ L: core::text::Renderer,
+ R: core::text::Renderer<
+ Font = L::Font,
+ Paragraph = L::Paragraph,
+ Editor = L::Editor,
+ >,
+{
+ type Font = L::Font;
+ type Paragraph = L::Paragraph;
+ type Editor = L::Editor;
+
+ const ICON_FONT: Self::Font = L::ICON_FONT;
+ const CHECKMARK_ICON: char = L::CHECKMARK_ICON;
+ const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON;
+
+ fn default_font(&self) -> Self::Font {
+ delegate!(self, renderer, renderer.default_font())
+ }
+
+ fn default_size(&self) -> core::Pixels {
+ delegate!(self, renderer, renderer.default_size())
+ }
+
+ fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
+ delegate!(self, renderer, renderer.load_font(font));
+ }
+
+ fn fill_paragraph(
+ &mut self,
+ text: &Self::Paragraph,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ delegate!(
+ self,
+ renderer,
+ renderer.fill_paragraph(text, position, color, clip_bounds)
+ );
+ }
+
+ fn fill_editor(
+ &mut self,
+ editor: &Self::Editor,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ delegate!(
+ self,
+ renderer,
+ renderer.fill_editor(editor, position, color, clip_bounds)
+ );
+ }
+
+ fn fill_text(
+ &mut self,
+ text: core::Text<'_, Self::Font>,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ delegate!(
+ self,
+ renderer,
+ renderer.fill_text(text, position, color, clip_bounds)
+ );
+ }
+}
+
+impl<L, R> image::Renderer for Renderer<L, R>
+where
+ L: image::Renderer,
+ R: image::Renderer<Handle = L::Handle>,
+{
+ type Handle = L::Handle;
+
+ fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
+ delegate!(self, renderer, renderer.measure_image(handle))
+ }
+
+ fn draw_image(
+ &mut self,
+ handle: Self::Handle,
+ filter_method: image::FilterMethod,
+ bounds: Rectangle,
+ ) {
+ delegate!(
+ self,
+ renderer,
+ renderer.draw_image(handle, filter_method, bounds)
+ );
+ }
+}
+
+impl<L, R> svg::Renderer for Renderer<L, R>
+where
+ L: svg::Renderer,
+ R: svg::Renderer,
+{
+ fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
+ delegate!(self, renderer, renderer.measure_svg(handle))
+ }
+
+ fn draw_svg(
+ &mut self,
+ handle: svg::Handle,
+ color: Option<Color>,
+ bounds: Rectangle,
+ ) {
+ delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
+ }
+}
+
+impl<L, R> mesh::Renderer for Renderer<L, R>
+where
+ L: mesh::Renderer,
+ R: mesh::Renderer,
+{
+ fn draw_mesh(&mut self, mesh: graphics::Mesh) {
+ delegate!(self, renderer, renderer.draw_mesh(mesh));
+ }
+}
+
+pub enum Compositor<L, R>
+where
+ L: graphics::Compositor,
+ R: graphics::Compositor,
+{
+ Left(L),
+ Right(R),
+}
+
+pub enum Surface<L, R> {
+ Left(L),
+ Right(R),
+}
+
+impl<L, R> graphics::Compositor for Compositor<L, R>
+where
+ L: graphics::Compositor,
+ R: graphics::Compositor,
+{
+ type Renderer = Renderer<L::Renderer, R::Renderer>;
+ type Surface = Surface<L::Surface, R::Surface>;
+
+ async fn with_backend<W: compositor::Window + Clone>(
+ settings: graphics::Settings,
+ compatible_window: W,
+ backend: Option<&str>,
+ ) -> Result<Self, graphics::Error> {
+ use std::env;
+
+ let backends = backend
+ .map(str::to_owned)
+ .or_else(|| env::var("ICED_BACKEND").ok());
+
+ let mut candidates: Vec<_> = backends
+ .map(|backends| {
+ backends
+ .split(',')
+ .filter(|candidate| !candidate.is_empty())
+ .map(str::to_owned)
+ .map(Some)
+ .collect()
+ })
+ .unwrap_or_default();
+
+ if candidates.is_empty() {
+ candidates.push(None);
+ }
+
+ let mut errors = vec![];
+
+ for backend in candidates.iter().map(Option::as_deref) {
+ match L::with_backend(settings, compatible_window.clone(), backend)
+ .await
+ {
+ Ok(compositor) => return Ok(Self::Left(compositor)),
+ Err(error) => {
+ errors.push(error);
+ }
+ }
+
+ match R::with_backend(settings, compatible_window.clone(), backend)
+ .await
+ {
+ Ok(compositor) => return Ok(Self::Right(compositor)),
+ Err(error) => {
+ errors.push(error);
+ }
+ }
+ }
+
+ Err(graphics::Error::List(errors))
+ }
+
+ fn create_renderer(&self) -> Self::Renderer {
+ match self {
+ Self::Left(compositor) => {
+ Renderer::Left(compositor.create_renderer())
+ }
+ Self::Right(compositor) => {
+ Renderer::Right(compositor.create_renderer())
+ }
+ }
+ }
+
+ fn create_surface<W: compositor::Window + Clone>(
+ &mut self,
+ window: W,
+ width: u32,
+ height: u32,
+ ) -> Self::Surface {
+ match self {
+ Self::Left(compositor) => {
+ Surface::Left(compositor.create_surface(window, width, height))
+ }
+ Self::Right(compositor) => {
+ Surface::Right(compositor.create_surface(window, width, height))
+ }
+ }
+ }
+
+ fn configure_surface(
+ &mut self,
+ surface: &mut Self::Surface,
+ width: u32,
+ height: u32,
+ ) {
+ match (self, surface) {
+ (Self::Left(compositor), Surface::Left(surface)) => {
+ compositor.configure_surface(surface, width, height);
+ }
+ (Self::Right(compositor), Surface::Right(surface)) => {
+ compositor.configure_surface(surface, width, height);
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ fn fetch_information(&self) -> compositor::Information {
+ delegate!(self, compositor, compositor.fetch_information())
+ }
+
+ fn present<T: AsRef<str>>(
+ &mut self,
+ renderer: &mut Self::Renderer,
+ surface: &mut Self::Surface,
+ viewport: &graphics::Viewport,
+ background_color: Color,
+ overlay: &[T],
+ ) -> Result<(), compositor::SurfaceError> {
+ match (self, renderer, surface) {
+ (
+ Self::Left(compositor),
+ Renderer::Left(renderer),
+ Surface::Left(surface),
+ ) => compositor.present(
+ renderer,
+ surface,
+ viewport,
+ background_color,
+ overlay,
+ ),
+ (
+ Self::Right(compositor),
+ Renderer::Right(renderer),
+ Surface::Right(surface),
+ ) => compositor.present(
+ renderer,
+ surface,
+ viewport,
+ background_color,
+ overlay,
+ ),
+ _ => unreachable!(),
+ }
+ }
+
+ fn screenshot<T: AsRef<str>>(
+ &mut self,
+ renderer: &mut Self::Renderer,
+ surface: &mut Self::Surface,
+ viewport: &graphics::Viewport,
+ background_color: Color,
+ overlay: &[T],
+ ) -> Vec<u8> {
+ match (self, renderer, surface) {
+ (
+ Self::Left(compositor),
+ Renderer::Left(renderer),
+ Surface::Left(surface),
+ ) => compositor.screenshot(
+ renderer,
+ surface,
+ viewport,
+ background_color,
+ overlay,
+ ),
+ (
+ Self::Right(compositor),
+ Renderer::Right(renderer),
+ Surface::Right(surface),
+ ) => compositor.screenshot(
+ renderer,
+ surface,
+ viewport,
+ background_color,
+ overlay,
+ ),
+ _ => unreachable!(),
+ }
+ }
+}
+
+#[cfg(feature = "wgpu")]
+impl<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R>
+where
+ L: iced_wgpu::primitive::pipeline::Renderer,
+ R: core::Renderer,
+{
+ fn draw_pipeline_primitive(
+ &mut self,
+ bounds: Rectangle,
+ primitive: impl iced_wgpu::primitive::pipeline::Primitive,
+ ) {
+ match self {
+ Self::Left(renderer) => {
+ renderer.draw_pipeline_primitive(bounds, primitive);
+ }
+ Self::Right(_) => {
+ log::warn!(
+ "Custom shader primitive is not supported with this renderer."
+ );
+ }
+ }
+ }
+}
+
+#[cfg(feature = "geometry")]
+mod geometry {
+ use super::Renderer;
+ use crate::core::{Point, Radians, Size, Vector};
+ use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
+ use crate::graphics::Cached;
+
+ impl<L, R> geometry::Renderer for Renderer<L, R>
+ where
+ L: geometry::Renderer,
+ R: geometry::Renderer,
+ {
+ type Geometry = Geometry<L::Geometry, R::Geometry>;
+ type Frame = Frame<L::Frame, R::Frame>;
+
+ fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame {
+ match self {
+ Self::Left(renderer) => Frame::Left(renderer.new_frame(size)),
+ Self::Right(renderer) => Frame::Right(renderer.new_frame(size)),
+ }
+ }
+
+ fn draw_geometry(&mut self, geometry: Self::Geometry) {
+ match (self, geometry) {
+ (Self::Left(renderer), Geometry::Left(geometry)) => {
+ renderer.draw_geometry(geometry);
+ }
+ (Self::Right(renderer), Geometry::Right(geometry)) => {
+ renderer.draw_geometry(geometry);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub enum Geometry<L, R> {
+ Left(L),
+ Right(R),
+ }
+
+ impl<L, R> Cached for Geometry<L, R>
+ where
+ L: Cached,
+ R: Cached,
+ {
+ type Cache = Geometry<L::Cache, R::Cache>;
+
+ fn load(cache: &Self::Cache) -> Self {
+ match cache {
+ Geometry::Left(cache) => Self::Left(L::load(cache)),
+ Geometry::Right(cache) => Self::Right(R::load(cache)),
+ }
+ }
+
+ fn cache(self) -> Self::Cache {
+ match self {
+ Self::Left(geometry) => Geometry::Left(geometry.cache()),
+ Self::Right(geometry) => Geometry::Right(geometry.cache()),
+ }
+ }
+ }
+
+ pub enum Frame<L, R> {
+ Left(L),
+ Right(R),
+ }
+
+ impl<L, R> geometry::frame::Backend for Frame<L, R>
+ where
+ L: geometry::frame::Backend,
+ R: geometry::frame::Backend,
+ {
+ type Geometry = Geometry<L::Geometry, R::Geometry>;
+
+ fn width(&self) -> f32 {
+ delegate!(self, frame, frame.width())
+ }
+
+ fn height(&self) -> f32 {
+ delegate!(self, frame, frame.height())
+ }
+
+ fn size(&self) -> Size {
+ delegate!(self, frame, frame.size())
+ }
+
+ fn center(&self) -> Point {
+ delegate!(self, frame, frame.center())
+ }
+
+ fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+ delegate!(self, frame, frame.fill(path, fill));
+ }
+
+ fn fill_rectangle(
+ &mut self,
+ top_left: Point,
+ size: Size,
+ fill: impl Into<Fill>,
+ ) {
+ delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
+ }
+
+ fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
+ delegate!(self, frame, frame.stroke(path, stroke));
+ }
+
+ fn fill_text(&mut self, text: impl Into<Text>) {
+ delegate!(self, frame, frame.fill_text(text));
+ }
+
+ fn push_transform(&mut self) {
+ delegate!(self, frame, frame.push_transform());
+ }
+
+ fn pop_transform(&mut self) {
+ delegate!(self, frame, frame.pop_transform());
+ }
+
+ fn draft(&mut self, size: Size) -> Self {
+ match self {
+ Self::Left(frame) => Self::Left(frame.draft(size)),
+ Self::Right(frame) => Self::Right(frame.draft(size)),
+ }
+ }
+
+ fn paste(&mut self, frame: Self, at: Point) {
+ match (self, frame) {
+ (Self::Left(target), Self::Left(source)) => {
+ target.paste(source, at);
+ }
+ (Self::Right(target), Self::Right(source)) => {
+ target.paste(source, at);
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ fn translate(&mut self, translation: Vector) {
+ delegate!(self, frame, frame.translate(translation));
+ }
+
+ fn rotate(&mut self, angle: impl Into<Radians>) {
+ delegate!(self, frame, frame.rotate(angle));
+ }
+
+ fn scale(&mut self, scale: impl Into<f32>) {
+ delegate!(self, frame, frame.scale(scale));
+ }
+
+ fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+ delegate!(self, frame, frame.scale_nonuniform(scale));
+ }
+
+ fn into_geometry(self) -> Self::Geometry {
+ match self {
+ Frame::Left(frame) => Geometry::Left(frame.into_geometry()),
+ Frame::Right(frame) => Geometry::Right(frame.into_geometry()),
+ }
+ }
+ }
+}
+
+impl<L, R> compositor::Default for Renderer<L, R>
+where
+ L: compositor::Default,
+ R: compositor::Default,
+{
+ type Compositor = Compositor<L::Compositor, R::Compositor>;
+}
diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs
deleted file mode 100644
index 36435148..00000000
--- a/renderer/src/geometry.rs
+++ /dev/null
@@ -1,210 +0,0 @@
-mod cache;
-
-pub use cache::Cache;
-
-use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector};
-use crate::graphics::geometry::{Fill, Path, Stroke, Text};
-use crate::Renderer;
-
-macro_rules! delegate {
- ($frame:expr, $name:ident, $body:expr) => {
- match $frame {
- Self::TinySkia($name) => $body,
- #[cfg(feature = "wgpu")]
- Self::Wgpu($name) => $body,
- }
- };
-}
-
-pub enum Geometry {
- TinySkia(iced_tiny_skia::Primitive),
- #[cfg(feature = "wgpu")]
- Wgpu(iced_wgpu::Primitive),
-}
-
-impl Geometry {
- pub fn transform(self, transformation: Transformation) -> Self {
- match self {
- Self::TinySkia(primitive) => {
- Self::TinySkia(primitive.transform(transformation))
- }
- #[cfg(feature = "wgpu")]
- Self::Wgpu(primitive) => {
- Self::Wgpu(primitive.transform(transformation))
- }
- }
- }
-}
-
-pub enum Frame {
- TinySkia(iced_tiny_skia::geometry::Frame),
- #[cfg(feature = "wgpu")]
- Wgpu(iced_wgpu::geometry::Frame),
-}
-
-impl Frame {
- pub fn new(renderer: &Renderer, size: Size) -> Self {
- match renderer {
- Renderer::TinySkia(_) => {
- Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size))
- }
- #[cfg(feature = "wgpu")]
- Renderer::Wgpu(_) => {
- Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
- }
- }
- }
-
- /// Returns the width of the [`Frame`].
- #[inline]
- pub fn width(&self) -> f32 {
- delegate!(self, frame, frame.width())
- }
-
- /// Returns the height of the [`Frame`].
- #[inline]
- pub fn height(&self) -> f32 {
- delegate!(self, frame, frame.height())
- }
-
- /// Returns the dimensions of the [`Frame`].
- #[inline]
- pub fn size(&self) -> Size {
- delegate!(self, frame, frame.size())
- }
-
- /// Returns the coordinate of the center of the [`Frame`].
- #[inline]
- pub fn center(&self) -> Point {
- delegate!(self, frame, frame.center())
- }
-
- /// Draws the given [`Path`] on the [`Frame`] by filling it with the
- /// provided style.
- pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
- delegate!(self, frame, frame.fill(path, fill));
- }
-
- /// Draws an axis-aligned rectangle given its top-left corner coordinate and
- /// its `Size` on the [`Frame`] by filling it with the provided style.
- pub fn fill_rectangle(
- &mut self,
- top_left: Point,
- size: Size,
- fill: impl Into<Fill>,
- ) {
- delegate!(self, frame, frame.fill_rectangle(top_left, size, fill));
- }
-
- /// Draws the stroke of the given [`Path`] on the [`Frame`] with the
- /// provided style.
- pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
- delegate!(self, frame, frame.stroke(path, stroke));
- }
-
- /// Draws the characters of the given [`Text`] on the [`Frame`], filling
- /// them with the given color.
- ///
- /// __Warning:__ Text currently does not work well with rotations and scale
- /// transforms! The position will be correctly transformed, but the
- /// resulting glyphs will not be rotated or scaled properly.
- ///
- /// Additionally, all text will be rendered on top of all the layers of
- /// a `Canvas`. Therefore, it is currently only meant to be used for
- /// overlays, which is the most common use case.
- ///
- /// Support for vectorial text is planned, and should address all these
- /// limitations.
- pub fn fill_text(&mut self, text: impl Into<Text>) {
- delegate!(self, frame, frame.fill_text(text));
- }
-
- /// Stores the current transform of the [`Frame`] and executes the given
- /// drawing operations, restoring the transform afterwards.
- ///
- /// This method is useful to compose transforms and perform drawing
- /// operations in different coordinate systems.
- #[inline]
- pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
- delegate!(self, frame, frame.push_transform());
-
- let result = f(self);
-
- delegate!(self, frame, frame.pop_transform());
-
- result
- }
-
- /// Executes the given drawing operations within a [`Rectangle`] region,
- /// clipping any geometry that overflows its bounds. Any transformations
- /// performed are local to the provided closure.
- ///
- /// This method is useful to perform drawing operations that need to be
- /// clipped.
- #[inline]
- pub fn with_clip<R>(
- &mut self,
- region: Rectangle,
- f: impl FnOnce(&mut Frame) -> R,
- ) -> R {
- let mut frame = match self {
- Self::TinySkia(_) => Self::TinySkia(
- iced_tiny_skia::geometry::Frame::new(region.size()),
- ),
- #[cfg(feature = "wgpu")]
- Self::Wgpu(_) => {
- Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size()))
- }
- };
-
- let result = f(&mut frame);
-
- let origin = Point::new(region.x, region.y);
-
- match (self, frame) {
- (Self::TinySkia(target), Self::TinySkia(frame)) => {
- target.clip(frame, origin);
- }
- #[cfg(feature = "wgpu")]
- (Self::Wgpu(target), Self::Wgpu(frame)) => {
- target.clip(frame, origin);
- }
- #[allow(unreachable_patterns)]
- _ => unreachable!(),
- };
-
- result
- }
-
- /// Applies a translation to the current transform of the [`Frame`].
- #[inline]
- pub fn translate(&mut self, translation: Vector) {
- delegate!(self, frame, frame.translate(translation));
- }
-
- /// Applies a rotation in radians to the current transform of the [`Frame`].
- #[inline]
- pub fn rotate(&mut self, angle: impl Into<Radians>) {
- delegate!(self, frame, frame.rotate(angle));
- }
-
- /// Applies a uniform scaling to the current transform of the [`Frame`].
- #[inline]
- pub fn scale(&mut self, scale: impl Into<f32>) {
- delegate!(self, frame, frame.scale(scale));
- }
-
- /// Applies a non-uniform scaling to the current transform of the [`Frame`].
- #[inline]
- pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
- delegate!(self, frame, frame.scale_nonuniform(scale));
- }
-
- pub fn into_geometry(self) -> Geometry {
- match self {
- Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()),
- #[cfg(feature = "wgpu")]
- Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()),
- }
- }
-}
diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs
deleted file mode 100644
index 3aff76b9..00000000
--- a/renderer/src/geometry/cache.rs
+++ /dev/null
@@ -1,125 +0,0 @@
-use crate::core::Size;
-use crate::geometry::{Frame, Geometry};
-use crate::Renderer;
-
-use std::cell::RefCell;
-use std::sync::Arc;
-
-/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
-///
-/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
-/// change or it is explicitly cleared.
-#[derive(Debug, Default)]
-pub struct Cache {
- state: RefCell<State>,
-}
-
-#[derive(Debug, Default)]
-enum State {
- #[default]
- Empty,
- Filled {
- bounds: Size,
- primitive: Internal,
- },
-}
-
-#[derive(Debug, Clone)]
-enum Internal {
- TinySkia(Arc<iced_tiny_skia::Primitive>),
- #[cfg(feature = "wgpu")]
- Wgpu(Arc<iced_wgpu::Primitive>),
-}
-
-impl Cache {
- /// Creates a new empty [`Cache`].
- pub fn new() -> Self {
- Cache {
- state: RefCell::default(),
- }
- }
-
- /// Clears the [`Cache`], forcing a redraw the next time it is used.
- pub fn clear(&self) {
- *self.state.borrow_mut() = State::Empty;
- }
-
- /// Draws [`Geometry`] using the provided closure and stores it in the
- /// [`Cache`].
- ///
- /// The closure will only be called when
- /// - the bounds have changed since the previous draw call.
- /// - the [`Cache`] is empty or has been explicitly cleared.
- ///
- /// Otherwise, the previously stored [`Geometry`] will be returned. The
- /// [`Cache`] is not cleared in this case. In other words, it will keep
- /// returning the stored [`Geometry`] if needed.
- pub fn draw(
- &self,
- renderer: &Renderer,
- bounds: Size,
- draw_fn: impl FnOnce(&mut Frame),
- ) -> Geometry {
- use std::ops::Deref;
-
- if let State::Filled {
- bounds: cached_bounds,
- primitive,
- } = self.state.borrow().deref()
- {
- if *cached_bounds == bounds {
- match primitive {
- Internal::TinySkia(primitive) => {
- return Geometry::TinySkia(
- iced_tiny_skia::Primitive::Cache {
- content: primitive.clone(),
- },
- );
- }
- #[cfg(feature = "wgpu")]
- Internal::Wgpu(primitive) => {
- return Geometry::Wgpu(iced_wgpu::Primitive::Cache {
- content: primitive.clone(),
- });
- }
- }
- }
- }
-
- let mut frame = Frame::new(renderer, bounds);
- draw_fn(&mut frame);
-
- let primitive = {
- let geometry = frame.into_geometry();
-
- match geometry {
- Geometry::TinySkia(primitive) => {
- Internal::TinySkia(Arc::new(primitive))
- }
- #[cfg(feature = "wgpu")]
- Geometry::Wgpu(primitive) => {
- Internal::Wgpu(Arc::new(primitive))
- }
- }
- };
-
- *self.state.borrow_mut() = State::Filled {
- bounds,
- primitive: primitive.clone(),
- };
-
- match primitive {
- Internal::TinySkia(primitive) => {
- Geometry::TinySkia(iced_tiny_skia::Primitive::Cache {
- content: primitive,
- })
- }
- #[cfg(feature = "wgpu")]
- Internal::Wgpu(primitive) => {
- Geometry::Wgpu(iced_wgpu::Primitive::Cache {
- content: primitive,
- })
- }
- }
- }
-}
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index 757c264d..7c48995d 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -4,299 +4,51 @@
#[cfg(feature = "wgpu")]
pub use iced_wgpu as wgpu;
-pub mod compositor;
-
-#[cfg(feature = "geometry")]
-pub mod geometry;
-
-mod settings;
+pub mod fallback;
pub use iced_graphics as graphics;
pub use iced_graphics::core;
-pub use compositor::Compositor;
-pub use settings::Settings;
-
#[cfg(feature = "geometry")]
-pub use geometry::Geometry;
-
-use crate::core::renderer;
-use crate::core::text::{self, Text};
-use crate::core::{
- Background, Color, Font, Pixels, Point, Rectangle, Transformation,
-};
-use crate::graphics::text::Editor;
-use crate::graphics::text::Paragraph;
-use crate::graphics::Mesh;
-
-use std::borrow::Cow;
+pub use iced_graphics::geometry;
/// The default graphics renderer for [`iced`].
///
/// [`iced`]: https://github.com/iced-rs/iced
-pub enum Renderer {
- TinySkia(iced_tiny_skia::Renderer),
- #[cfg(feature = "wgpu")]
- Wgpu(iced_wgpu::Renderer),
-}
-
-macro_rules! delegate {
- ($renderer:expr, $name:ident, $body:expr) => {
- match $renderer {
- Self::TinySkia($name) => $body,
- #[cfg(feature = "wgpu")]
- Self::Wgpu($name) => $body,
- }
- };
-}
-
-impl Renderer {
- pub fn draw_mesh(&mut self, mesh: Mesh) {
- match self {
- Self::TinySkia(_) => {
- log::warn!("Unsupported mesh primitive: {mesh:?}");
- }
- #[cfg(feature = "wgpu")]
- Self::Wgpu(renderer) => {
- renderer.draw_primitive(iced_wgpu::Primitive::Custom(
- iced_wgpu::primitive::Custom::Mesh(mesh),
- ));
- }
- }
- }
-}
-
-impl core::Renderer for Renderer {
- fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
- match self {
- Self::TinySkia(renderer) => {
- let primitives = renderer.start_layer();
-
- f(self);
-
- match self {
- Self::TinySkia(renderer) => {
- renderer.end_layer(primitives, bounds);
- }
- #[cfg(feature = "wgpu")]
- _ => unreachable!(),
- }
- }
- #[cfg(feature = "wgpu")]
- Self::Wgpu(renderer) => {
- let primitives = renderer.start_layer();
-
- f(self);
-
- match self {
- #[cfg(feature = "wgpu")]
- Self::Wgpu(renderer) => {
- renderer.end_layer(primitives, bounds);
- }
- _ => unreachable!(),
- }
- }
- }
- }
-
- fn with_transformation(
- &mut self,
- transformation: Transformation,
- f: impl FnOnce(&mut Self),
- ) {
- match self {
- Self::TinySkia(renderer) => {
- let primitives = renderer.start_transformation();
-
- f(self);
-
- match self {
- Self::TinySkia(renderer) => {
- renderer.end_transformation(primitives, transformation);
- }
- #[cfg(feature = "wgpu")]
- _ => unreachable!(),
- }
- }
- #[cfg(feature = "wgpu")]
- Self::Wgpu(renderer) => {
- let primitives = renderer.start_transformation();
-
- f(self);
+pub type Renderer = renderer::Renderer;
- match self {
- #[cfg(feature = "wgpu")]
- Self::Wgpu(renderer) => {
- renderer.end_transformation(primitives, transformation);
- }
- _ => unreachable!(),
- }
- }
- }
- }
-
- fn fill_quad(
- &mut self,
- quad: renderer::Quad,
- background: impl Into<Background>,
- ) {
- delegate!(self, renderer, renderer.fill_quad(quad, background));
- }
-
- fn clear(&mut self) {
- delegate!(self, renderer, renderer.clear());
- }
-}
-
-impl text::Renderer for Renderer {
- type Font = Font;
- type Paragraph = Paragraph;
- type Editor = Editor;
-
- const ICON_FONT: Font = iced_tiny_skia::Renderer::ICON_FONT;
- const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::CHECKMARK_ICON;
- const ARROW_DOWN_ICON: char = iced_tiny_skia::Renderer::ARROW_DOWN_ICON;
-
- fn default_font(&self) -> Self::Font {
- delegate!(self, renderer, renderer.default_font())
- }
-
- fn default_size(&self) -> Pixels {
- delegate!(self, renderer, renderer.default_size())
- }
-
- fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
- delegate!(self, renderer, renderer.load_font(bytes));
- }
-
- fn fill_paragraph(
- &mut self,
- paragraph: &Self::Paragraph,
- position: Point,
- color: Color,
- clip_bounds: Rectangle,
- ) {
- delegate!(
- self,
- renderer,
- renderer.fill_paragraph(paragraph, position, color, clip_bounds)
- );
- }
-
- fn fill_editor(
- &mut self,
- editor: &Self::Editor,
- position: Point,
- color: Color,
- clip_bounds: Rectangle,
- ) {
- delegate!(
- self,
- renderer,
- renderer.fill_editor(editor, position, color, clip_bounds)
- );
- }
-
- fn fill_text(
- &mut self,
- text: Text<'_, Self::Font>,
- position: Point,
- color: Color,
- clip_bounds: Rectangle,
- ) {
- delegate!(
- self,
- renderer,
- renderer.fill_text(text, position, color, clip_bounds)
- );
- }
-}
-
-#[cfg(feature = "image")]
-impl crate::core::image::Renderer for Renderer {
- type Handle = crate::core::image::Handle;
+/// The default graphics compositor for [`iced`].
+///
+/// [`iced`]: https://github.com/iced-rs/iced
+pub type Compositor = renderer::Compositor;
- fn dimensions(
- &self,
- handle: &crate::core::image::Handle,
- ) -> core::Size<u32> {
- delegate!(self, renderer, renderer.dimensions(handle))
- }
+#[cfg(all(feature = "wgpu", feature = "tiny-skia"))]
+mod renderer {
+ pub type Renderer = crate::fallback::Renderer<
+ iced_wgpu::Renderer,
+ iced_tiny_skia::Renderer,
+ >;
- fn draw(
- &mut self,
- handle: crate::core::image::Handle,
- filter_method: crate::core::image::FilterMethod,
- bounds: Rectangle,
- ) {
- delegate!(self, renderer, renderer.draw(handle, filter_method, bounds));
- }
+ pub type Compositor = crate::fallback::Compositor<
+ iced_wgpu::window::Compositor,
+ iced_tiny_skia::window::Compositor,
+ >;
}
-#[cfg(feature = "svg")]
-impl crate::core::svg::Renderer for Renderer {
- fn dimensions(&self, handle: &crate::core::svg::Handle) -> core::Size<u32> {
- delegate!(self, renderer, renderer.dimensions(handle))
- }
-
- fn draw(
- &mut self,
- handle: crate::core::svg::Handle,
- color: Option<crate::core::Color>,
- bounds: Rectangle,
- ) {
- delegate!(self, renderer, renderer.draw(handle, color, bounds));
- }
+#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))]
+mod renderer {
+ pub type Renderer = iced_wgpu::Renderer;
+ pub type Compositor = iced_wgpu::window::Compositor;
}
-#[cfg(feature = "geometry")]
-impl crate::graphics::geometry::Renderer for Renderer {
- type Geometry = crate::Geometry;
-
- fn draw(&mut self, layers: Vec<Self::Geometry>) {
- match self {
- Self::TinySkia(renderer) => {
- for layer in layers {
- match layer {
- crate::Geometry::TinySkia(primitive) => {
- renderer.draw_primitive(primitive);
- }
- #[cfg(feature = "wgpu")]
- crate::Geometry::Wgpu(_) => unreachable!(),
- }
- }
- }
- #[cfg(feature = "wgpu")]
- Self::Wgpu(renderer) => {
- for layer in layers {
- match layer {
- crate::Geometry::Wgpu(primitive) => {
- renderer.draw_primitive(primitive);
- }
- crate::Geometry::TinySkia(_) => unreachable!(),
- }
- }
- }
- }
- }
+#[cfg(all(not(feature = "wgpu"), feature = "tiny-skia"))]
+mod renderer {
+ pub type Renderer = iced_tiny_skia::Renderer;
+ pub type Compositor = iced_tiny_skia::window::Compositor;
}
-#[cfg(feature = "wgpu")]
-impl iced_wgpu::primitive::pipeline::Renderer for Renderer {
- fn draw_pipeline_primitive(
- &mut self,
- bounds: Rectangle,
- primitive: impl wgpu::primitive::pipeline::Primitive,
- ) {
- match self {
- Self::TinySkia(_renderer) => {
- log::warn!(
- "Custom shader primitive is unavailable with tiny-skia."
- );
- }
- Self::Wgpu(renderer) => {
- renderer.draw_pipeline_primitive(bounds, primitive);
- }
- }
- }
+#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))]
+mod renderer {
+ pub type Renderer = ();
+ pub type Compositor = ();
}
diff --git a/runtime/src/program.rs b/runtime/src/program.rs
index 6c1b8f07..0ea94d3b 100644
--- a/runtime/src/program.rs
+++ b/runtime/src/program.rs
@@ -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;
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index 748fb651..006225ed 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -45,7 +45,7 @@ where
///
/// ```no_run
/// # mod iced_wgpu {
- /// # pub use iced_runtime::core::renderer::Null as Renderer;
+ /// # pub type Renderer = ();
/// # }
/// #
/// # pub struct Counter;
@@ -62,7 +62,7 @@ where
/// // Initialization
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
- /// let mut renderer = Renderer::new();
+ /// let mut renderer = Renderer::default();
/// let mut window_size = Size::new(1024.0, 768.0);
///
/// // Application loop
@@ -121,7 +121,7 @@ where
///
/// ```no_run
/// # mod iced_wgpu {
- /// # pub use iced_runtime::core::renderer::Null as Renderer;
+ /// # pub type Renderer = ();
/// # }
/// #
/// # pub struct Counter;
@@ -139,7 +139,7 @@ where
///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
- /// let mut renderer = Renderer::new();
+ /// let mut renderer = Renderer::default();
/// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
@@ -374,7 +374,7 @@ where
///
/// ```no_run
/// # mod iced_wgpu {
- /// # pub use iced_runtime::core::renderer::Null as Renderer;
+ /// # pub type Renderer = ();
/// # pub type Theme = ();
/// # }
/// #
@@ -394,7 +394,7 @@ where
///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
- /// let mut renderer = Renderer::new();
+ /// let mut renderer = Renderer::default();
/// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
diff --git a/src/application.rs b/src/application.rs
index 8317abcb..9197834b 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -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::Default;
+
/// 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::Default>::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)
diff --git a/src/lib.rs b/src/lib.rs
index 0e9566e2..e67b46e3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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: program::Renderer + 'static,
{
program(title, update, view).run()
}
diff --git a/src/multi_window.rs b/src/multi_window.rs
index fca0be46..b81297dc 100644
--- a/src/multi_window.rs
+++ b/src/multi_window.rs
@@ -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::<
diff --git a/src/program.rs b/src/program.rs
index 7a366585..d4c2a266 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -31,7 +31,9 @@
//! }
//! ```
use crate::application::Application;
+use crate::core::text;
use crate::executor::{self, Executor};
+use crate::graphics::compositor;
use crate::window;
use crate::{Command, Element, Font, Result, Settings, Size, Subscription};
@@ -67,37 +69,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: self::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: self::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 +121,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 +133,7 @@ where
_state: PhantomData,
_message: PhantomData,
_theme: PhantomData,
+ _renderer: PhantomData,
},
settings: Settings::default(),
}
@@ -184,6 +191,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 +224,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 +425,9 @@ pub trait Definition: Sized {
/// 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;
@@ -431,7 +442,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 +481,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 +503,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 +546,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 +564,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 +611,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 +636,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 +679,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 +705,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 +744,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 +774,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 +850,30 @@ 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)
}
}
+
+/// The renderer of some [`Program`].
+pub trait Renderer: text::Renderer + compositor::Default {}
+
+impl<T> Renderer for T where T: text::Renderer + compositor::Default {}
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index b6487b38..8c8781e3 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -5,6 +5,7 @@ use crate::graphics::backend;
use crate::graphics::text;
use crate::graphics::{Damage, Viewport};
use crate::primitive::{self, Primitive};
+use crate::window;
use std::borrow::Cow;
@@ -989,8 +990,9 @@ fn rounded_box_sdf(
(x.powf(2.0) + y.powf(2.0)).sqrt() - radius
}
-impl iced_graphics::Backend for Backend {
+impl backend::Backend for Backend {
type Primitive = primitive::Custom;
+ type Compositor = window::Compositor;
}
impl backend::Text for Backend {
@@ -1018,3 +1020,12 @@ impl backend::Svg for Backend {
self.vector_pipeline.viewport_dimensions(handle)
}
}
+
+#[cfg(feature = "geometry")]
+impl crate::graphics::geometry::Backend for Backend {
+ type Frame = crate::geometry::Frame;
+
+ fn new_frame(&self, size: Size) -> Self::Frame {
+ crate::geometry::Frame::new(size)
+ }
+}
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index 16787f89..76482e12 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -4,7 +4,7 @@ use crate::core::{
};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
-use crate::graphics::geometry::{Path, Style, Text};
+use crate::graphics::geometry::{self, Path, Style, Text};
use crate::graphics::Gradient;
use crate::primitive::{self, Primitive};
@@ -25,23 +25,36 @@ impl Frame {
}
}
- pub fn width(&self) -> f32 {
+ pub fn into_primitive(self) -> Primitive {
+ Primitive::Clip {
+ bounds: Rectangle::new(Point::ORIGIN, self.size),
+ content: Box::new(Primitive::Group {
+ primitives: self.primitives,
+ }),
+ }
+ }
+}
+
+impl geometry::frame::Backend for Frame {
+ type Geometry = Primitive;
+
+ fn width(&self) -> f32 {
self.size.width
}
- pub fn height(&self) -> f32 {
+ fn height(&self) -> f32 {
self.size.height
}
- pub fn size(&self) -> Size {
+ fn size(&self) -> Size {
self.size
}
- pub fn center(&self) -> Point {
+ fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
}
- pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+ fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Some(path) =
convert_path(path).and_then(|path| path.transform(self.transform))
else {
@@ -61,7 +74,7 @@ impl Frame {
}));
}
- pub fn fill_rectangle(
+ fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
@@ -89,7 +102,7 @@ impl Frame {
}));
}
- pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
+ fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let Some(path) =
convert_path(path).and_then(|path| path.transform(self.transform))
else {
@@ -110,7 +123,7 @@ impl Frame {
}));
}
- pub fn fill_text(&mut self, text: impl Into<Text>) {
+ fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transform.get_scale();
@@ -174,51 +187,50 @@ impl Frame {
}
}
- pub fn push_transform(&mut self) {
+ fn push_transform(&mut self) {
self.stack.push(self.transform);
}
- pub fn pop_transform(&mut self) {
+ fn pop_transform(&mut self) {
self.transform = self.stack.pop().expect("Pop transform");
}
- pub fn clip(&mut self, frame: Self, at: Point) {
+ fn draft(&mut self, size: Size) -> Self {
+ Self::new(size)
+ }
+
+ fn paste(&mut self, frame: Self, at: Point) {
self.primitives.push(Primitive::Transform {
transformation: Transformation::translate(at.x, at.y),
content: Box::new(frame.into_primitive()),
});
}
- pub fn translate(&mut self, translation: Vector) {
+ fn translate(&mut self, translation: Vector) {
self.transform =
self.transform.pre_translate(translation.x, translation.y);
}
- pub fn rotate(&mut self, angle: impl Into<Radians>) {
+ fn rotate(&mut self, angle: impl Into<Radians>) {
self.transform = self.transform.pre_concat(
tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()),
);
}
- pub fn scale(&mut self, scale: impl Into<f32>) {
+ fn scale(&mut self, scale: impl Into<f32>) {
let scale = scale.into();
self.scale_nonuniform(Vector { x: scale, y: scale });
}
- pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+ fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
let scale = scale.into();
self.transform = self.transform.pre_scale(scale.x, scale.y);
}
- pub fn into_primitive(self) -> Primitive {
- Primitive::Clip {
- bounds: Rectangle::new(Point::ORIGIN, self.size),
- content: Box::new(Primitive::Group {
- primitives: self.primitives,
- }),
- }
+ fn into_geometry(self) -> Self::Geometry {
+ self.into_primitive()
}
}
diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs
index 7718d542..b7c428e4 100644
--- a/tiny_skia/src/primitive.rs
+++ b/tiny_skia/src/primitive.rs
@@ -1,5 +1,5 @@
use crate::core::Rectangle;
-use crate::graphics::Damage;
+use crate::graphics::{Damage, Mesh};
pub type Primitive = crate::graphics::Primitive<Custom>;
@@ -42,3 +42,11 @@ impl Damage for Custom {
}
}
}
+
+impl TryFrom<Mesh> for Custom {
+ type Error = &'static str;
+
+ fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
+ Err("unsupported")
+ }
+}
diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs
index ec27b218..01d015b4 100644
--- a/tiny_skia/src/settings.rs
+++ b/tiny_skia/src/settings.rs
@@ -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,
+ }
+ }
+}
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index a98825f1..25c57dc1 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -1,11 +1,11 @@
use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
-use crate::graphics::{Error, Viewport};
+use crate::graphics::error::{self, Error};
+use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use std::collections::VecDeque;
-use std::future::{self, Future};
use std::num::NonZeroU32;
pub struct Compositor {
@@ -25,15 +25,25 @@ 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,
+ async fn with_backend<W: compositor::Window>(
+ settings: graphics::Settings,
compatible_window: W,
- ) -> impl Future<Output = Result<Self, Error>> {
- future::ready(Ok(new(settings, compatible_window)))
+ backend: Option<&str>,
+ ) -> Result<Self, Error> {
+ match backend {
+ None | Some("tiny-skia") | Some("tiny_skia") => {
+ Ok(new(settings.into(), compatible_window))
+ }
+ Some(backend) => Err(Error::GraphicsAdapterNotFound {
+ backend: "tiny-skia",
+ reason: error::Reason::DidNotMatch {
+ preferred_backend: backend.to_owned(),
+ },
+ }),
+ }
}
fn create_renderer(&self) -> Self::Renderer {
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 4a0d89f0..f6162e0f 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -32,6 +32,7 @@ glyphon.workspace = true
guillotiere.workspace = true
log.workspace = true
once_cell.workspace = true
+thiserror.workspace = true
wgpu.workspace = true
lyon.workspace = true
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 09ddbe4d..5019191c 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive};
use crate::quad;
use crate::text;
use crate::triangle;
+use crate::window;
use crate::{Layer, Settings};
#[cfg(feature = "tracing")]
@@ -371,8 +372,9 @@ impl Backend {
}
}
-impl crate::graphics::Backend for Backend {
+impl backend::Backend for Backend {
type Primitive = primitive::Custom;
+ type Compositor = window::Compositor;
}
impl backend::Text for Backend {
@@ -397,3 +399,12 @@ impl backend::Svg for Backend {
self.image_pipeline.viewport_dimensions(handle)
}
}
+
+#[cfg(feature = "geometry")]
+impl crate::graphics::geometry::Backend for Backend {
+ type Frame = crate::geometry::Frame;
+
+ fn new_frame(&self, size: Size) -> Self::Frame {
+ crate::geometry::Frame::new(size)
+ }
+}
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index f4e0fbda..ba56c59d 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -6,7 +6,7 @@ use crate::core::{
use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
- LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
+ self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
@@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive};
use lyon::geom::euclid;
use lyon::tessellation;
+
use std::borrow::Cow;
/// A frame for drawing some geometry.
@@ -27,191 +28,87 @@ pub struct Frame {
stroke_tessellator: tessellation::StrokeTessellator,
}
-enum Buffer {
- Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
- Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
-}
-
-struct BufferStack {
- stack: Vec<Buffer>,
-}
-
-impl BufferStack {
- fn new() -> Self {
- Self { stack: Vec::new() }
- }
-
- fn get_mut(&mut self, style: &Style) -> &mut Buffer {
- match style {
- Style::Solid(_) => match self.stack.last() {
- Some(Buffer::Solid(_)) => {}
- _ => {
- self.stack.push(Buffer::Solid(
- tessellation::VertexBuffers::new(),
- ));
- }
- },
- Style::Gradient(_) => match self.stack.last() {
- Some(Buffer::Gradient(_)) => {}
- _ => {
- self.stack.push(Buffer::Gradient(
- tessellation::VertexBuffers::new(),
- ));
- }
+impl Frame {
+ /// Creates a new [`Frame`] with the given [`Size`].
+ pub fn new(size: Size) -> Frame {
+ Frame {
+ size,
+ buffers: BufferStack::new(),
+ primitives: Vec::new(),
+ transforms: Transforms {
+ previous: Vec::new(),
+ current: Transform(lyon::math::Transform::identity()),
},
+ fill_tessellator: tessellation::FillTessellator::new(),
+ stroke_tessellator: tessellation::StrokeTessellator::new(),
}
-
- self.stack.last_mut().unwrap()
}
- fn get_fill<'a>(
- &'a mut self,
- style: &Style,
- ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
- match (style, self.get_mut(style)) {
- (Style::Solid(color), Buffer::Solid(buffer)) => {
- Box::new(tessellation::BuffersBuilder::new(
- buffer,
- TriangleVertex2DBuilder(color::pack(*color)),
- ))
- }
- (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
- Box::new(tessellation::BuffersBuilder::new(
- buffer,
- GradientVertex2DBuilder {
- gradient: gradient.pack(),
- },
- ))
+ fn into_primitives(mut self) -> Vec<Primitive> {
+ for buffer in self.buffers.stack {
+ match buffer {
+ Buffer::Solid(buffer) => {
+ if !buffer.indices.is_empty() {
+ self.primitives.push(Primitive::Custom(
+ primitive::Custom::Mesh(Mesh::Solid {
+ buffers: mesh::Indexed {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ size: self.size,
+ }),
+ ));
+ }
+ }
+ Buffer::Gradient(buffer) => {
+ if !buffer.indices.is_empty() {
+ self.primitives.push(Primitive::Custom(
+ primitive::Custom::Mesh(Mesh::Gradient {
+ buffers: mesh::Indexed {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ size: self.size,
+ }),
+ ));
+ }
+ }
}
- _ => unreachable!(),
}
- }
- fn get_stroke<'a>(
- &'a mut self,
- style: &Style,
- ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
- match (style, self.get_mut(style)) {
- (Style::Solid(color), Buffer::Solid(buffer)) => {
- Box::new(tessellation::BuffersBuilder::new(
- buffer,
- TriangleVertex2DBuilder(color::pack(*color)),
- ))
- }
- (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
- Box::new(tessellation::BuffersBuilder::new(
- buffer,
- GradientVertex2DBuilder {
- gradient: gradient.pack(),
- },
- ))
- }
- _ => unreachable!(),
- }
+ self.primitives
}
}
-#[derive(Debug)]
-struct Transforms {
- previous: Vec<Transform>,
- current: Transform,
-}
-
-#[derive(Debug, Clone, Copy)]
-struct Transform(lyon::math::Transform);
-
-impl Transform {
- fn is_identity(&self) -> bool {
- self.0 == lyon::math::Transform::identity()
- }
-
- fn is_scale_translation(&self) -> bool {
- self.0.m12.abs() < 2.0 * f32::EPSILON
- && self.0.m21.abs() < 2.0 * f32::EPSILON
- }
-
- fn scale(&self) -> (f32, f32) {
- (self.0.m11, self.0.m22)
- }
-
- fn transform_point(&self, point: Point) -> Point {
- let transformed = self
- .0
- .transform_point(euclid::Point2D::new(point.x, point.y));
-
- Point {
- x: transformed.x,
- y: transformed.y,
- }
- }
-
- fn transform_style(&self, style: Style) -> Style {
- match style {
- Style::Solid(color) => Style::Solid(color),
- Style::Gradient(gradient) => {
- Style::Gradient(self.transform_gradient(gradient))
- }
- }
- }
-
- fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
- match &mut gradient {
- Gradient::Linear(linear) => {
- linear.start = self.transform_point(linear.start);
- linear.end = self.transform_point(linear.end);
- }
- }
-
- gradient
- }
-}
+impl geometry::frame::Backend for Frame {
+ type Geometry = Primitive;
-impl Frame {
/// Creates a new empty [`Frame`] with the given dimensions.
///
/// The default coordinate system of a [`Frame`] has its origin at the
/// top-left corner of its bounds.
- pub fn new(size: Size) -> Frame {
- Frame {
- size,
- buffers: BufferStack::new(),
- primitives: Vec::new(),
- transforms: Transforms {
- previous: Vec::new(),
- current: Transform(lyon::math::Transform::identity()),
- },
- fill_tessellator: tessellation::FillTessellator::new(),
- stroke_tessellator: tessellation::StrokeTessellator::new(),
- }
- }
- /// Returns the width of the [`Frame`].
#[inline]
- pub fn width(&self) -> f32 {
+ fn width(&self) -> f32 {
self.size.width
}
- /// Returns the height of the [`Frame`].
#[inline]
- pub fn height(&self) -> f32 {
+ fn height(&self) -> f32 {
self.size.height
}
- /// Returns the dimensions of the [`Frame`].
#[inline]
- pub fn size(&self) -> Size {
+ fn size(&self) -> Size {
self.size
}
- /// Returns the coordinate of the center of the [`Frame`].
#[inline]
- pub fn center(&self) -> Point {
+ fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
}
- /// Draws the given [`Path`] on the [`Frame`] by filling it with the
- /// provided style.
- pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+ fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Fill { style, rule } = fill.into();
let mut buffer = self
@@ -239,9 +136,7 @@ impl Frame {
.expect("Tessellate path.");
}
- /// Draws an axis-aligned rectangle given its top-left corner coordinate and
- /// its `Size` on the [`Frame`] by filling it with the provided style.
- pub fn fill_rectangle(
+ fn fill_rectangle(
&mut self,
top_left: Point,
size: Size,
@@ -276,9 +171,7 @@ impl Frame {
.expect("Fill rectangle");
}
- /// Draws the stroke of the given [`Path`] on the [`Frame`] with the
- /// provided style.
- pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
+ fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let stroke = stroke.into();
let mut buffer = self
@@ -315,20 +208,7 @@ impl Frame {
.expect("Stroke path");
}
- /// Draws the characters of the given [`Text`] on the [`Frame`], filling
- /// them with the given color.
- ///
- /// __Warning:__ Text currently does not work well with rotations and scale
- /// transforms! The position will be correctly transformed, but the
- /// resulting glyphs will not be rotated or scaled properly.
- ///
- /// Additionally, all text will be rendered on top of all the layers of
- /// a `Canvas`. Therefore, it is currently only meant to be used for
- /// overlays, which is the most common use case.
- ///
- /// Support for vectorial text is planned, and should address all these
- /// limitations.
- pub fn fill_text(&mut self, text: impl Into<Text>) {
+ fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transforms.current.scale();
@@ -384,57 +264,55 @@ impl Frame {
}
}
- /// Stores the current transform of the [`Frame`] and executes the given
- /// drawing operations, restoring the transform afterwards.
- ///
- /// This method is useful to compose transforms and perform drawing
- /// operations in different coordinate systems.
#[inline]
- pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
- self.push_transform();
-
- let result = f(self);
-
- self.pop_transform();
-
- result
+ fn translate(&mut self, translation: Vector) {
+ self.transforms.current.0 =
+ self.transforms
+ .current
+ .0
+ .pre_translate(lyon::math::Vector::new(
+ translation.x,
+ translation.y,
+ ));
}
- /// Pushes the current transform in the transform stack.
- pub fn push_transform(&mut self) {
- self.transforms.previous.push(self.transforms.current);
+ #[inline]
+ fn rotate(&mut self, angle: impl Into<Radians>) {
+ self.transforms.current.0 = self
+ .transforms
+ .current
+ .0
+ .pre_rotate(lyon::math::Angle::radians(angle.into().0));
}
- /// Pops a transform from the transform stack and sets it as the current transform.
- pub fn pop_transform(&mut self) {
- self.transforms.current = self.transforms.previous.pop().unwrap();
+ #[inline]
+ fn scale(&mut self, scale: impl Into<f32>) {
+ let scale = scale.into();
+
+ self.scale_nonuniform(Vector { x: scale, y: scale });
}
- /// Executes the given drawing operations within a [`Rectangle`] region,
- /// clipping any geometry that overflows its bounds. Any transformations
- /// performed are local to the provided closure.
- ///
- /// This method is useful to perform drawing operations that need to be
- /// clipped.
#[inline]
- pub fn with_clip<R>(
- &mut self,
- region: Rectangle,
- f: impl FnOnce(&mut Frame) -> R,
- ) -> R {
- let mut frame = Frame::new(region.size());
+ fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
+ let scale = scale.into();
- let result = f(&mut frame);
+ self.transforms.current.0 =
+ self.transforms.current.0.pre_scale(scale.x, scale.y);
+ }
- let origin = Point::new(region.x, region.y);
+ fn push_transform(&mut self) {
+ self.transforms.previous.push(self.transforms.current);
+ }
- self.clip(frame, origin);
+ fn pop_transform(&mut self) {
+ self.transforms.current = self.transforms.previous.pop().unwrap();
+ }
- result
+ fn draft(&mut self, size: Size) -> Frame {
+ Frame::new(size)
}
- /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
- pub fn clip(&mut self, frame: Frame, at: Point) {
+ fn paste(&mut self, frame: Frame, at: Point) {
let size = frame.size();
let primitives = frame.into_primitives();
let transformation = Transformation::translate(at.x, at.y);
@@ -462,89 +340,151 @@ impl Frame {
});
}
- /// Applies a translation to the current transform of the [`Frame`].
- #[inline]
- pub fn translate(&mut self, translation: Vector) {
- self.transforms.current.0 =
- self.transforms
- .current
- .0
- .pre_translate(lyon::math::Vector::new(
- translation.x,
- translation.y,
- ));
+ fn into_geometry(self) -> Self::Geometry {
+ Primitive::Group {
+ primitives: self.into_primitives(),
+ }
}
+}
- /// Applies a rotation in radians to the current transform of the [`Frame`].
- #[inline]
- pub fn rotate(&mut self, angle: impl Into<Radians>) {
- self.transforms.current.0 = self
- .transforms
- .current
- .0
- .pre_rotate(lyon::math::Angle::radians(angle.into().0));
+enum Buffer {
+ Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
+ Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
+}
+
+struct BufferStack {
+ stack: Vec<Buffer>,
+}
+
+impl BufferStack {
+ fn new() -> Self {
+ Self { stack: Vec::new() }
}
- /// Applies a uniform scaling to the current transform of the [`Frame`].
- #[inline]
- pub fn scale(&mut self, scale: impl Into<f32>) {
- let scale = scale.into();
+ fn get_mut(&mut self, style: &Style) -> &mut Buffer {
+ match style {
+ Style::Solid(_) => match self.stack.last() {
+ Some(Buffer::Solid(_)) => {}
+ _ => {
+ self.stack.push(Buffer::Solid(
+ tessellation::VertexBuffers::new(),
+ ));
+ }
+ },
+ Style::Gradient(_) => match self.stack.last() {
+ Some(Buffer::Gradient(_)) => {}
+ _ => {
+ self.stack.push(Buffer::Gradient(
+ tessellation::VertexBuffers::new(),
+ ));
+ }
+ },
+ }
- self.scale_nonuniform(Vector { x: scale, y: scale });
+ self.stack.last_mut().unwrap()
}
- /// Applies a non-uniform scaling to the current transform of the [`Frame`].
- #[inline]
- pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
- let scale = scale.into();
+ fn get_fill<'a>(
+ &'a mut self,
+ style: &Style,
+ ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
+ match (style, self.get_mut(style)) {
+ (Style::Solid(color), Buffer::Solid(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ TriangleVertex2DBuilder(color::pack(*color)),
+ ))
+ }
+ (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ GradientVertex2DBuilder {
+ gradient: gradient.pack(),
+ },
+ ))
+ }
+ _ => unreachable!(),
+ }
+ }
- self.transforms.current.0 =
- self.transforms.current.0.pre_scale(scale.x, scale.y);
+ fn get_stroke<'a>(
+ &'a mut self,
+ style: &Style,
+ ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
+ match (style, self.get_mut(style)) {
+ (Style::Solid(color), Buffer::Solid(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ TriangleVertex2DBuilder(color::pack(*color)),
+ ))
+ }
+ (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ GradientVertex2DBuilder {
+ gradient: gradient.pack(),
+ },
+ ))
+ }
+ _ => unreachable!(),
+ }
}
+}
- /// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
- pub fn into_primitive(self) -> Primitive {
- Primitive::Group {
- primitives: self.into_primitives(),
+#[derive(Debug)]
+struct Transforms {
+ previous: Vec<Transform>,
+ current: Transform,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Transform(lyon::math::Transform);
+
+impl Transform {
+ fn is_identity(&self) -> bool {
+ self.0 == lyon::math::Transform::identity()
+ }
+
+ fn is_scale_translation(&self) -> bool {
+ self.0.m12.abs() < 2.0 * f32::EPSILON
+ && self.0.m21.abs() < 2.0 * f32::EPSILON
+ }
+
+ fn scale(&self) -> (f32, f32) {
+ (self.0.m11, self.0.m22)
+ }
+
+ fn transform_point(&self, point: Point) -> Point {
+ let transformed = self
+ .0
+ .transform_point(euclid::Point2D::new(point.x, point.y));
+
+ Point {
+ x: transformed.x,
+ y: transformed.y,
}
}
- fn into_primitives(mut self) -> Vec<Primitive> {
- for buffer in self.buffers.stack {
- match buffer {
- Buffer::Solid(buffer) => {
- if !buffer.indices.is_empty() {
- self.primitives.push(Primitive::Custom(
- primitive::Custom::Mesh(Mesh::Solid {
- buffers: mesh::Indexed {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- size: self.size,
- }),
- ));
- }
- }
- Buffer::Gradient(buffer) => {
- if !buffer.indices.is_empty() {
- self.primitives.push(Primitive::Custom(
- primitive::Custom::Mesh(Mesh::Gradient {
- buffers: mesh::Indexed {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- size: self.size,
- }),
- ));
- }
- }
+ fn transform_style(&self, style: Style) -> Style {
+ match style {
+ Style::Solid(color) => Style::Solid(color),
+ Style::Gradient(gradient) => {
+ Style::Gradient(self.transform_gradient(gradient))
}
}
+ }
- self.primitives
+ fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
+ match &mut gradient {
+ Gradient::Linear(linear) => {
+ linear.start = self.transform_point(linear.start);
+ linear.end = self.transform_point(linear.end);
+ }
+ }
+
+ gradient
}
}
-
struct GradientVertex2DBuilder {
gradient: gradient::Packed,
}
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
index fff927ea..ee9af93c 100644
--- a/wgpu/src/primitive.rs
+++ b/wgpu/src/primitive.rs
@@ -28,3 +28,11 @@ impl Damage for Custom {
}
}
}
+
+impl TryFrom<Mesh> for Custom {
+ type Error = &'static str;
+
+ fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
+ Ok(Custom::Mesh(mesh))
+ }
+}
diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs
index c6b7c5e2..814440ba 100644
--- a/wgpu/src/primitive/pipeline.rs
+++ b/wgpu/src/primitive/pipeline.rs
@@ -1,5 +1,5 @@
//! Draw primitives using custom pipelines.
-use crate::core::{Rectangle, Size};
+use crate::core::{self, Rectangle, Size};
use std::any::{Any, TypeId};
use std::collections::HashMap;
@@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static {
}
/// A renderer than can draw custom pipeline primitives.
-pub trait Renderer: crate::core::Renderer {
+pub trait Renderer: core::Renderer {
/// Draws a custom pipeline primitive.
fn draw_pipeline_primitive(
&mut self,
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index c9338fec..828d9e09 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -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`].
///
@@ -29,30 +29,6 @@ pub struct Settings {
pub antialiasing: Option<Antialiasing>,
}
-impl Settings {
- /// Creates new [`Settings`] using environment configuration.
- ///
- /// Specifically:
- ///
- /// - The `internal_backend` can be configured using the `WGPU_BACKEND`
- /// environment variable. If the variable is not set, the primary backend
- /// will be used. The following values are allowed:
- /// - `vulkan`
- /// - `metal`
- /// - `dx12`
- /// - `dx11`
- /// - `gl`
- /// - `webgpu`
- /// - `primary`
- pub fn from_env() -> Self {
- Settings {
- internal_backend: wgpu::util::backend_bits_from_env()
- .unwrap_or(wgpu::Backends::all()),
- ..Self::default()
- }
- }
-}
-
impl Default for Settings {
fn default() -> Settings {
Settings {
@@ -64,3 +40,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()
+ }
+ }
+}
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index fa6b9373..9a3e3b34 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -1,13 +1,11 @@
//! 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::error;
+use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
-use std::future::Future;
-
/// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
@@ -20,6 +18,32 @@ pub struct Compositor {
alpha_mode: wgpu::CompositeAlphaMode,
}
+/// A compositor error.
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+ /// The surface creation failed.
+ #[error("the surface creation failed: {0}")]
+ SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
+ /// The surface is not compatible.
+ #[error("the surface is not compatible")]
+ IncompatibleSurface,
+ /// No adapter was found for the options requested.
+ #[error("no adapter was found for the options requested: {0:?}")]
+ NoAdapterFound(String),
+ /// No device request succeeded.
+ #[error("no device request succeeded: {0:?}")]
+ RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
+}
+
+impl From<Error> for graphics::Error {
+ fn from(error: Error) -> Self {
+ Self::GraphicsAdapterNotFound {
+ backend: "wgpu",
+ reason: error::Reason::RequestFailed(error.to_string()),
+ }
+ }
+}
+
impl Compositor {
/// Requests a new [`Compositor`] with the given [`Settings`].
///
@@ -27,7 +51,7 @@ impl Compositor {
pub async fn request<W: compositor::Window>(
settings: Settings,
compatible_window: Option<W>,
- ) -> Option<Self> {
+ ) -> Result<Self, Error> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: settings.internal_backend,
..Default::default()
@@ -49,23 +73,27 @@ impl Compositor {
let compatible_surface = compatible_window
.and_then(|window| instance.create_surface(window).ok());
+ let adapter_options = wgpu::RequestAdapterOptions {
+ power_preference: wgpu::util::power_preference_from_env()
+ .unwrap_or(if settings.antialiasing.is_none() {
+ wgpu::PowerPreference::LowPower
+ } else {
+ wgpu::PowerPreference::HighPerformance
+ }),
+ compatible_surface: compatible_surface.as_ref(),
+ force_fallback_adapter: false,
+ };
+
let adapter = instance
- .request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::util::power_preference_from_env()
- .unwrap_or(if settings.antialiasing.is_none() {
- wgpu::PowerPreference::LowPower
- } else {
- wgpu::PowerPreference::HighPerformance
- }),
- compatible_surface: compatible_surface.as_ref(),
- force_fallback_adapter: false,
- })
- .await?;
+ .request_adapter(&adapter_options)
+ .await
+ .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
log::info!("Selected: {:#?}", adapter.get_info());
- let (format, alpha_mode) =
- compatible_surface.as_ref().and_then(|surface| {
+ let (format, alpha_mode) = compatible_surface
+ .as_ref()
+ .and_then(|surface| {
let capabilities = surface.get_capabilities(&adapter);
let mut formats = capabilities.formats.iter().copied();
@@ -101,7 +129,8 @@ impl Compositor {
};
format.zip(Some(preferred_alpha))
- })?;
+ })
+ .ok_or(Error::IncompatibleSurface)?;
log::info!(
"Selected format: {format:?} with alpha mode: {alpha_mode:?}"
@@ -115,39 +144,46 @@ impl Compositor {
let limits =
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
- let mut limits = limits.into_iter().map(|limits| wgpu::Limits {
+ let limits = limits.into_iter().map(|limits| wgpu::Limits {
max_bind_groups: 2,
..limits
});
- let (device, queue) =
- loop {
- let required_limits = limits.next()?;
- let device = adapter.request_device(
+ let mut errors = Vec::new();
+
+ for required_limits in limits {
+ let result = adapter
+ .request_device(
&wgpu::DeviceDescriptor {
label: Some(
"iced_wgpu::window::compositor device descriptor",
),
required_features: wgpu::Features::empty(),
- required_limits,
+ required_limits: required_limits.clone(),
},
None,
- ).await.ok();
-
- if let Some(device) = device {
- break Some(device);
+ )
+ .await;
+
+ match result {
+ Ok((device, queue)) => {
+ return Ok(Compositor {
+ instance,
+ settings,
+ adapter,
+ device,
+ queue,
+ format,
+ alpha_mode,
+ })
}
- }?;
-
- Some(Compositor {
- instance,
- settings,
- adapter,
- device,
- queue,
- format,
- alpha_mode,
- })
+ Err(error) => {
+ errors.push((required_limits, error));
+ }
+ }
+ }
+
+ Err(Error::RequestDeviceFailed(errors))
}
/// Creates a new rendering [`Backend`] for this [`Compositor`].
@@ -168,9 +204,7 @@ pub async fn new<W: compositor::Window>(
settings: Settings,
compatible_window: W,
) -> Result<Compositor, Error> {
- Compositor::request(settings, Some(compatible_window))
- .await
- .ok_or(Error::GraphicsAdapterNotFound)
+ Compositor::request(settings, Some(compatible_window)).await
}
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
@@ -229,15 +263,31 @@ 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,
+ async fn with_backend<W: compositor::Window>(
+ settings: graphics::Settings,
compatible_window: W,
- ) -> impl Future<Output = Result<Self, Error>> {
- new(settings, compatible_window)
+ backend: Option<&str>,
+ ) -> Result<Self, graphics::Error> {
+ match backend {
+ None | Some("wgpu") => Ok(new(
+ Settings {
+ internal_backend: wgpu::util::backend_bits_from_env()
+ .unwrap_or(wgpu::Backends::all()),
+ ..settings.into()
+ },
+ compatible_window,
+ )
+ .await?),
+ Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
+ backend: "wgpu",
+ reason: error::Reason::DidNotMatch {
+ preferred_backend: backend.to_owned(),
+ },
+ }),
+ }
}
fn create_renderer(&self) -> Self::Renderer {
diff --git a/widget/Cargo.toml b/widget/Cargo.toml
index 3c9ffddb..a45f47ef 100644
--- a/widget/Cargo.toml
+++ b/widget/Cargo.toml
@@ -21,6 +21,7 @@ svg = ["iced_renderer/svg"]
canvas = ["iced_renderer/geometry"]
qr_code = ["canvas", "qrcode"]
wgpu = ["iced_renderer/wgpu"]
+advanced = []
[dependencies]
iced_renderer.workspace = true
diff --git a/widget/src/button.rs b/widget/src/button.rs
index 5790f811..dc949671 100644
--- a/widget/src/button.rs
+++ b/widget/src/button.rs
@@ -49,6 +49,7 @@ use crate::core::{
pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where
Renderer: crate::core::Renderer,
+ Theme: Catalog,
{
content: Element<'a, Message, Theme, Renderer>,
on_press: Option<Message>,
@@ -56,20 +57,18 @@ where
height: Length,
padding: Padding,
clip: bool,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Button<'a, Message, Theme, Renderer>
where
Renderer: crate::core::Renderer,
+ Theme: Catalog,
{
/// Creates a new [`Button`] with the given content.
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
- ) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ ) -> Self {
let content = content.into();
let size = content.as_widget().size_hint();
@@ -80,7 +79,7 @@ where
height: size.height.fluid(),
padding: DEFAULT_PADDING,
clip: false,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -119,21 +118,30 @@ where
self
}
- /// Sets the style variant of this [`Button`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
- self
- }
-
/// Sets whether the contents of the [`Button`] should be clipped on
/// overflow.
pub fn clip(mut self, clip: bool) -> Self {
self.clip = clip;
self
}
+
+ /// Sets the style of the [`Button`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Button`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -146,6 +154,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + crate::core::Renderer,
+ Theme: Catalog,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@@ -304,19 +313,19 @@ where
Status::Active
};
- let styling = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
- if styling.background.is_some()
- || styling.border.width > 0.0
- || styling.shadow.color.a > 0.0
+ if style.background.is_some()
+ || style.border.width > 0.0
+ || style.shadow.color.a > 0.0
{
renderer.fill_quad(
renderer::Quad {
bounds,
- border: styling.border,
- shadow: styling.shadow,
+ border: style.border,
+ shadow: style.shadow,
},
- styling
+ style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
@@ -333,7 +342,7 @@ where
renderer,
theme,
&renderer::Style {
- text_color: styling.text_color,
+ text_color: style.text_color,
},
content_layout,
cursor,
@@ -378,7 +387,7 @@ impl<'a, Message, Theme, Renderer> From<Button<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: crate::core::Renderer + 'a,
{
fn from(button: Button<'a, Message, Theme, Renderer>) -> Self {
@@ -407,9 +416,9 @@ pub enum Status {
Disabled,
}
-/// The appearance of a button.
+/// The style of a button.
#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the button.
pub background: Option<Background>,
/// The text [`Color`] of the button.
@@ -420,8 +429,8 @@ pub struct Appearance {
pub shadow: Shadow,
}
-impl Appearance {
- /// Updates the [`Appearance`] with the given [`Background`].
+impl Style {
+ /// Updates the [`Style`] with the given [`Background`].
pub fn with_background(self, background: impl Into<Background>) -> Self {
Self {
background: Some(background.into()),
@@ -430,7 +439,7 @@ impl Appearance {
}
}
-impl std::default::Default for Appearance {
+impl Default for Style {
fn default() -> Self {
Self {
background: None,
@@ -441,41 +450,41 @@ impl std::default::Default for Appearance {
}
}
-/// The style of a [`Button`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`Button`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Button`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Button`].
- fn default_style(&self, status: Status) -> Appearance;
-}
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- primary(self, status)
- }
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+/// A styling function for a [`Button`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(primary)
}
-}
-impl DefaultStyle for Color {
- fn default_style(&self, _status: Status) -> Appearance {
- Appearance::default().with_background(*self)
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// A primary button; denoting a main action.
-pub fn primary(theme: &Theme, status: Status) -> Appearance {
+pub fn primary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let base = styled(palette.primary.strong);
match status {
Status::Active | Status::Pressed => base,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
background: Some(Background::Color(palette.primary.base.color)),
..base
},
@@ -484,13 +493,13 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance {
}
/// A secondary button; denoting a complementary action.
-pub fn secondary(theme: &Theme, status: Status) -> Appearance {
+pub fn secondary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let base = styled(palette.secondary.base);
match status {
Status::Active | Status::Pressed => base,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
background: Some(Background::Color(palette.secondary.strong.color)),
..base
},
@@ -499,13 +508,13 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance {
}
/// A success button; denoting a good outcome.
-pub fn success(theme: &Theme, status: Status) -> Appearance {
+pub fn success(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let base = styled(palette.success.base);
match status {
Status::Active | Status::Pressed => base,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
background: Some(Background::Color(palette.success.strong.color)),
..base
},
@@ -514,13 +523,13 @@ pub fn success(theme: &Theme, status: Status) -> Appearance {
}
/// A danger button; denoting a destructive action.
-pub fn danger(theme: &Theme, status: Status) -> Appearance {
+pub fn danger(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let base = styled(palette.danger.base);
match status {
Status::Active | Status::Pressed => base,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
background: Some(Background::Color(palette.danger.strong.color)),
..base
},
@@ -529,17 +538,17 @@ pub fn danger(theme: &Theme, status: Status) -> Appearance {
}
/// A text button; useful for links.
-pub fn text(theme: &Theme, status: Status) -> Appearance {
+pub fn text(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
- let base = Appearance {
+ let base = Style {
text_color: palette.background.base.text,
- ..Appearance::default()
+ ..Style::default()
};
match status {
Status::Active | Status::Pressed => base,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
text_color: palette.background.base.text.scale_alpha(0.8),
..base
},
@@ -547,21 +556,21 @@ pub fn text(theme: &Theme, status: Status) -> Appearance {
}
}
-fn styled(pair: palette::Pair) -> Appearance {
- Appearance {
+fn styled(pair: palette::Pair) -> Style {
+ Style {
background: Some(Background::Color(pair.color)),
text_color: pair.text,
border: Border::rounded(2),
- ..Appearance::default()
+ ..Style::default()
}
}
-fn disabled(appearance: Appearance) -> Appearance {
- Appearance {
- background: appearance
+fn disabled(style: Style) -> Style {
+ Style {
+ background: style
.background
.map(|background| background.scale_alpha(0.5)),
- text_color: appearance.text_color.scale_alpha(0.5),
- ..appearance
+ text_color: style.text_color.scale_alpha(0.5),
+ ..style
}
}
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 0eda0191..7a21895a 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -6,8 +6,10 @@ mod program;
pub use event::Event;
pub use program::Program;
-pub use crate::graphics::geometry::*;
-pub use crate::renderer::geometry::*;
+pub use crate::graphics::geometry::{
+ fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,
+ Path, Stroke, Style, Text,
+};
use crate::core;
use crate::core::layout::{self, Layout};
@@ -21,6 +23,19 @@ use crate::graphics::geometry;
use std::marker::PhantomData;
+/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
+///
+/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
+/// change or it is explicitly cleared.
+pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
+
+/// The geometry supported by a renderer.
+pub type Geometry<Renderer = crate::Renderer> =
+ <Renderer as geometry::Renderer>::Geometry;
+
+/// The frame supported by a renderer.
+pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
+
/// A widget capable of drawing 2D graphics.
///
/// ## Drawing a simple circle
@@ -42,7 +57,7 @@ use std::marker::PhantomData;
/// impl Program<()> for Circle {
/// type State = ();
///
-/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry>{
+/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> {
/// // We prepare a new `Frame`
/// let mut frame = Frame::new(renderer, bounds.size());
///
@@ -210,9 +225,12 @@ where
renderer.with_transformation(
Transformation::translate(bounds.x, bounds.y),
|renderer| {
- renderer.draw(
- self.program.draw(state, renderer, theme, bounds, cursor),
- );
+ let layers =
+ self.program.draw(state, renderer, theme, bounds, cursor);
+
+ for layer in layers {
+ renderer.draw_geometry(layer);
+ }
},
);
}
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index 0bff4bda..a7ded0f4 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -1,5 +1,6 @@
use crate::canvas::event::{self, Event};
use crate::canvas::mouse;
+use crate::canvas::Geometry;
use crate::core::Rectangle;
use crate::graphics::geometry;
@@ -52,7 +53,7 @@ where
theme: &Theme,
bounds: Rectangle,
cursor: mouse::Cursor,
- ) -> Vec<Renderer::Geometry>;
+ ) -> Vec<Geometry<Renderer>>;
/// Returns the current mouse interaction of the [`Program`].
///
@@ -94,7 +95,7 @@ where
theme: &Theme,
bounds: Rectangle,
cursor: mouse::Cursor,
- ) -> Vec<Renderer::Geometry> {
+ ) -> Vec<Geometry<Renderer>> {
T::draw(self, state, renderer, theme, bounds, cursor)
}
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index 15fb8f58..48f6abf6 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -39,6 +39,7 @@ pub struct Checkbox<
Renderer = crate::Renderer,
> where
Renderer: text::Renderer,
+ Theme: Catalog,
{
is_checked: bool,
on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>,
@@ -51,12 +52,13 @@ pub struct Checkbox<
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>
where
Renderer: text::Renderer,
+ Theme: Catalog,
{
/// The default size of a [`Checkbox`].
const DEFAULT_SIZE: f32 = 16.0;
@@ -69,10 +71,7 @@ where
/// It expects:
/// * the label of the [`Checkbox`]
/// * a boolean describing whether the [`Checkbox`] is checked or not
- pub fn new(label: impl Into<String>, is_checked: bool) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(label: impl Into<String>, is_checked: bool) -> Self {
Checkbox {
is_checked,
on_toggle: None,
@@ -91,7 +90,7 @@ where
line_height: text::LineHeight::default(),
shaping: text::Shaping::Basic,
},
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -174,11 +173,20 @@ where
}
/// Sets the style of the [`Checkbox`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Checkbox`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -187,6 +195,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Checkbox<'a, Message, Theme, Renderer>
where
Renderer: text::Renderer,
+ Theme: Catalog,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<widget::text::State<Renderer::Paragraph>>()
@@ -285,7 +294,7 @@ where
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
@@ -304,7 +313,7 @@ where
Status::Active { is_checked }
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
{
let layout = children.next().unwrap();
@@ -313,10 +322,10 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.border,
+ border: style.border,
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
let Icon {
@@ -341,7 +350,7 @@ where
shaping: *shaping,
},
bounds.center(),
- appearance.icon_color,
+ style.icon_color,
*viewport,
);
}
@@ -352,11 +361,11 @@ where
crate::text::draw(
renderer,
- style,
+ defaults,
label_layout,
tree.state.downcast_ref(),
- crate::text::Appearance {
- color: appearance.text_color,
+ crate::text::Style {
+ color: style.text_color,
},
viewport,
);
@@ -368,7 +377,7 @@ impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
+ Theme: 'a + Catalog,
Renderer: 'a + text::Renderer,
{
fn from(
@@ -413,9 +422,9 @@ pub enum Status {
},
}
-/// The appearance of a checkbox.
+/// The style of a checkbox.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the checkbox.
pub background: Background,
/// The icon [`Color`] of the checkbox.
@@ -426,29 +435,37 @@ pub struct Appearance {
pub text_color: Option<Color>,
}
-/// The style of a [`Checkbox`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`Checkbox`].
+pub trait Catalog: Sized {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Checkbox`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Checkbox`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- primary(self, status)
+/// A styling function for a [`Checkbox`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(primary)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// A primary checkbox; denoting a main toggle.
-pub fn primary(theme: &Theme, status: Status) -> Appearance {
+pub fn primary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
match status {
@@ -474,7 +491,7 @@ pub fn primary(theme: &Theme, status: Status) -> Appearance {
}
/// A secondary checkbox; denoting a complementary toggle.
-pub fn secondary(theme: &Theme, status: Status) -> Appearance {
+pub fn secondary(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
match status {
@@ -500,7 +517,7 @@ pub fn secondary(theme: &Theme, status: Status) -> Appearance {
}
/// A success checkbox; denoting a positive toggle.
-pub fn success(theme: &Theme, status: Status) -> Appearance {
+pub fn success(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
match status {
@@ -526,7 +543,7 @@ pub fn success(theme: &Theme, status: Status) -> Appearance {
}
/// A danger checkbox; denoting a negaive toggle.
-pub fn danger(theme: &Theme, status: Status) -> Appearance {
+pub fn danger(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
match status {
@@ -556,8 +573,8 @@ fn styled(
base: palette::Pair,
accent: palette::Pair,
is_checked: bool,
-) -> Appearance {
- Appearance {
+) -> Style {
+ Style {
background: Background::Color(if is_checked {
accent.color
} else {
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index ee24d742..e4f4a41f 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -32,6 +32,7 @@ pub struct ComboBox<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
+ Theme: Catalog,
Renderer: text::Renderer,
{
state: &'a State<T>,
@@ -42,7 +43,7 @@ pub struct ComboBox<
on_option_hovered: Option<Box<dyn Fn(T) -> Message>>,
on_close: Option<Message>,
on_input: Option<Box<dyn Fn(String) -> Message>>,
- menu_style: menu::Style<'a, Theme>,
+ menu_class: <Theme as menu::Catalog>::Class<'a>,
padding: Padding,
size: Option<f32>,
}
@@ -50,6 +51,7 @@ pub struct ComboBox<
impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>
where
T: std::fmt::Display + Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Creates a new [`ComboBox`] with the given list of options, a placeholder,
@@ -60,18 +62,10 @@ where
placeholder: &str,
selection: Option<&T>,
on_selected: impl Fn(T) -> Message + 'static,
- ) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
- let style = Theme::default_style();
-
- let text_input = TextInput::with_style(
- placeholder,
- &state.value(),
- style.text_input,
- )
- .on_input(TextInputEvent::TextChanged);
+ ) -> Self {
+ let text_input = TextInput::new(placeholder, &state.value())
+ .on_input(TextInputEvent::TextChanged)
+ .class(Theme::default_input());
let selection = selection.map(T::to_string).unwrap_or_default();
@@ -84,7 +78,7 @@ where
on_option_hovered: None,
on_input: None,
on_close: None,
- menu_style: style.menu,
+ menu_class: <Theme as Catalog>::default_menu(),
padding: text_input::DEFAULT_PADDING,
size: None,
}
@@ -124,18 +118,6 @@ where
self
}
- /// Sets the style of the [`ComboBox`].
- pub fn style(mut self, style: impl Into<Style<'a, Theme>>) -> Self
- where
- Theme: 'a,
- {
- let style = style.into();
-
- self.text_input = self.text_input.style(style.text_input);
- self.menu_style = style.menu;
- self
- }
-
/// Sets the [`Renderer::Font`] of the [`ComboBox`].
///
/// [`Renderer::Font`]: text::Renderer
@@ -173,6 +155,55 @@ where
..self
}
}
+
+ /// Sets the style of the input of the [`ComboBox`].
+ #[must_use]
+ pub fn input_style(
+ mut self,
+ style: impl Fn(&Theme, text_input::Status) -> text_input::Style + 'a,
+ ) -> Self
+ where
+ <Theme as text_input::Catalog>::Class<'a>:
+ From<text_input::StyleFn<'a, Theme>>,
+ {
+ self.text_input = self.text_input.style(style);
+ self
+ }
+
+ /// Sets the style of the menu of the [`ComboBox`].
+ #[must_use]
+ pub fn menu_style(
+ mut self,
+ style: impl Fn(&Theme) -> menu::Style + 'a,
+ ) -> Self
+ where
+ <Theme as menu::Catalog>::Class<'a>: From<menu::StyleFn<'a, Theme>>,
+ {
+ self.menu_class = (Box::new(style) as menu::StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the input of the [`ComboBox`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn input_class(
+ mut self,
+ class: impl Into<<Theme as text_input::Catalog>::Class<'a>>,
+ ) -> Self {
+ self.text_input = self.text_input.class(class);
+ self
+ }
+
+ /// Sets the style class of the menu of the [`ComboBox`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn menu_class(
+ mut self,
+ class: impl Into<<Theme as menu::Catalog>::Class<'a>>,
+ ) -> Self {
+ self.menu_class = class.into();
+ self
+ }
}
/// The local state of a [`ComboBox`].
@@ -296,6 +327,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn size(&self) -> Size<Length> {
@@ -686,7 +718,7 @@ where
(self.on_selected)(x)
},
self.on_option_hovered.as_deref(),
- &self.menu_style,
+ &self.menu_class,
)
.width(bounds.width)
.padding(self.padding);
@@ -712,7 +744,7 @@ impl<'a, T, Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone + 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self {
@@ -720,6 +752,21 @@ where
}
}
+/// The theme catalog of a [`ComboBox`].
+pub trait Catalog: text_input::Catalog + menu::Catalog {
+ /// The default class for the text input of the [`ComboBox`].
+ fn default_input<'a>() -> <Self as text_input::Catalog>::Class<'a> {
+ <Self as text_input::Catalog>::default()
+ }
+
+ /// The default class for the menu of the [`ComboBox`].
+ fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> {
+ <Self as menu::Catalog>::default()
+ }
+}
+
+impl Catalog for Theme {}
+
fn search<'a, T, A>(
options: impl IntoIterator<Item = T> + 'a,
option_matchers: impl IntoIterator<Item = &'a A> + 'a,
@@ -762,30 +809,3 @@ where
})
.collect()
}
-
-/// The style of a [`ComboBox`].
-#[allow(missing_debug_implementations)]
-pub struct Style<'a, Theme> {
- /// The style of the [`TextInput`] of the [`ComboBox`].
- pub text_input: text_input::Style<'a, Theme>,
-
- /// The style of the [`Menu`] of the [`ComboBox`].
- ///
- /// [`Menu`]: menu::Menu
- pub menu: menu::Style<'a, Theme>,
-}
-
-/// The default style of a [`ComboBox`].
-pub trait DefaultStyle: Sized {
- /// Returns the default style of a [`ComboBox`].
- fn default_style() -> Style<'static, Self>;
-}
-
-impl DefaultStyle for Theme {
- fn default_style() -> Style<'static, Self> {
- Style {
- text_input: Box::new(text_input::default),
- menu: menu::DefaultStyle::default_style(),
- }
- }
-}
diff --git a/widget/src/container.rs b/widget/src/container.rs
index 7c133588..21405722 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -9,8 +9,9 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Operation};
use crate::core::{
- Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
- Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
+ self, Background, Border, Clipboard, Color, Element, Layout, Length,
+ Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
+ Widget,
};
use crate::runtime::Command;
@@ -24,7 +25,8 @@ pub struct Container<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
id: Option<Id>,
padding: Padding,
@@ -36,27 +38,17 @@ pub struct Container<
vertical_alignment: alignment::Vertical,
clip: bool,
content: Element<'a, Message, Theme, Renderer>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
/// Creates a [`Container`] with the given content.
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
- ) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
- Self::with_style(content, Theme::default_style)
- }
-
- /// Creates a [`Container`] with the given content and style.
- pub fn with_style(
- content: impl Into<Element<'a, Message, Theme, Renderer>>,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
) -> Self {
let content = content.into();
let size = content.as_widget().size_hint();
@@ -71,7 +63,7 @@ where
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
clip: false,
- style: Box::new(style),
+ class: Theme::default(),
content,
}
}
@@ -136,27 +128,37 @@ where
self
}
- /// Sets the style of the [`Container`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
- self
- }
-
/// Sets whether the contents of the [`Container`] should be clipped on
/// overflow.
pub fn clip(mut self, clip: bool) -> Self {
self.clip = clip;
self
}
+
+ /// Sets the style of the [`Container`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Container`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Container<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
self.content.as_widget().tag()
@@ -272,14 +274,7 @@ where
viewport: &Rectangle,
) {
let bounds = layout.bounds();
-
- let status = if cursor.is_over(bounds) {
- Status::Hovered
- } else {
- Status::Idle
- };
-
- let style = (self.style)(theme, status);
+ let style = theme.style(&self.class);
if let Some(clipped_viewport) = bounds.intersection(viewport) {
draw_background(renderer, &style, bounds);
@@ -324,8 +319,8 @@ impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
- Renderer: 'a + crate::core::Renderer,
+ Theme: Catalog + 'a,
+ Renderer: core::Renderer + 'a,
{
fn from(
column: Container<'a, Message, Theme, Renderer>,
@@ -362,25 +357,25 @@ pub fn layout(
)
}
-/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.
+/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
pub fn draw_background<Renderer>(
renderer: &mut Renderer,
- appearance: &Appearance,
+ style: &Style,
bounds: Rectangle,
) where
- Renderer: crate::core::Renderer,
+ Renderer: core::Renderer,
{
- if appearance.background.is_some()
- || appearance.border.width > 0.0
- || appearance.shadow.color.a > 0.0
+ if style.background.is_some()
+ || style.border.width > 0.0
+ || style.shadow.color.a > 0.0
{
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.border,
- shadow: appearance.shadow,
+ border: style.border,
+ shadow: style.shadow,
},
- appearance
+ style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
@@ -502,7 +497,7 @@ pub fn visible_bounds(id: Id) -> Command<Option<Rectangle>> {
/// The appearance of a container.
#[derive(Debug, Clone, Copy, Default)]
-pub struct Appearance {
+pub struct Style {
/// The text [`Color`] of the container.
pub text_color: Option<Color>,
/// The [`Background`] of the container.
@@ -513,8 +508,8 @@ pub struct Appearance {
pub shadow: Shadow,
}
-impl Appearance {
- /// Updates the border of the [`Appearance`] with the given [`Color`] and `width`.
+impl Style {
+ /// Updates the border of the [`Style`] with the given [`Color`] and `width`.
pub fn with_border(
self,
color: impl Into<Color>,
@@ -530,7 +525,7 @@ impl Appearance {
}
}
- /// Updates the background of the [`Appearance`].
+ /// Updates the background of the [`Style`].
pub fn with_background(self, background: impl Into<Background>) -> Self {
Self {
background: Some(background.into()),
@@ -539,99 +534,78 @@ impl Appearance {
}
}
-impl From<Color> for Appearance {
+impl From<Color> for Style {
fn from(color: Color) -> Self {
Self::default().with_background(color)
}
}
-impl From<Gradient> for Appearance {
+impl From<Gradient> for Style {
fn from(gradient: Gradient) -> Self {
Self::default().with_background(gradient)
}
}
-impl From<gradient::Linear> for Appearance {
+impl From<gradient::Linear> for Style {
fn from(gradient: gradient::Linear) -> Self {
Self::default().with_background(gradient)
}
}
-/// The possible status of a [`Container`].
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Status {
- /// The [`Container`] is idle.
- Idle,
- /// The [`Container`] is being hovered.
- Hovered,
-}
+/// The theme catalog of a [`Container`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The style of a [`Container`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
-/// The default style of a [`Container`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Container`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- transparent(self, status)
- }
-}
+/// A styling function for a [`Container`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
- }
-}
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
-impl DefaultStyle for Color {
- fn default_style(&self, _status: Status) -> Appearance {
- Appearance::from(*self)
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(transparent)
}
-}
-
-impl DefaultStyle for Gradient {
- fn default_style(&self, _status: Status) -> Appearance {
- Appearance::from(*self)
- }
-}
-impl DefaultStyle for gradient::Linear {
- fn default_style(&self, _status: Status) -> Appearance {
- Appearance::from(*self)
+ fn style(&self, class: &Self::Class<'_>) -> Style {
+ class(self)
}
}
/// A transparent [`Container`].
-pub fn transparent<Theme>(_theme: &Theme, _status: Status) -> Appearance {
- Appearance::default()
+pub fn transparent<Theme>(_theme: &Theme) -> Style {
+ Style::default()
}
/// A rounded [`Container`] with a background.
-pub fn rounded_box(theme: &Theme, _status: Status) -> Appearance {
+pub fn rounded_box(theme: &Theme) -> Style {
let palette = theme.extended_palette();
- Appearance {
+ Style {
background: Some(palette.background.weak.color.into()),
border: Border::rounded(2),
- ..Appearance::default()
+ ..Style::default()
}
}
/// A bordered [`Container`] with a background.
-pub fn bordered_box(theme: &Theme, _status: Status) -> Appearance {
+pub fn bordered_box(theme: &Theme) -> Style {
let palette = theme.extended_palette();
- Appearance {
+ Style {
background: Some(palette.background.weak.color.into()),
border: Border {
width: 1.0,
radius: 0.0.into(),
color: palette.background.strong.color,
},
- ..Appearance::default()
+ ..Style::default()
}
}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 4863e550..77b30882 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -7,6 +7,7 @@ use crate::core;
use crate::core::widget::operation;
use crate::core::{Element, Length, Pixels};
use crate::keyed;
+use crate::overlay;
use crate::pick_list::{self, PickList};
use crate::progress_bar::{self, ProgressBar};
use crate::radio::{self, Radio};
@@ -58,7 +59,7 @@ pub fn container<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Container<'a, Message, Theme, Renderer>
where
- Theme: container::DefaultStyle + 'a,
+ Theme: container::Catalog + 'a,
Renderer: core::Renderer,
{
Container::new(content)
@@ -104,7 +105,7 @@ pub fn scrollable<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Scrollable<'a, Message, Theme, Renderer>
where
- Theme: scrollable::DefaultStyle + 'a,
+ Theme: scrollable::Catalog + 'a,
Renderer: core::Renderer,
{
Scrollable::new(content)
@@ -117,7 +118,7 @@ pub fn button<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Button<'a, Message, Theme, Renderer>
where
- Theme: button::DefaultStyle + 'a,
+ Theme: button::Catalog + 'a,
Renderer: core::Renderer,
{
Button::new(content)
@@ -134,7 +135,7 @@ pub fn tooltip<'a, Message, Theme, Renderer>(
position: tooltip::Position,
) -> crate::Tooltip<'a, Message, Theme, Renderer>
where
- Theme: container::DefaultStyle + 'a,
+ Theme: container::Catalog + 'a,
Renderer: core::text::Renderer,
{
Tooltip::new(content, tooltip, position)
@@ -147,7 +148,7 @@ pub fn text<'a, Theme, Renderer>(
text: impl ToString,
) -> Text<'a, Theme, Renderer>
where
- Theme: text::DefaultStyle + 'a,
+ Theme: text::Catalog + 'a,
Renderer: core::text::Renderer,
{
Text::new(text.to_string())
@@ -161,7 +162,7 @@ pub fn checkbox<'a, Message, Theme, Renderer>(
is_checked: bool,
) -> Checkbox<'a, Message, Theme, Renderer>
where
- Theme: checkbox::DefaultStyle + 'a,
+ Theme: checkbox::Catalog + 'a,
Renderer: core::text::Renderer,
{
Checkbox::new(label, is_checked)
@@ -178,7 +179,7 @@ pub fn radio<'a, Message, Theme, Renderer, V>(
) -> Radio<'a, Message, Theme, Renderer>
where
Message: Clone,
- Theme: radio::DefaultStyle + 'a,
+ Theme: radio::Catalog + 'a,
Renderer: core::text::Renderer,
V: Copy + Eq,
{
@@ -194,7 +195,7 @@ pub fn toggler<'a, Message, Theme, Renderer>(
f: impl Fn(bool) -> Message + 'a,
) -> Toggler<'a, Message, Theme, Renderer>
where
- Theme: toggler::DefaultStyle + 'a,
+ Theme: toggler::Catalog + 'a,
Renderer: core::text::Renderer,
{
Toggler::new(label, is_checked, f)
@@ -209,7 +210,7 @@ pub fn text_input<'a, Message, Theme, Renderer>(
) -> TextInput<'a, Message, Theme, Renderer>
where
Message: Clone,
- Theme: text_input::DefaultStyle + 'a,
+ Theme: text_input::Catalog + 'a,
Renderer: core::text::Renderer,
{
TextInput::new(placeholder, value)
@@ -223,7 +224,7 @@ pub fn text_editor<'a, Message, Theme, Renderer>(
) -> TextEditor<'a, core::text::highlighter::PlainText, Message, Theme, Renderer>
where
Message: Clone,
- Theme: text_editor::DefaultStyle + 'a,
+ Theme: text_editor::Catalog + 'a,
Renderer: core::text::Renderer,
{
TextEditor::new(content)
@@ -240,7 +241,7 @@ pub fn slider<'a, T, Message, Theme>(
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
- Theme: slider::DefaultStyle + 'a,
+ Theme: slider::Catalog + 'a,
{
Slider::new(range, value, on_change)
}
@@ -256,7 +257,7 @@ pub fn vertical_slider<'a, T, Message, Theme>(
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
- Theme: vertical_slider::DefaultStyle + 'a,
+ Theme: vertical_slider::Catalog + 'a,
{
VerticalSlider::new(range, value, on_change)
}
@@ -274,7 +275,7 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
- Theme: pick_list::DefaultStyle,
+ Theme: pick_list::Catalog + overlay::menu::Catalog,
Renderer: core::text::Renderer,
{
PickList::new(options, selected, on_selected)
@@ -291,7 +292,7 @@ pub fn combo_box<'a, T, Message, Theme, Renderer>(
) -> ComboBox<'a, T, Message, Theme, Renderer>
where
T: std::fmt::Display + Clone,
- Theme: combo_box::DefaultStyle + 'a,
+ Theme: combo_box::Catalog + 'a,
Renderer: core::text::Renderer,
{
ComboBox::new(state, placeholder, selection, on_selected)
@@ -318,7 +319,7 @@ pub fn vertical_space() -> Space {
/// [`Rule`]: crate::Rule
pub fn horizontal_rule<'a, Theme>(height: impl Into<Pixels>) -> Rule<'a, Theme>
where
- Theme: rule::DefaultStyle + 'a,
+ Theme: rule::Catalog + 'a,
{
Rule::horizontal(height)
}
@@ -328,7 +329,7 @@ where
/// [`Rule`]: crate::Rule
pub fn vertical_rule<'a, Theme>(width: impl Into<Pixels>) -> Rule<'a, Theme>
where
- Theme: rule::DefaultStyle + 'a,
+ Theme: rule::Catalog + 'a,
{
Rule::vertical(width)
}
@@ -345,7 +346,7 @@ pub fn progress_bar<'a, Theme>(
value: f32,
) -> ProgressBar<'a, Theme>
where
- Theme: progress_bar::DefaultStyle + 'a,
+ Theme: progress_bar::Catalog + 'a,
{
ProgressBar::new(range, value)
}
@@ -367,7 +368,7 @@ pub fn svg<'a, Theme>(
handle: impl Into<core::svg::Handle>,
) -> crate::Svg<'a, Theme>
where
- Theme: crate::svg::DefaultStyle + 'a,
+ Theme: crate::svg::Catalog,
{
crate::Svg::new(handle)
}
@@ -395,7 +396,7 @@ pub fn qr_code<'a, Theme>(
data: &'a crate::qr_code::Data,
) -> crate::QRCode<'a, Theme>
where
- Theme: crate::qr_code::DefaultStyle + 'a,
+ Theme: crate::qr_code::Catalog + 'a,
{
crate::QRCode::new(data)
}
diff --git a/widget/src/image.rs b/widget/src/image.rs
index ccf1f175..f673c7b3 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -93,7 +93,7 @@ where
{
// The raw w/h of the underlying image
let image_size = {
- let Size { width, height } = renderer.dimensions(handle);
+ let Size { width, height } = renderer.measure_image(handle);
Size::new(width as f32, height as f32)
};
@@ -130,7 +130,7 @@ pub fn draw<Renderer, Handle>(
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
- let Size { width, height } = renderer.dimensions(handle);
+ let Size { width, height } = renderer.measure_image(handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
@@ -148,7 +148,11 @@ pub fn draw<Renderer, Handle>(
..bounds
};
- renderer.draw(handle.clone(), filter_method, drawing_bounds + offset);
+ renderer.draw_image(
+ handle.clone(),
+ filter_method,
+ drawing_bounds + offset,
+ );
};
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index bd10e953..fba00028 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -127,7 +127,7 @@ where
limits: &layout::Limits,
) -> layout::Node {
let image_size = {
- let Size { width, height } = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.measure_image(&self.handle);
Size::new(width as f32, height as f32)
};
let raw_size = limits.resolve(self.width, self.height, image_size);
@@ -344,8 +344,7 @@ where
height: image_size.height,
..bounds
};
-
- renderer.draw(
+ renderer.draw_image(
self.handle.clone(),
self.filter_method,
drawing_bounds,
@@ -429,7 +428,7 @@ pub fn image_size<Renderer>(
where
Renderer: image::Renderer,
{
- let size = renderer.dimensions(handle);
+ let size = renderer.measure_image(handle);
let size = Size::new(size.width as f32, size.height as f32);
let size = content_fit.fit(size, bounds);
diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index 0364f980..d76caa8a 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -1,5 +1,4 @@
//! Build and show dropdown menus.
-use crate::container::{self, Container};
use crate::core::alignment;
use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
@@ -20,12 +19,15 @@ use crate::scrollable::{self, Scrollable};
#[allow(missing_debug_implementations)]
pub struct Menu<
'a,
+ 'b,
T,
Message,
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
+ Theme: Catalog,
Renderer: text::Renderer,
+ 'b: 'a,
{
state: &'a mut State,
options: &'a [T],
@@ -38,15 +40,17 @@ pub struct Menu<
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
- style: &'a Style<'a, Theme>,
+ class: &'a <Theme as Catalog>::Class<'b>,
}
-impl<'a, T, Message, Theme, Renderer> Menu<'a, T, Message, Theme, Renderer>
+impl<'a, 'b, T, Message, Theme, Renderer>
+ Menu<'a, 'b, T, Message, Theme, Renderer>
where
T: ToString + Clone,
Message: 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
+ 'b: 'a,
{
/// Creates a new [`Menu`] with the given [`State`], a list of options,
/// the message to produced when an option is selected, and its [`Style`].
@@ -56,7 +60,7 @@ where
hovered_option: &'a mut Option<usize>,
on_selected: impl FnMut(T) -> Message + 'a,
on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
- style: &'a Style<'a, Theme>,
+ class: &'a <Theme as Catalog>::Class<'b>,
) -> Self {
Menu {
state,
@@ -70,7 +74,7 @@ where
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
- style,
+ class,
}
}
@@ -153,27 +157,29 @@ impl Default for State {
}
}
-struct Overlay<'a, Message, Theme, Renderer>
+struct Overlay<'a, 'b, Message, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: crate::core::Renderer,
{
position: Point,
state: &'a mut Tree,
- container: Container<'a, Message, Theme, Renderer>,
+ list: Scrollable<'a, Message, Theme, Renderer>,
width: f32,
target_height: f32,
- style: &'a Style<'a, Theme>,
+ class: &'a <Theme as Catalog>::Class<'b>,
}
-impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer>
+impl<'a, 'b, Message, Theme, Renderer> Overlay<'a, 'b, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
+ Theme: Catalog + scrollable::Catalog + 'a,
Renderer: text::Renderer + 'a,
+ 'b: 'a,
{
pub fn new<T>(
position: Point,
- menu: Menu<'a, T, Message, Theme, Renderer>,
+ menu: Menu<'a, 'b, T, Message, Theme, Renderer>,
target_height: f32,
) -> Self
where
@@ -191,46 +197,43 @@ where
text_size,
text_line_height,
text_shaping,
- style,
+ class,
} = menu;
- let container = Container::with_style(
- Scrollable::with_direction_and_style(
- List {
- options,
- hovered_option,
- on_selected,
- on_option_hovered,
- font,
- text_size,
- text_line_height,
- text_shaping,
- padding,
- style: &style.list,
- },
- scrollable::Direction::default(),
- &style.scrollable,
- ),
- container::transparent,
+ let list = Scrollable::with_direction(
+ List {
+ options,
+ hovered_option,
+ on_selected,
+ on_option_hovered,
+ font,
+ text_size,
+ text_line_height,
+ text_shaping,
+ padding,
+ class,
+ },
+ scrollable::Direction::default(),
);
- state.tree.diff(&container as &dyn Widget<_, _, _>);
+ state.tree.diff(&list as &dyn Widget<_, _, _>);
Self {
position,
state: &mut state.tree,
- container,
+ list,
width,
target_height,
- style,
+ class,
}
}
}
-impl<'a, Message, Theme, Renderer>
+impl<'a, 'b, Message, Theme, Renderer>
crate::core::Overlay<Message, Theme, Renderer>
- for Overlay<'a, Message, Theme, Renderer>
+ for Overlay<'a, 'b, Message, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@@ -251,7 +254,7 @@ where
)
.width(self.width);
- let node = self.container.layout(self.state, renderer, &limits);
+ let node = self.list.layout(self.state, renderer, &limits);
let size = node.size();
node.move_to(if space_below > space_above {
@@ -272,7 +275,7 @@ where
) -> event::Status {
let bounds = layout.bounds();
- self.container.on_event(
+ self.list.on_event(
self.state, event, layout, cursor, renderer, clipboard, shell,
&bounds,
)
@@ -285,7 +288,7 @@ where
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.container
+ self.list
.mouse_interaction(self.state, layout, cursor, viewport, renderer)
}
@@ -293,30 +296,32 @@ where
&self,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
let bounds = layout.bounds();
- let appearance = (self.style.list)(theme);
+ let style = Catalog::style(theme, self.class);
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.border,
+ border: style.border,
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
- self.container
- .draw(self.state, renderer, theme, style, layout, cursor, &bounds);
+ self.list.draw(
+ self.state, renderer, theme, defaults, layout, cursor, &bounds,
+ );
}
}
-struct List<'a, T, Message, Theme, Renderer>
+struct List<'a, 'b, T, Message, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
options: &'a [T],
@@ -328,13 +333,14 @@ where
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
- style: &'a dyn Fn(&Theme) -> Appearance,
+ class: &'a <Theme as Catalog>::Class<'b>,
}
-impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for List<'a, T, Message, Theme, Renderer>
+impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for List<'a, 'b, T, Message, Theme, Renderer>
where
T: Clone + ToString,
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn size(&self) -> Size<Length> {
@@ -477,7 +483,7 @@ where
_cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- let appearance = (self.style)(theme);
+ let style = Catalog::style(theme, self.class);
let bounds = layout.bounds();
let text_size =
@@ -507,14 +513,14 @@ where
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: bounds.x + appearance.border.width,
- width: bounds.width - appearance.border.width * 2.0,
+ x: bounds.x + style.border.width,
+ width: bounds.width - style.border.width * 2.0,
..bounds
},
- border: Border::rounded(appearance.border.radius),
+ border: Border::rounded(style.border.radius),
..renderer::Quad::default()
},
- appearance.selected_background,
+ style.selected_background,
);
}
@@ -531,9 +537,9 @@ where
},
Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected {
- appearance.selected_text_color
+ style.selected_text_color
} else {
- appearance.text_color
+ style.text_color
},
*viewport,
);
@@ -541,23 +547,24 @@ where
}
}
-impl<'a, T, Message, Theme, Renderer>
- From<List<'a, T, Message, Theme, Renderer>>
+impl<'a, 'b, T, Message, Theme, Renderer>
+ From<List<'a, 'b, T, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
T: ToString + Clone,
Message: 'a,
- Theme: 'a,
+ Theme: 'a + Catalog,
Renderer: 'a + text::Renderer,
+ 'b: 'a,
{
- fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self {
+ fn from(list: List<'a, 'b, T, Message, Theme, Renderer>) -> Self {
Element::new(list)
}
}
/// The appearance of a [`Menu`].
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the menu.
pub background: Background,
/// The [`Border`] of the menu.
@@ -570,35 +577,43 @@ pub struct Appearance {
pub selected_background: Background,
}
-/// The style of the different parts of a [`Menu`].
-#[allow(missing_debug_implementations)]
-pub struct Style<'a, Theme> {
- /// The style of the list of the [`Menu`].
- pub list: Box<dyn Fn(&Theme) -> Appearance + 'a>,
- /// The style of the [`Scrollable`] of the [`Menu`].
- pub scrollable: scrollable::Style<'a, Theme>,
-}
+/// The theme catalog of a [`Menu`].
+pub trait Catalog: scrollable::Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
+
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> <Self as Catalog>::Class<'a>;
-/// The default style of a [`Menu`].
-pub trait DefaultStyle: Sized {
- /// Returns the default style of a [`Menu`].
- fn default_style() -> Style<'static, Self>;
+ /// The default class for the scrollable of the [`Menu`].
+ fn default_scrollable<'a>() -> <Self as scrollable::Catalog>::Class<'a> {
+ <Self as scrollable::Catalog>::default()
+ }
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style() -> Style<'static, Self> {
- Style {
- list: Box::new(default),
- scrollable: Box::new(scrollable::default),
- }
+/// A styling function for a [`Menu`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> StyleFn<'a, Self> {
+ Box::new(default)
+ }
+
+ fn style(&self, class: &StyleFn<'_, Self>) -> Style {
+ class(self)
}
}
/// The default style of the list of a [`Menu`].
-pub fn default(theme: &Theme) -> Appearance {
+pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette();
- Appearance {
+ Style {
background: palette.background.weak.color.into(),
border: Border {
width: 1.0,
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index beac0bd8..acfa9d44 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -30,6 +30,7 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
+use crate::container;
use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
@@ -39,8 +40,8 @@ use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
- Point, Rectangle, Shell, Size, Theme, Vector, Widget,
+ self, Background, Border, Clipboard, Color, Element, Layout, Length,
+ Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
const DRAG_DEADBAND_DISTANCE: f32 = 10.0;
@@ -101,7 +102,8 @@ pub struct PaneGrid<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
contents: Contents<'a, Content<'a, Message, Theme, Renderer>>,
width: Length,
@@ -110,12 +112,13 @@ pub struct PaneGrid<
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
- style: Style<'a, Theme>,
+ class: <Theme as Catalog>::Class<'a>,
}
impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
@@ -124,10 +127,7 @@ where
pub fn new<T>(
state: &'a State<T>,
view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>,
- ) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ ) -> Self {
let contents = if let Some((pane, pane_state)) =
state.maximized.and_then(|pane| {
state.panes.get(&pane).map(|pane_state| (pane, pane_state))
@@ -158,7 +158,7 @@ where
on_click: None,
on_drag: None,
on_resize: None,
- style: Box::new(Theme::default_style),
+ class: <Theme as Catalog>::default(),
}
}
@@ -218,8 +218,23 @@ where
}
/// Sets the style of the [`PaneGrid`].
- pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`PaneGrid`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(
+ mut self,
+ class: impl Into<<Theme as Catalog>::Class<'a>>,
+ ) -> Self {
+ self.class = class.into();
self
}
@@ -233,7 +248,8 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for PaneGrid<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<state::Action>()
@@ -596,7 +612,7 @@ where
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
@@ -677,7 +693,7 @@ where
None
};
- let appearance = (self.style)(theme);
+ let style = Catalog::style(theme, &self.class);
for ((id, (content, tree)), pane_layout) in
contents.zip(layout.children())
@@ -692,7 +708,7 @@ where
tree,
renderer,
theme,
- style,
+ defaults,
pane_layout,
pane_cursor,
viewport,
@@ -710,10 +726,10 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.hovered_region.border,
+ border: style.hovered_region.border,
..renderer::Quad::default()
},
- appearance.hovered_region.background,
+ style.hovered_region.background,
);
}
}
@@ -723,7 +739,7 @@ where
tree,
renderer,
theme,
- style,
+ defaults,
pane_layout,
pane_cursor,
viewport,
@@ -738,10 +754,10 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.hovered_region.border,
+ border: style.hovered_region.border,
..renderer::Quad::default()
},
- appearance.hovered_region.background,
+ style.hovered_region.background,
);
}
@@ -759,7 +775,7 @@ where
tree,
renderer,
theme,
- style,
+ defaults,
layout,
pane_cursor,
viewport,
@@ -772,9 +788,9 @@ where
if picked_pane.is_none() {
if let Some((axis, split_region, is_picked)) = picked_split {
let highlight = if is_picked {
- appearance.picked_split
+ style.picked_split
} else {
- appearance.hovered_split
+ style.hovered_split
};
renderer.fill_quad(
@@ -832,8 +848,8 @@ impl<'a, Message, Theme, Renderer> From<PaneGrid<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
- Renderer: crate::core::Renderer + 'a,
+ Theme: Catalog + 'a,
+ Renderer: core::Renderer + 'a,
{
fn from(
pane_grid: PaneGrid<'a, Message, Theme, Renderer>,
@@ -1116,7 +1132,7 @@ impl<'a, T> Contents<'a, T> {
/// The appearance of a [`PaneGrid`].
#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Appearance {
+pub struct Style {
/// The appearance of a hovered region highlight.
pub hovered_region: Highlight,
/// The appearance of a picked split.
@@ -1145,32 +1161,40 @@ pub struct Line {
pub width: f32,
}
-/// The style of a [`PaneGrid`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>;
+/// The theme catalog of a [`PaneGrid`].
+pub trait Catalog: container::Catalog {
+ /// The item class of this [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`PaneGrid`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`PaneGrid`].
- fn default_style(&self) -> Appearance;
+ /// The default class produced by this [`Catalog`].
+ fn default<'a>() -> <Self as Catalog>::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self) -> Appearance {
- default(self)
+/// A styling function for a [`PaneGrid`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> StyleFn<'a, Self> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self) -> Appearance {
- *self
+ fn style(&self, class: &StyleFn<'_, Self>) -> Style {
+ class(self)
}
}
/// The default style of a [`PaneGrid`].
-pub fn default(theme: &Theme) -> Appearance {
+pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette();
- Appearance {
+ Style {
hovered_region: Highlight {
background: Background::Color(Color {
a: 0.5,
diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs
index 98f4f99a..30ad52ca 100644
--- a/widget/src/pane_grid/content.rs
+++ b/widget/src/pane_grid/content.rs
@@ -6,7 +6,7 @@ use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{self, Tree};
use crate::core::{
- Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
+ self, Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
};
use crate::pane_grid::{Draggable, TitleBar};
@@ -20,30 +20,29 @@ pub struct Content<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
title_bar: Option<TitleBar<'a, Message, Theme, Renderer>>,
body: Element<'a, Message, Theme, Renderer>,
- style: container::Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
/// Creates a new [`Content`] with the provided body.
- pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self
- where
- Theme: container::DefaultStyle + 'a,
- {
+ pub fn new(body: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
Self {
title_bar: None,
body: body.into(),
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
- /// Sets the [`TitleBar`] of this [`Content`].
+ /// Sets the [`TitleBar`] of the [`Content`].
pub fn title_bar(
mut self,
title_bar: TitleBar<'a, Message, Theme, Renderer>,
@@ -53,18 +52,31 @@ where
}
/// Sets the style of the [`Content`].
+ #[must_use]
pub fn style(
mut self,
- style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ style: impl Fn(&Theme) -> container::Style + 'a,
+ ) -> Self
+ where
+ Theme::Class<'a>: From<container::StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Content`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
pub(super) fn state(&self) -> Tree {
let children = if let Some(title_bar) = self.title_bar.as_ref() {
@@ -93,7 +105,7 @@ where
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Renderer`]: crate::core::Renderer
+ /// [`Renderer`]: core::Renderer
pub fn draw(
&self,
tree: &Tree,
@@ -107,15 +119,7 @@ where
let bounds = layout.bounds();
{
- let style = {
- let status = if cursor.is_over(bounds) {
- container::Status::Hovered
- } else {
- container::Status::Idle
- };
-
- (self.style)(theme, status)
- };
+ let style = theme.style(&self.class);
container::draw_background(renderer, &style, bounds);
}
@@ -381,7 +385,8 @@ where
impl<'a, Message, Theme, Renderer> Draggable
for &Content<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
fn can_be_dragged_at(
&self,
@@ -403,8 +408,8 @@ impl<'a, T, Message, Theme, Renderer> From<T>
for Content<'a, Message, Theme, Renderer>
where
T: Into<Element<'a, Message, Theme, Renderer>>,
- Theme: container::DefaultStyle + 'a,
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog + 'a,
+ Renderer: core::Renderer,
{
fn from(element: T) -> Self {
Self::new(element)
diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index 8dfea6e3..c2eeebb7 100644
--- a/widget/src/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -6,7 +6,8 @@ use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{self, Tree};
use crate::core::{
- Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, Vector,
+ self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
+ Vector,
};
/// The title bar of a [`Pane`].
@@ -19,32 +20,31 @@ pub struct TitleBar<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
controls: Option<Element<'a, Message, Theme, Renderer>>,
padding: Padding,
always_show_controls: bool,
- style: container::Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
/// Creates a new [`TitleBar`] with the given content.
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
- ) -> Self
- where
- Theme: container::DefaultStyle + 'a,
- {
+ ) -> Self {
Self {
content: content.into(),
controls: None,
padding: Padding::ZERO,
always_show_controls: false,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -63,15 +63,6 @@ where
self
}
- /// Sets the style of the [`TitleBar`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
- self
- }
-
/// Sets whether or not the [`controls`] attached to this [`TitleBar`] are
/// always visible.
///
@@ -84,11 +75,33 @@ where
self.always_show_controls = true;
self
}
+
+ /// Sets the style of the [`TitleBar`].
+ #[must_use]
+ pub fn style(
+ mut self,
+ style: impl Fn(&Theme) -> container::Style + 'a,
+ ) -> Self
+ where
+ Theme::Class<'a>: From<container::StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`TitleBar`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: container::Catalog,
+ Renderer: core::Renderer,
{
pub(super) fn state(&self) -> Tree {
let children = if let Some(controls) = self.controls.as_ref() {
@@ -117,7 +130,7 @@ where
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Renderer`]: crate::core::Renderer
+ /// [`Renderer`]: core::Renderer
pub fn draw(
&self,
tree: &Tree,
@@ -130,16 +143,7 @@ where
show_controls: bool,
) {
let bounds = layout.bounds();
-
- let style = {
- let status = if cursor.is_over(bounds) {
- container::Status::Hovered
- } else {
- container::Status::Idle
- };
-
- (self.style)(theme, status)
- };
+ let style = theme.style(&self.class);
let inherited_style = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index 52d54397..801e792b 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -32,6 +32,7 @@ pub struct PickList<
T: ToString + PartialEq + Clone,
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
+ Theme: Catalog,
Renderer: text::Renderer,
{
on_select: Box<dyn Fn(T) -> Message + 'a>,
@@ -47,7 +48,8 @@ pub struct PickList<
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>,
- style: Style<'a, Theme>,
+ class: <Theme as Catalog>::Class<'a>,
+ menu_class: <Theme as menu::Catalog>::Class<'a>,
}
impl<'a, T, L, V, Message, Theme, Renderer>
@@ -57,6 +59,7 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Creates a new [`PickList`] with the given list of options, the current
@@ -65,10 +68,7 @@ where
options: L,
selected: Option<V>,
on_select: impl Fn(T) -> Message + 'a,
- ) -> Self
- where
- Theme: DefaultStyle,
- {
+ ) -> Self {
Self {
on_select: Box::new(on_select),
on_open: None,
@@ -83,7 +83,8 @@ where
text_shaping: text::Shaping::Basic,
font: None,
handle: Handle::default(),
- style: Theme::default_style(),
+ class: <Theme as Catalog>::default(),
+ menu_class: <Theme as Catalog>::default_menu(),
}
}
@@ -151,8 +152,23 @@ where
}
/// Sets the style of the [`PickList`].
- pub fn style(mut self, style: impl Into<Style<'a, Theme>>) -> Self {
- self.style = style.into();
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`PickList`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(
+ mut self,
+ class: impl Into<<Theme as Catalog>::Class<'a>>,
+ ) -> Self {
+ self.class = class.into();
self
}
}
@@ -164,6 +180,7 @@ where
L: Borrow<[T]>,
V: Borrow<T>,
Message: Clone + 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn tag(&self) -> tree::Tag {
@@ -409,15 +426,15 @@ where
Status::Active
};
- let appearance = (self.style.field)(theme, status);
+ let style = Catalog::style(theme, &self.class, status);
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.border,
+ border: style.border,
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
let handle = match &self.handle {
@@ -478,7 +495,7 @@ where
bounds.x + bounds.width - self.padding.right,
bounds.center_y(),
),
- appearance.handle_color,
+ style.handle_color,
*viewport,
);
}
@@ -505,9 +522,9 @@ where
},
Point::new(bounds.x + self.padding.left, bounds.center_y()),
if is_selected {
- appearance.text_color
+ style.text_color
} else {
- appearance.placeholder_color
+ style.placeholder_color
},
*viewport,
);
@@ -539,7 +556,7 @@ where
(on_select)(option)
},
None,
- &self.style.menu,
+ &self.menu_class,
)
.width(bounds.width)
.padding(self.padding)
@@ -565,7 +582,7 @@ where
L: Borrow<[T]> + 'a,
V: Borrow<T> + 'a,
Message: Clone + 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -662,7 +679,7 @@ pub enum Status {
/// The appearance of a pick list.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The text [`Color`] of the pick list.
pub text_color: Color,
/// The placeholder [`Color`] of the pick list.
@@ -675,36 +692,49 @@ pub struct Appearance {
pub border: Border,
}
-/// The styles of the different parts of a [`PickList`].
-#[allow(missing_debug_implementations)]
-pub struct Style<'a, Theme> {
- /// The style of the [`PickList`] itself.
- pub field: Box<dyn Fn(&Theme, Status) -> Appearance + 'a>,
+/// The theme catalog of a [`PickList`].
+pub trait Catalog: menu::Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
- /// The style of the [`Menu`] of the pick list.
- pub menu: menu::Style<'a, Theme>,
-}
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> <Self as Catalog>::Class<'a>;
-/// The default style of a [`PickList`].
-pub trait DefaultStyle: Sized {
- /// Returns the default style of a [`PickList`].
- fn default_style() -> Style<'static, Self>;
+ /// The default class for the menu of the [`PickList`].
+ fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> {
+ <Self as menu::Catalog>::default()
+ }
+
+ /// The [`Style`] of a class with the given status.
+ fn style(
+ &self,
+ class: &<Self as Catalog>::Class<'_>,
+ status: Status,
+ ) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style() -> Style<'static, Self> {
- Style {
- field: Box::new(default),
- menu: menu::DefaultStyle::default_style(),
- }
+/// A styling function for a [`PickList`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> StyleFn<'a, Self> {
+ Box::new(default)
+ }
+
+ fn style(&self, class: &StyleFn<'_, Self>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of the field of a [`PickList`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
- let active = Appearance {
+ let active = Style {
text_color: palette.background.weak.text,
background: palette.background.weak.color.into(),
placeholder_color: palette.background.strong.color,
@@ -718,7 +748,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status {
Status::Active => active,
- Status::Hovered | Status::Opened => Appearance {
+ Status::Hovered | Status::Opened => Style {
border: Border {
color: palette.primary.strong.color,
..active.border
diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs
index 38d8da85..e7821b43 100644
--- a/widget/src/progress_bar.rs
+++ b/widget/src/progress_bar.rs
@@ -4,7 +4,8 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
use crate::core::{
- Background, Border, Element, Layout, Length, Rectangle, Size, Theme, Widget,
+ self, Background, Border, Element, Layout, Length, Rectangle, Size, Theme,
+ Widget,
};
use std::ops::RangeInclusive;
@@ -22,15 +23,21 @@ use std::ops::RangeInclusive;
///
/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
-pub struct ProgressBar<'a, Theme = crate::Theme> {
+pub struct ProgressBar<'a, Theme = crate::Theme>
+where
+ Theme: Catalog,
+{
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
-impl<'a, Theme> ProgressBar<'a, Theme> {
+impl<'a, Theme> ProgressBar<'a, Theme>
+where
+ Theme: Catalog,
+{
/// The default height of a [`ProgressBar`].
pub const DEFAULT_HEIGHT: f32 = 30.0;
@@ -39,16 +46,13 @@ impl<'a, Theme> ProgressBar<'a, Theme> {
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
- pub fn new(range: RangeInclusive<f32>, value: f32) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
ProgressBar {
value: value.clamp(*range.start(), *range.end()),
range,
width: Length::Fill,
height: None,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -65,8 +69,20 @@ impl<'a, Theme> ProgressBar<'a, Theme> {
}
/// Sets the style of the [`ProgressBar`].
- pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`ProgressBar`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -74,7 +90,8 @@ impl<'a, Theme> ProgressBar<'a, Theme> {
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for ProgressBar<'a, Theme>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
fn size(&self) -> Size<Length> {
Size {
@@ -116,15 +133,15 @@ where
/ (range_end - range_start)
};
- let appearance = (self.style)(theme);
+ let style = theme.style(&self.class);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle { ..bounds },
- border: appearance.border,
+ border: style.border,
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
if active_progress_width > 0.0 {
@@ -134,10 +151,10 @@ where
width: active_progress_width,
..bounds
},
- border: Border::rounded(appearance.border.radius),
+ border: Border::rounded(style.border.radius),
..renderer::Quad::default()
},
- appearance.bar,
+ style.bar,
);
}
}
@@ -147,8 +164,8 @@ impl<'a, Message, Theme, Renderer> From<ProgressBar<'a, Theme>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
- Renderer: 'a + crate::core::Renderer,
+ Theme: 'a + Catalog,
+ Renderer: 'a + core::Renderer,
{
fn from(
progress_bar: ProgressBar<'a, Theme>,
@@ -159,7 +176,7 @@ where
/// The appearance of a progress bar.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the progress bar.
pub background: Background,
/// The [`Background`] of the bar of the progress bar.
@@ -168,29 +185,37 @@ pub struct Appearance {
pub border: Border,
}
-/// The style of a [`ProgressBar`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>;
+/// The theme catalog of a [`ProgressBar`].
+pub trait Catalog: Sized {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
+
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
-/// The default style of a [`ProgressBar`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`ProgressBar`].
- fn default_style(&self) -> Appearance;
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self) -> Appearance {
- primary(self)
+/// A styling function for a [`ProgressBar`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(primary)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>) -> Style {
+ class(self)
}
}
/// The primary style of a [`ProgressBar`].
-pub fn primary(theme: &Theme) -> Appearance {
+pub fn primary(theme: &Theme) -> Style {
let palette = theme.extended_palette();
styled(
@@ -200,7 +225,7 @@ pub fn primary(theme: &Theme) -> Appearance {
}
/// The secondary style of a [`ProgressBar`].
-pub fn secondary(theme: &Theme) -> Appearance {
+pub fn secondary(theme: &Theme) -> Style {
let palette = theme.extended_palette();
styled(
@@ -210,14 +235,14 @@ pub fn secondary(theme: &Theme) -> Appearance {
}
/// The success style of a [`ProgressBar`].
-pub fn success(theme: &Theme) -> Appearance {
+pub fn success(theme: &Theme) -> Style {
let palette = theme.extended_palette();
styled(palette.background.strong.color, palette.success.base.color)
}
/// The danger style of a [`ProgressBar`].
-pub fn danger(theme: &Theme) -> Appearance {
+pub fn danger(theme: &Theme) -> Style {
let palette = theme.extended_palette();
styled(palette.background.strong.color, palette.danger.base.color)
@@ -226,8 +251,8 @@ pub fn danger(theme: &Theme) -> Appearance {
fn styled(
background: impl Into<Background>,
bar: impl Into<Background>,
-) -> Appearance {
- Appearance {
+) -> Style {
+ Style {
background: background.into(),
bar: bar.into(),
border: Border::rounded(2),
diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs
index 90c0c970..e064aada 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -8,7 +8,6 @@ use crate::core::{
Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector,
Widget,
};
-use crate::graphics::geometry::Renderer as _;
use crate::Renderer;
use std::cell::RefCell;
@@ -20,22 +19,25 @@ const QUIET_ZONE: usize = 2;
/// A type of matrix barcode consisting of squares arranged in a grid which
/// can be read by an imaging device, such as a camera.
#[allow(missing_debug_implementations)]
-pub struct QRCode<'a, Theme = crate::Theme> {
+pub struct QRCode<'a, Theme = crate::Theme>
+where
+ Theme: Catalog,
+{
data: &'a Data,
cell_size: u16,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
-impl<'a, Theme> QRCode<'a, Theme> {
+impl<'a, Theme> QRCode<'a, Theme>
+where
+ Theme: Catalog,
+{
/// Creates a new [`QRCode`] with the provided [`Data`].
- pub fn new(data: &'a Data) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(data: &'a Data) -> Self {
Self {
data,
cell_size: DEFAULT_CELL_SIZE,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -46,14 +48,27 @@ impl<'a, Theme> QRCode<'a, Theme> {
}
/// Sets the style of the [`QRCode`].
- pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`QRCode`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
-impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
- for QRCode<'a, Theme>
+impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme>
+where
+ Theme: Catalog,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@@ -97,13 +112,13 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
let bounds = layout.bounds();
let side_length = self.data.width + 2 * QUIET_ZONE;
- let appearance = (self.style)(theme);
- let mut last_appearance = state.last_appearance.borrow_mut();
+ let style = theme.style(&self.class);
+ let mut last_style = state.last_style.borrow_mut();
- if Some(appearance) != *last_appearance {
+ if Some(style) != *last_style {
self.data.cache.clear();
- *last_appearance = Some(appearance);
+ *last_style = Some(style);
}
// Reuse cache if possible
@@ -115,7 +130,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
frame.fill_rectangle(
Point::ORIGIN,
Size::new(side_length as f32, side_length as f32),
- appearance.background,
+ style.background,
);
// Avoid drawing on the quiet zone
@@ -134,7 +149,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
frame.fill_rectangle(
Point::new(column as f32, row as f32),
Size::UNIT,
- appearance.cell,
+ style.cell,
);
});
});
@@ -142,7 +157,9 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
renderer.with_translation(
bounds.position() - Point::ORIGIN,
|renderer| {
- renderer.draw(vec![geometry]);
+ use crate::graphics::geometry::Renderer as _;
+
+ renderer.draw_geometry(geometry);
},
);
}
@@ -151,7 +168,7 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer>
impl<'a, Message, Theme> From<QRCode<'a, Theme>>
for Element<'a, Message, Theme, Renderer>
where
- Theme: 'a,
+ Theme: Catalog + 'a,
{
fn from(qr_code: QRCode<'a, Theme>) -> Self {
Self::new(qr_code)
@@ -165,7 +182,7 @@ where
pub struct Data {
contents: Vec<qrcode::Color>,
width: usize,
- cache: canvas::Cache,
+ cache: canvas::Cache<Renderer>,
}
impl Data {
@@ -323,44 +340,50 @@ impl From<qrcode::types::QrError> for Error {
#[derive(Default)]
struct State {
- last_appearance: RefCell<Option<Appearance>>,
+ last_style: RefCell<Option<Style>>,
}
/// The appearance of a QR code.
#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Appearance {
+pub struct Style {
/// The color of the QR code data cells
pub cell: Color,
/// The color of the QR code background
pub background: Color,
}
-/// The style of a [`QRCode`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>;
+/// The theme catalog of a [`QRCode`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`QRCode`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`QRCode`].
- fn default_style(&self) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self) -> Appearance {
- default(self)
+/// A styling function for a [`QRCode`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>) -> Style {
+ class(self)
}
}
/// The default style of a [`QRCode`].
-pub fn default(theme: &Theme) -> Appearance {
+pub fn default(theme: &Theme) -> Style {
let palette = theme.palette();
- Appearance {
+ Style {
cell: palette.text,
background: palette.background,
}
diff --git a/widget/src/radio.rs b/widget/src/radio.rs
index a7b7dd03..6b22961d 100644
--- a/widget/src/radio.rs
+++ b/widget/src/radio.rs
@@ -69,6 +69,7 @@ use crate::core::{
#[allow(missing_debug_implementations)]
pub struct Radio<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
is_selected: bool,
@@ -81,12 +82,13 @@ where
text_line_height: text::LineHeight,
text_shaping: text::Shaping,
font: Option<Renderer::Font>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>
where
Message: Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// The default size of a [`Radio`] button.
@@ -110,7 +112,6 @@ where
f: F,
) -> Self
where
- Theme: DefaultStyle + 'a,
V: Eq + Copy,
F: FnOnce(V) -> Message,
{
@@ -125,7 +126,7 @@ where
text_line_height: text::LineHeight::default(),
text_shaping: text::Shaping::Basic,
font: None,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -175,11 +176,20 @@ where
}
/// Sets the style of the [`Radio`] button.
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Radio`] button.
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -188,6 +198,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Radio<'a, Message, Theme, Renderer>
where
Message: Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@@ -284,7 +295,7 @@ where
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
@@ -300,7 +311,7 @@ where
Status::Active { is_selected }
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
{
let layout = children.next().unwrap();
@@ -314,12 +325,12 @@ where
bounds,
border: Border {
radius: (size / 2.0).into(),
- width: appearance.border_width,
- color: appearance.border_color,
+ width: style.border_width,
+ color: style.border_color,
},
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
if self.is_selected {
@@ -334,7 +345,7 @@ where
border: Border::rounded(dot_size / 2.0),
..renderer::Quad::default()
},
- appearance.dot_color,
+ style.dot_color,
);
}
}
@@ -344,11 +355,11 @@ where
crate::text::draw(
renderer,
- style,
+ defaults,
label_layout,
tree.state.downcast_ref(),
- crate::text::Appearance {
- color: appearance.text_color,
+ crate::text::Style {
+ color: style.text_color,
},
viewport,
);
@@ -360,7 +371,7 @@ impl<'a, Message, Theme, Renderer> From<Radio<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
- Theme: 'a,
+ Theme: 'a + Catalog,
Renderer: 'a + text::Renderer,
{
fn from(
@@ -387,7 +398,7 @@ pub enum Status {
/// The appearance of a radio button.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the radio button.
pub background: Background,
/// The [`Color`] of the dot of the radio button.
@@ -400,32 +411,38 @@ pub struct Appearance {
pub text_color: Option<Color>,
}
-/// The style of a [`Radio`] button.
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`Radio`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Radio`] button.
-pub trait DefaultStyle {
- /// Returns the default style of a [`Radio`] button.
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- default(self, status)
+/// A styling function for a [`Radio`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of a [`Radio`] button.
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
- let active = Appearance {
+ let active = Style {
background: Color::TRANSPARENT.into(),
dot_color: palette.primary.strong.color,
border_width: 1.0,
@@ -435,7 +452,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status {
Status::Active { .. } => active,
- Status::Hovered { .. } => Appearance {
+ Status::Hovered { .. } => Style {
dot_color: palette.primary.strong.color,
background: palette.primary.weak.color.into(),
..active
diff --git a/widget/src/rule.rs b/widget/src/rule.rs
index 9fa5f74f..1a536d2f 100644
--- a/widget/src/rule.rs
+++ b/widget/src/rule.rs
@@ -1,4 +1,5 @@
//! Display a horizontal or vertical rule for dividing content.
+use crate::core;
use crate::core::border::{self, Border};
use crate::core::layout;
use crate::core::mouse;
@@ -10,43 +11,55 @@ use crate::core::{
/// Display a horizontal or vertical rule for dividing content.
#[allow(missing_debug_implementations)]
-pub struct Rule<'a, Theme = crate::Theme> {
+pub struct Rule<'a, Theme = crate::Theme>
+where
+ Theme: Catalog,
+{
width: Length,
height: Length,
is_horizontal: bool,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
-impl<'a, Theme> Rule<'a, Theme> {
+impl<'a, Theme> Rule<'a, Theme>
+where
+ Theme: Catalog,
+{
/// Creates a horizontal [`Rule`] with the given height.
- pub fn horizontal(height: impl Into<Pixels>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn horizontal(height: impl Into<Pixels>) -> Self {
Rule {
width: Length::Fill,
height: Length::Fixed(height.into().0),
is_horizontal: true,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
/// Creates a vertical [`Rule`] with the given width.
- pub fn vertical(width: impl Into<Pixels>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn vertical(width: impl Into<Pixels>) -> Self {
Rule {
width: Length::Fixed(width.into().0),
height: Length::Fill,
is_horizontal: false,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
/// Sets the style of the [`Rule`].
- pub fn style(mut self, style: impl Fn(&Theme) -> Appearance + 'a) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Rule`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -54,7 +67,8 @@ impl<'a, Theme> Rule<'a, Theme> {
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Rule<'a, Theme>
where
- Renderer: crate::core::Renderer,
+ Renderer: core::Renderer,
+ Theme: Catalog,
{
fn size(&self) -> Size<Length> {
Size {
@@ -83,35 +97,34 @@ where
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
- let appearance = (self.style)(theme);
+ let style = theme.style(&self.class);
let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
- - (appearance.width as f32 / 2.0))
+ - (style.width as f32 / 2.0))
.round();
- let (offset, line_width) = appearance.fill_mode.fill(bounds.width);
+ let (offset, line_width) = style.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset;
Rectangle {
x: line_x,
y: line_y,
width: line_width,
- height: appearance.width as f32,
+ height: style.width as f32,
}
} else {
let line_x = (bounds.x + (bounds.width / 2.0)
- - (appearance.width as f32 / 2.0))
+ - (style.width as f32 / 2.0))
.round();
- let (offset, line_height) =
- appearance.fill_mode.fill(bounds.height);
+ let (offset, line_height) = style.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset;
Rectangle {
x: line_x,
y: line_y,
- width: appearance.width as f32,
+ width: style.width as f32,
height: line_height,
}
};
@@ -119,10 +132,10 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border: Border::rounded(appearance.radius),
+ border: Border::rounded(style.radius),
..renderer::Quad::default()
},
- appearance.color,
+ style.color,
);
}
}
@@ -131,8 +144,8 @@ impl<'a, Message, Theme, Renderer> From<Rule<'a, Theme>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
- Renderer: 'a + crate::core::Renderer,
+ Theme: 'a + Catalog,
+ Renderer: 'a + core::Renderer,
{
fn from(rule: Rule<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
Element::new(rule)
@@ -141,7 +154,7 @@ where
/// The appearance of a rule.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The color of the rule.
pub color: Color,
/// The width (thickness) of the rule line.
@@ -216,32 +229,40 @@ impl FillMode {
}
}
-/// The style of a [`Rule`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme) -> Appearance + 'a>;
+/// The theme catalog of a [`Rule`].
+pub trait Catalog: Sized {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Rule`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Rule`].
- fn default_style(&self) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self) -> Appearance {
- default(self)
+/// A styling function for a [`Rule`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>) -> Style {
+ class(self)
}
}
/// The default styling of a [`Rule`].
-pub fn default(theme: &Theme) -> Appearance {
+pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette();
- Appearance {
+ Style {
color: palette.background.strong.color,
width: 1,
radius: 0.0.into(),
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index c03bbb7d..84e9ac15 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -12,8 +12,8 @@ use crate::core::widget;
use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
- Point, Rectangle, Shell, Size, Theme, Vector, Widget,
+ self, Background, Border, Clipboard, Color, Element, Layout, Length,
+ Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::Command;
@@ -28,7 +28,8 @@ pub struct Scrollable<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
id: Option<Id>,
width: Length,
@@ -36,20 +37,18 @@ pub struct Scrollable<
direction: Direction,
content: Element<'a, Message, Theme, Renderer>,
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
/// Creates a new vertical [`Scrollable`].
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
- ) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ ) -> Self {
Self::with_direction(content, Direction::default())
}
@@ -57,18 +56,6 @@ where
pub fn with_direction(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
direction: Direction,
- ) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
- Self::with_direction_and_style(content, direction, Theme::default_style)
- }
-
- /// Creates a new [`Scrollable`] with the given [`Direction`] and style.
- pub fn with_direction_and_style(
- content: impl Into<Element<'a, Message, Theme, Renderer>>,
- direction: Direction,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
) -> Self {
let content = content.into();
@@ -91,7 +78,7 @@ where
direction,
content,
on_scroll: None,
- style: Box::new(style),
+ class: Theme::default(),
}
}
@@ -121,12 +108,21 @@ where
self
}
- /// Sets the style of the [`Scrollable`] .
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ /// Sets the style of this [`Scrollable`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Scrollable`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -237,7 +233,8 @@ pub enum Alignment {
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Scrollable<'a, Message, Theme, Renderer>
where
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@@ -651,7 +648,7 @@ where
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
@@ -701,13 +698,9 @@ where
Status::Active
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
- container::draw_background(
- renderer,
- &appearance.container,
- layout.bounds(),
- );
+ container::draw_background(renderer, &style.container, layout.bounds());
// Draw inner content
if scrollbars.active() {
@@ -719,7 +712,7 @@ where
&tree.children[0],
renderer,
theme,
- style,
+ defaults,
content_layout,
cursor,
&Rectangle {
@@ -782,7 +775,7 @@ where
if let Some(scrollbar) = scrollbars.y {
draw_scrollbar(
renderer,
- appearance.vertical_scrollbar,
+ style.vertical_scrollbar,
&scrollbar,
);
}
@@ -790,14 +783,14 @@ where
if let Some(scrollbar) = scrollbars.x {
draw_scrollbar(
renderer,
- appearance.horizontal_scrollbar,
+ style.horizontal_scrollbar,
&scrollbar,
);
}
if let (Some(x), Some(y)) = (scrollbars.x, scrollbars.y) {
let background =
- appearance.gap.or(appearance.container.background);
+ style.gap.or(style.container.background);
if let Some(background) = background {
renderer.fill_quad(
@@ -821,7 +814,7 @@ where
&tree.children[0],
renderer,
theme,
- style,
+ defaults,
content_layout,
cursor,
&Rectangle {
@@ -916,8 +909,8 @@ impl<'a, Message, Theme, Renderer>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
- Renderer: 'a + crate::core::Renderer,
+ Theme: 'a + Catalog,
+ Renderer: 'a + core::Renderer,
{
fn from(
text_input: Scrollable<'a, Message, Theme, Renderer>,
@@ -1570,9 +1563,9 @@ pub enum Status {
/// The appearance of a scrolable.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
- /// The [`container::Appearance`] of a scrollable.
- pub container: container::Appearance,
+pub struct Style {
+ /// The [`container::Style`] of a scrollable.
+ pub container: container::Style,
/// The vertical [`Scrollbar`] appearance.
pub vertical_scrollbar: Scrollbar,
/// The horizontal [`Scrollbar`] appearance.
@@ -1601,29 +1594,35 @@ pub struct Scroller {
pub border: Border,
}
-/// The style of a [`Scrollable`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`Scrollable`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Scrollable`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Scrollable`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- default(self, status)
+/// A styling function for a [`Scrollable`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of a [`Scrollable`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let scrollbar = Scrollbar {
@@ -1636,8 +1635,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
};
match status {
- Status::Active => Appearance {
- container: container::Appearance::default(),
+ Status::Active => Style {
+ container: container::Style::default(),
vertical_scrollbar: scrollbar,
horizontal_scrollbar: scrollbar,
gap: None,
@@ -1654,8 +1653,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
..scrollbar
};
- Appearance {
- container: container::Appearance::default(),
+ Style {
+ container: container::Style::default(),
vertical_scrollbar: if is_vertical_scrollbar_hovered {
hovered_scrollbar
} else {
@@ -1681,8 +1680,8 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
..scrollbar
};
- Appearance {
- container: container::Appearance::default(),
+ Style {
+ container: container::Style::default(),
vertical_scrollbar: if is_vertical_scrollbar_dragged {
dragged_scrollbar
} else {
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index d3b46a98..a8f1d192 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -9,7 +9,7 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Border, Clipboard, Color, Element, Layout, Length, Pixels, Point,
+ self, Border, Clipboard, Color, Element, Layout, Length, Pixels, Point,
Rectangle, Shell, Size, Theme, Widget,
};
@@ -39,7 +39,10 @@ use std::ops::RangeInclusive;
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, T, Message, Theme = crate::Theme> {
+pub struct Slider<'a, T, Message, Theme = crate::Theme>
+where
+ Theme: Catalog,
+{
range: RangeInclusive<T>,
step: T,
shift_step: Option<T>,
@@ -49,13 +52,14 @@ pub struct Slider<'a, T, Message, Theme = crate::Theme> {
on_release: Option<Message>,
width: Length,
height: f32,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
where
T: Copy + From<u8> + PartialOrd,
Message: Clone,
+ Theme: Catalog,
{
/// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: f32 = 16.0;
@@ -70,7 +74,6 @@ where
/// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
- Theme: DefaultStyle + 'a,
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
@@ -95,7 +98,7 @@ where
on_release: None,
width: Length::Fill,
height: Self::DEFAULT_HEIGHT,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -130,15 +133,6 @@ where
self
}
- /// Sets the style of the [`Slider`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
- self
- }
-
/// Sets the step size of the [`Slider`].
pub fn step(mut self, step: impl Into<T>) -> Self {
self.step = step.into();
@@ -152,6 +146,24 @@ where
self.shift_step = Some(shift_step.into());
self
}
+
+ /// Sets the style of the [`Slider`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Slider`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@@ -159,7 +171,8 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
- Renderer: crate::core::Renderer,
+ Theme: Catalog,
+ Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@@ -349,8 +362,8 @@ where
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
- let style = (self.style)(
- theme,
+ let style = theme.style(
+ &self.class,
if state.is_dragging {
Status::Dragged
} else if is_mouse_over {
@@ -461,8 +474,8 @@ impl<'a, T, Message, Theme, Renderer> From<Slider<'a, T, Message, Theme>>
where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a,
- Theme: 'a,
- Renderer: crate::core::Renderer + 'a,
+ Theme: Catalog + 'a,
+ Renderer: core::Renderer + 'a,
{
fn from(
slider: Slider<'a, T, Message, Theme>,
@@ -490,15 +503,15 @@ pub enum Status {
/// The appearance of a slider.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The colors of the rail of the slider.
pub rail: Rail,
/// The appearance of the [`Handle`] of the slider.
pub handle: Handle,
}
-impl Appearance {
- /// Changes the [`HandleShape`] of the [`Appearance`] to a circle
+impl Style {
+ /// Changes the [`HandleShape`] of the [`Style`] to a circle
/// with the given radius.
pub fn with_circular_handle(mut self, radius: impl Into<Pixels>) -> Self {
self.handle.shape = HandleShape::Circle {
@@ -549,29 +562,35 @@ pub enum HandleShape {
},
}
-/// The style of a [`Slider`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`Slider`].
+pub trait Catalog: Sized {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Slider`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Slider`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- default(self, status)
+/// A styling function for a [`Slider`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of a [`Slider`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let color = match status {
@@ -580,7 +599,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
Status::Dragged => palette.primary.strong.color,
};
- Appearance {
+ Style {
rail: Rail {
colors: (color, palette.secondary.base.color),
width: 4.0,
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index 1ac07ade..eb142189 100644
--- a/widget/src/svg.rs
+++ b/widget/src/svg.rs
@@ -20,36 +20,36 @@ pub use crate::core::svg::Handle;
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
#[allow(missing_debug_implementations)]
-pub struct Svg<'a, Theme = crate::Theme> {
+pub struct Svg<'a, Theme = crate::Theme>
+where
+ Theme: Catalog,
+{
handle: Handle,
width: Length,
height: Length,
content_fit: ContentFit,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
-impl<'a, Theme> Svg<'a, Theme> {
+impl<'a, Theme> Svg<'a, Theme>
+where
+ Theme: Catalog,
+{
/// Creates a new [`Svg`] from the given [`Handle`].
- pub fn new(handle: impl Into<Handle>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(handle: impl Into<Handle>) -> Self {
Svg {
handle: handle.into(),
width: Length::Fill,
height: Length::Shrink,
content_fit: ContentFit::Contain,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
/// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path.
#[must_use]
- pub fn from_path(path: impl Into<PathBuf>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self::new(Handle::from_path(path))
}
@@ -78,13 +78,21 @@ impl<'a, Theme> Svg<'a, Theme> {
}
}
- /// Sets the style variant of this [`Svg`].
+ /// Sets the style of the [`Svg`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Svg`].
+ #[cfg(feature = "advanced")]
#[must_use]
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -93,6 +101,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Svg<'a, Theme>
where
Renderer: svg::Renderer,
+ Theme: Catalog,
{
fn size(&self) -> Size<Length> {
Size {
@@ -108,7 +117,7 @@ where
limits: &layout::Limits,
) -> layout::Node {
// The raw w/h of the underlying image
- let Size { width, height } = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32);
// The size to be available to the widget prior to `Shrink`ing
@@ -142,7 +151,7 @@ where
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- let Size { width, height } = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
@@ -167,11 +176,11 @@ where
Status::Idle
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
- renderer.draw(
+ renderer.draw_svg(
self.handle.clone(),
- appearance.color,
+ style.color,
drawing_bounds + offset,
);
};
@@ -189,7 +198,7 @@ where
impl<'a, Message, Theme, Renderer> From<Svg<'a, Theme>>
for Element<'a, Message, Theme, Renderer>
where
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: svg::Renderer + 'a,
{
fn from(icon: Svg<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
@@ -208,7 +217,7 @@ pub enum Status {
/// The appearance of an [`Svg`].
#[derive(Debug, Clone, Copy, PartialEq, Default)]
-pub struct Appearance {
+pub struct Style {
/// The [`Color`] filter of an [`Svg`].
///
/// Useful for coloring a symbolic icon.
@@ -217,23 +226,37 @@ pub struct Appearance {
pub color: Option<Color>,
}
-/// The style of an [`Svg`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of an [`Svg`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
+
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
-/// The default style of an [`Svg`].
-pub trait DefaultStyle {
- /// Returns the default style of an [`Svg`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, _status: Status) -> Appearance {
- Appearance::default()
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(|_theme, _status| Style::default())
+ }
+
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+/// A styling function for an [`Svg`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl<'a, Theme> From<Style> for StyleFn<'a, Theme> {
+ fn from(style: Style) -> Self {
+ Box::new(move |_theme, _status| style)
}
}
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index 5b8f6a1b..a00df3c7 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -32,6 +32,7 @@ pub struct TextEditor<
Renderer = crate::Renderer,
> where
Highlighter: text::Highlighter,
+ Theme: Catalog,
Renderer: text::Renderer,
{
content: &'a Content<Renderer>,
@@ -41,7 +42,7 @@ pub struct TextEditor<
width: Length,
height: Length,
padding: Padding,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
highlighter_settings: Highlighter::Settings,
highlighter_format: fn(
@@ -53,13 +54,11 @@ pub struct TextEditor<
impl<'a, Message, Theme, Renderer>
TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Creates new [`TextEditor`] with the given [`Content`].
- pub fn new(content: &'a Content<Renderer>) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
+ pub fn new(content: &'a Content<Renderer>) -> Self {
Self {
content,
font: None,
@@ -68,7 +67,7 @@ where
width: Length::Fill,
height: Length::Shrink,
padding: Padding::new(5.0),
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
on_edit: None,
highlighter_settings: (),
highlighter_format: |_highlight, _theme| {
@@ -82,6 +81,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>
TextEditor<'a, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Sets the height of the [`TextEditor`].
@@ -134,7 +134,7 @@ where
width: self.width,
height: self.height,
padding: self.padding,
- style: self.style,
+ class: self.class,
on_edit: self.on_edit,
highlighter_settings: settings,
highlighter_format: to_format,
@@ -142,11 +142,20 @@ where
}
/// Sets the style of the [`TextEditor`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`TextEditor`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -309,6 +318,7 @@ impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for TextEditor<'a, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> widget::tree::Tag {
@@ -479,7 +489,7 @@ where
tree: &widget::Tree,
renderer: &mut Renderer,
theme: &Theme,
- style: &renderer::Style,
+ defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
@@ -508,22 +518,22 @@ where
Status::Active
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.border,
+ border: style.border,
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
renderer.fill_editor(
&internal.editor,
bounds.position()
+ Vector::new(self.padding.left, self.padding.top),
- style.text_color,
+ defaults.text_color,
*viewport,
);
@@ -555,7 +565,7 @@ where
},
..renderer::Quad::default()
},
- appearance.value,
+ style.value,
);
}
}
@@ -568,7 +578,7 @@ where
bounds: range,
..renderer::Quad::default()
},
- appearance.selection,
+ style.selection,
);
}
}
@@ -604,7 +614,7 @@ impl<'a, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
Message: 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer,
{
fn from(
@@ -796,7 +806,7 @@ pub enum Status {
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the text input.
pub background: Background,
/// The [`Border`] of the text input.
@@ -811,32 +821,38 @@ pub struct Appearance {
pub selection: Color,
}
-/// The style of a [`TextEditor`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`TextEditor`].
+pub trait Catalog {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`TextEditor`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`TextEditor`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- default(self, status)
+/// A styling function for a [`TextEditor`].
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of a [`TextEditor`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
- let active = Appearance {
+ let active = Style {
background: Background::Color(palette.background.base.color),
border: Border {
radius: 2.0.into(),
@@ -851,21 +867,21 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status {
Status::Active => active,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
border: Border {
color: palette.background.base.text,
..active.border
},
..active
},
- Status::Focused => Appearance {
+ Status::Focused => Style {
border: Border {
color: palette.primary.strong.color,
..active.border
},
..active
},
- Status::Disabled => Appearance {
+ Status::Disabled => Style {
background: Background::Color(palette.background.weak.color),
value: active.placeholder,
..active
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index b161ec74..dafe2fca 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -60,6 +60,7 @@ pub struct TextInput<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
+ Theme: Catalog,
Renderer: text::Renderer,
{
id: Option<Id>,
@@ -75,7 +76,7 @@ pub struct TextInput<
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>,
icon: Option<Icon<Renderer::Font>>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
/// The default [`Padding`] of a [`TextInput`].
@@ -84,24 +85,12 @@ pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
impl<'a, Message, Theme, Renderer> TextInput<'a, Message, Theme, Renderer>
where
Message: Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// Creates a new [`TextInput`] with the given placeholder and
/// its current value.
- pub fn new(placeholder: &str, value: &str) -> Self
- where
- Theme: DefaultStyle + 'a,
- {
- Self::with_style(placeholder, value, Theme::default_style)
- }
-
- /// Creates a new [`TextInput`] with the given placeholder,
- /// its current value, and its style.
- pub fn with_style(
- placeholder: &str,
- value: &str,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
+ pub fn new(placeholder: &str, value: &str) -> Self {
TextInput {
id: None,
placeholder: String::from(placeholder),
@@ -116,7 +105,7 @@ where
on_paste: None,
on_submit: None,
icon: None,
- style: Box::new(style),
+ class: Theme::default(),
}
}
@@ -203,11 +192,19 @@ where
}
/// Sets the style of the [`TextInput`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`TextInput`].
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
@@ -345,15 +342,15 @@ where
Status::Active
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
renderer.fill_quad(
renderer::Quad {
bounds,
- border: appearance.border,
+ border: style.border,
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
if self.icon.is_some() {
@@ -362,7 +359,7 @@ where
renderer.fill_paragraph(
&state.icon,
icon_layout.bounds().center(),
- appearance.icon,
+ style.icon,
*viewport,
);
}
@@ -401,7 +398,7 @@ where
},
..renderer::Quad::default()
},
- appearance.value,
+ style.value,
))
} else {
None
@@ -440,7 +437,7 @@ where
},
..renderer::Quad::default()
},
- appearance.selection,
+ style.selection,
)),
if end == right {
right_offset
@@ -475,9 +472,9 @@ where
Point::new(text_bounds.x, text_bounds.center_y())
- Vector::new(offset, 0.0),
if text.is_empty() {
- appearance.placeholder
+ style.placeholder
} else {
- appearance.value
+ style.value
},
viewport,
);
@@ -496,6 +493,7 @@ impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for TextInput<'a, Message, Theme, Renderer>
where
Message: Clone,
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@@ -1058,8 +1056,8 @@ where
impl<'a, Message, Theme, Renderer> From<TextInput<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
- Message: 'a + Clone,
- Theme: 'a,
+ Message: Clone + 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -1400,7 +1398,7 @@ pub enum Status {
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The [`Background`] of the text input.
pub background: Background,
/// The [`Border`] of the text input.
@@ -1415,32 +1413,40 @@ pub struct Appearance {
pub selection: Color,
}
-/// The style of a [`TextInput`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`TextInput`].
+pub trait Catalog: Sized {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`TextInput`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`TextInput`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- default(self, status)
+/// A styling function for a [`TextInput`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of a [`TextInput`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
- let active = Appearance {
+ let active = Style {
background: Background::Color(palette.background.base.color),
border: Border {
radius: 2.0.into(),
@@ -1455,21 +1461,21 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
match status {
Status::Active => active,
- Status::Hovered => Appearance {
+ Status::Hovered => Style {
border: Border {
color: palette.background.base.text,
..active.border
},
..active
},
- Status::Focused => Appearance {
+ Status::Focused => Style {
border: Border {
color: palette.primary.strong.color,
..active.border
},
..active
},
- Status::Disabled => Appearance {
+ Status::Disabled => Style {
background: Background::Color(palette.background.weak.color),
value: active.placeholder,
..active
diff --git a/widget/src/themer.rs b/widget/src/themer.rs
index a7eabd2c..f4597458 100644
--- a/widget/src/themer.rs
+++ b/widget/src/themer.rs
@@ -155,9 +155,9 @@ where
if let Some(background) = self.background {
container::draw_background(
renderer,
- &container::Appearance {
+ &container::Style {
background: Some(background(&theme)),
- ..container::Appearance::default()
+ ..container::Style::default()
},
layout.bounds(),
);
diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs
index fc9e06e1..ca6e37b0 100644
--- a/widget/src/toggler.rs
+++ b/widget/src/toggler.rs
@@ -35,6 +35,7 @@ pub struct Toggler<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
+ Theme: Catalog,
Renderer: text::Renderer,
{
is_toggled: bool,
@@ -48,11 +49,12 @@ pub struct Toggler<
text_shaping: text::Shaping,
spacing: f32,
font: Option<Renderer::Font>,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
/// The default size of a [`Toggler`].
@@ -72,7 +74,6 @@ where
f: F,
) -> Self
where
- Theme: 'a + DefaultStyle,
F: 'a + Fn(bool) -> Message,
{
Toggler {
@@ -87,7 +88,7 @@ where
text_shaping: text::Shaping::Basic,
spacing: Self::DEFAULT_SIZE / 2.0,
font: None,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -145,11 +146,20 @@ where
}
/// Sets the style of the [`Toggler`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Toggler`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -157,6 +167,7 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Toggler<'a, Message, Theme, Renderer>
where
+ Theme: Catalog,
Renderer: text::Renderer,
{
fn tag(&self) -> tree::Tag {
@@ -284,7 +295,7 @@ where
style,
label_layout,
tree.state.downcast_ref(),
- crate::text::Appearance::default(),
+ crate::text::Style::default(),
viewport,
);
}
@@ -302,7 +313,7 @@ where
}
};
- let appearance = (self.style)(theme, status);
+ let style = theme.style(&self.class, status);
let border_radius = bounds.height / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height;
@@ -319,12 +330,12 @@ where
bounds: toggler_background_bounds,
border: Border {
radius: border_radius.into(),
- width: appearance.background_border_width,
- color: appearance.background_border_color,
+ width: style.background_border_width,
+ color: style.background_border_color,
},
..renderer::Quad::default()
},
- appearance.background,
+ style.background,
);
let toggler_foreground_bounds = Rectangle {
@@ -344,12 +355,12 @@ where
bounds: toggler_foreground_bounds,
border: Border {
radius: border_radius.into(),
- width: appearance.foreground_border_width,
- color: appearance.foreground_border_color,
+ width: style.foreground_border_width,
+ color: style.foreground_border_color,
},
..renderer::Quad::default()
},
- appearance.foreground,
+ style.foreground,
);
}
}
@@ -358,7 +369,7 @@ impl<'a, Message, Theme, Renderer> From<Toggler<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -385,7 +396,7 @@ pub enum Status {
/// The appearance of a toggler.
#[derive(Debug, Clone, Copy)]
-pub struct Appearance {
+pub struct Style {
/// The background [`Color`] of the toggler.
pub background: Color,
/// The width of the background border of the toggler.
@@ -400,29 +411,37 @@ pub struct Appearance {
pub foreground_border_color: Color,
}
-/// The style of a [`Toggler`].
-pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
+/// The theme catalog of a [`Toggler`].
+pub trait Catalog: Sized {
+ /// The item class of the [`Catalog`].
+ type Class<'a>;
-/// The default style of a [`Toggler`].
-pub trait DefaultStyle {
- /// Returns the default style of a [`Toggler`].
- fn default_style(&self, status: Status) -> Appearance;
+ /// The default class produced by the [`Catalog`].
+ fn default<'a>() -> Self::Class<'a>;
+
+ /// The [`Style`] of a class with the given status.
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
}
-impl DefaultStyle for Theme {
- fn default_style(&self, status: Status) -> Appearance {
- default(self, status)
+/// A styling function for a [`Toggler`].
+///
+/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
+pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
+
+impl Catalog for Theme {
+ type Class<'a> = StyleFn<'a, Self>;
+
+ fn default<'a>() -> Self::Class<'a> {
+ Box::new(default)
}
-}
-impl DefaultStyle for Appearance {
- fn default_style(&self, _status: Status) -> Appearance {
- *self
+ fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+ class(self, status)
}
}
/// The default style of a [`Toggler`].
-pub fn default(theme: &Theme, status: Status) -> Appearance {
+pub fn default(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
let background = match status {
@@ -455,7 +474,7 @@ pub fn default(theme: &Theme, status: Status) -> Appearance {
}
};
- Appearance {
+ Style {
background,
foreground,
foreground_border_width: 0.0,
diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs
index 32c962fc..39f2e07d 100644
--- a/widget/src/tooltip.rs
+++ b/widget/src/tooltip.rs
@@ -20,6 +20,7 @@ pub struct Tooltip<
Theme = crate::Theme,
Renderer = crate::Renderer,
> where
+ Theme: container::Catalog,
Renderer: text::Renderer,
{
content: Element<'a, Message, Theme, Renderer>,
@@ -28,11 +29,12 @@ pub struct Tooltip<
gap: f32,
padding: f32,
snap_within_viewport: bool,
- style: container::Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, Message, Theme, Renderer> Tooltip<'a, Message, Theme, Renderer>
where
+ Theme: container::Catalog,
Renderer: text::Renderer,
{
/// The default padding of a [`Tooltip`] drawn by this renderer.
@@ -45,10 +47,7 @@ where
content: impl Into<Element<'a, Message, Theme, Renderer>>,
tooltip: impl Into<Element<'a, Message, Theme, Renderer>>,
position: Position,
- ) -> Self
- where
- Theme: container::DefaultStyle + 'a,
- {
+ ) -> Self {
Tooltip {
content: content.into(),
tooltip: tooltip.into(),
@@ -56,7 +55,7 @@ where
gap: 0.0,
padding: Self::DEFAULT_PADDING,
snap_within_viewport: true,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -79,11 +78,23 @@ where
}
/// Sets the style of the [`Tooltip`].
+ #[must_use]
pub fn style(
mut self,
- style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
+ style: impl Fn(&Theme) -> container::Style + 'a,
+ ) -> Self
+ where
+ Theme::Class<'a>: From<container::StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`Tooltip`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
self
}
}
@@ -91,6 +102,7 @@ where
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Tooltip<'a, Message, Theme, Renderer>
where
+ Theme: container::Catalog,
Renderer: text::Renderer,
{
fn children(&self) -> Vec<widget::Tree> {
@@ -239,7 +251,7 @@ where
positioning: self.position,
gap: self.gap,
padding: self.padding,
- style: &self.style,
+ class: &self.class,
})))
} else {
None
@@ -262,7 +274,7 @@ impl<'a, Message, Theme, Renderer> From<Tooltip<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
- Theme: 'a,
+ Theme: container::Catalog + 'a,
Renderer: text::Renderer + 'a,
{
fn from(
@@ -299,6 +311,7 @@ enum State {
struct Overlay<'a, 'b, Message, Theme, Renderer>
where
+ Theme: container::Catalog,
Renderer: text::Renderer,
{
position: Point,
@@ -310,14 +323,14 @@ where
positioning: Position,
gap: f32,
padding: f32,
- style:
- &'b (dyn Fn(&Theme, container::Status) -> container::Appearance + 'a),
+ class: &'b Theme::Class<'a>,
}
impl<'a, 'b, Message, Theme, Renderer>
overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer>
where
+ Theme: container::Catalog,
Renderer: text::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
@@ -426,7 +439,7 @@ where
layout: Layout<'_>,
cursor_position: mouse::Cursor,
) {
- let style = (self.style)(theme, container::Status::Idle);
+ let style = theme.style(self.class);
container::draw_background(renderer, &style, layout.bounds());
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index 2aa8f4d1..defb442f 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -2,10 +2,9 @@
use std::ops::RangeInclusive;
pub use crate::slider::{
- default, Appearance, DefaultStyle, Handle, HandleShape, Status, Style,
+ default, Catalog, Handle, HandleShape, Status, Style, StyleFn,
};
-use crate::core;
use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key::{self, Key};
@@ -15,8 +14,8 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Border, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size,
- Widget,
+ self, Border, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell,
+ Size, Widget,
};
/// An vertical bar and a handle that selects a single value from a range of
@@ -41,7 +40,10 @@ use crate::core::{
/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);
/// ```
#[allow(missing_debug_implementations)]
-pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> {
+pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme>
+where
+ Theme: Catalog,
+{
range: RangeInclusive<T>,
step: T,
shift_step: Option<T>,
@@ -51,13 +53,14 @@ pub struct VerticalSlider<'a, T, Message, Theme = crate::Theme> {
on_release: Option<Message>,
width: f32,
height: Length,
- style: Style<'a, Theme>,
+ class: Theme::Class<'a>,
}
impl<'a, T, Message, Theme> VerticalSlider<'a, T, Message, Theme>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
+ Theme: Catalog,
{
/// The default width of a [`VerticalSlider`].
pub const DEFAULT_WIDTH: f32 = 16.0;
@@ -72,7 +75,6 @@ where
/// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
- Theme: DefaultStyle + 'a,
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
@@ -97,7 +99,7 @@ where
on_release: None,
width: Self::DEFAULT_WIDTH,
height: Length::Fill,
- style: Box::new(Theme::default_style),
+ class: Theme::default(),
}
}
@@ -132,15 +134,6 @@ where
self
}
- /// Sets the style of the [`VerticalSlider`].
- pub fn style(
- mut self,
- style: impl Fn(&Theme, Status) -> Appearance + 'a,
- ) -> Self {
- self.style = Box::new(style);
- self
- }
-
/// Sets the step size of the [`VerticalSlider`].
pub fn step(mut self, step: T) -> Self {
self.step = step;
@@ -154,6 +147,24 @@ where
self.shift_step = Some(shift_step.into());
self
}
+
+ /// Sets the style of the [`VerticalSlider`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From<StyleFn<'a, Theme>>,
+ {
+ self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
+ self
+ }
+
+ /// Sets the style class of the [`VerticalSlider`].
+ #[cfg(feature = "advanced")]
+ #[must_use]
+ pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
+ self.class = class.into();
+ self
+ }
}
impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@@ -161,6 +172,7 @@ impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
+ Theme: Catalog,
Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
@@ -354,8 +366,8 @@ where
let bounds = layout.bounds();
let is_mouse_over = cursor.is_over(bounds);
- let style = (self.style)(
- theme,
+ let style = theme.style(
+ &self.class,
if state.is_dragging {
Status::Dragged
} else if is_mouse_over {
@@ -467,7 +479,7 @@ impl<'a, T, Message, Theme, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive + 'a,
Message: Clone + 'a,
- Theme: 'a,
+ Theme: Catalog + 'a,
Renderer: core::Renderer + 'a,
{
fn from(
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 13d9282d..d68523fa 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -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: C::Settings,
+ graphics_settings: graphics::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
@@ -219,7 +220,7 @@ where
};
}
- let compositor = C::new(compositor_settings, window.clone()).await?;
+ let compositor = C::new(graphics_settings, window.clone()).await?;
let mut renderer = compositor.create_renderer();
for font in settings.fonts {
diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs
index 18db1fb5..b4c25411 100644
--- a/winit/src/multi_window.rs
+++ b/winit/src/multi_window.rs
@@ -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: C::Settings,
+ graphics_settings: graphics::Settings,
) -> Result<(), Error>
where
A: Application + 'static,
@@ -187,7 +188,7 @@ where
}
let mut compositor =
- executor::block_on(C::new(compositor_settings, main_window.clone()))?;
+ executor::block_on(C::new(graphics_settings, main_window.clone()))?;
let mut window_manager = WindowManager::new();
let _ = window_manager.insert(