Merge branch 'master' into viewer_content_fit
This commit is contained in:
commit
19afc66cad
87 changed files with 3334 additions and 2607 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ keywords.workspace = true
|
|||
|
||||
[features]
|
||||
auto-detect-theme = ["dep:dark-light"]
|
||||
advanced = []
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultStyle for Color {
|
||||
fn default_style(&self) -> Appearance {
|
||||
Appearance { color: Some(*self) }
|
||||
/// 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())
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ where
|
|||
|renderer| {
|
||||
use iced::advanced::graphics::geometry::Renderer as _;
|
||||
|
||||
renderer.draw(vec![geometry]);
|
||||
renderer.draw_geometry(geometry);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
42
graphics/src/cached.rs
Normal file
42
graphics/src/cached.rs
Normal file
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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 = ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
/// A renderer capable of drawing some [`Self::Geometry`].
|
||||
pub trait Renderer: crate::core::Renderer {
|
||||
/// The kind of geometry this renderer can draw.
|
||||
type Geometry;
|
||||
use crate::core::{self, Size};
|
||||
use crate::Cached;
|
||||
|
||||
/// Draws the given layers of [`Self::Geometry`].
|
||||
fn draw(&mut self, layers: Vec<Self::Geometry>);
|
||||
/// A renderer capable of drawing some [`Self::Geometry`].
|
||||
pub trait Renderer: core::Renderer {
|
||||
/// The kind of geometry this renderer can draw.
|
||||
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 {}
|
||||
|
||||
fn draw_geometry(&mut self, _geometry: Self::Geometry) {}
|
||||
}
|
||||
|
|
|
|||
108
graphics/src/geometry/cache.rs
Normal file
108
graphics/src/geometry/cache.rs
Normal file
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
251
graphics/src/geometry/frame.rs
Normal file
251
graphics/src/geometry/frame.rs
Normal file
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
570
renderer/src/fallback.rs
Normal file
570
renderer/src/fallback.rs
Normal file
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
pub type Renderer = renderer::Renderer;
|
||||
|
||||
/// The default graphics compositor for [`iced`].
|
||||
///
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
pub type Compositor = renderer::Compositor;
|
||||
|
||||
#[cfg(all(feature = "wgpu", feature = "tiny-skia"))]
|
||||
mod renderer {
|
||||
pub type Renderer = crate::fallback::Renderer<
|
||||
iced_wgpu::Renderer,
|
||||
iced_tiny_skia::Renderer,
|
||||
>;
|
||||
|
||||
pub type Compositor = crate::fallback::Compositor<
|
||||
iced_wgpu::window::Compositor,
|
||||
iced_tiny_skia::window::Compositor,
|
||||
>;
|
||||
}
|
||||
|
||||
macro_rules! delegate {
|
||||
($renderer:expr, $name:ident, $body:expr) => {
|
||||
match $renderer {
|
||||
Self::TinySkia($name) => $body,
|
||||
#[cfg(feature = "wgpu")]
|
||||
Self::Wgpu($name) => $body,
|
||||
}
|
||||
};
|
||||
#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))]
|
||||
mod renderer {
|
||||
pub type Renderer = iced_wgpu::Renderer;
|
||||
pub type Compositor = iced_wgpu::window::Compositor;
|
||||
}
|
||||
|
||||
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),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
fn dimensions(
|
||||
&self,
|
||||
handle: &crate::core::image::Handle,
|
||||
) -> core::Size<u32> {
|
||||
delegate!(self, renderer, renderer.dimensions(handle))
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
#[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(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(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 = ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::<
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +28,325 @@ pub struct Frame {
|
|||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.primitives
|
||||
}
|
||||
}
|
||||
|
||||
impl geometry::frame::Backend for Frame {
|
||||
type Geometry = Primitive;
|
||||
|
||||
/// 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.
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> f32 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn height(&self) -> f32 {
|
||||
self.size.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn center(&self) -> Point {
|
||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||
}
|
||||
|
||||
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
}
|
||||
|
||||
fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: impl Into<Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (position, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(text.position, text.size, text.line_height)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(position, size, line_height)
|
||||
};
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
// TODO: Honor layering!
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn translate(&mut self, translation: Vector) {
|
||||
self.transforms.current.0 =
|
||||
self.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_translate(lyon::math::Vector::new(
|
||||
translation.x,
|
||||
translation.y,
|
||||
));
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale(&mut self, scale: impl Into<f32>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
fn pop_transform(&mut self) {
|
||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
fn draft(&mut self, size: Size) -> Frame {
|
||||
Frame::new(size)
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let (text, meshes) = primitives
|
||||
.into_iter()
|
||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
||||
|
||||
self.primitives.push(Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
||||
},
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Clip {
|
||||
bounds: Rectangle::with_size(size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: text,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
fn into_geometry(self) -> Self::Geometry {
|
||||
Primitive::Group {
|
||||
primitives: self.into_primitives(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Buffer {
|
||||
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
|
||||
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
|
||||
|
|
@ -165,386 +485,6 @@ impl Transform {
|
|||
gradient
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
/// Returns the height of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn height(&self) -> f32 {
|
||||
self.size.height
|
||||
}
|
||||
|
||||
/// Returns the dimensions of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Returns the coordinate of the center of the [`Frame`].
|
||||
#[inline]
|
||||
pub 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>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.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(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.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>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.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>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (position, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(text.position, text.size, text.line_height)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(position, size, line_height)
|
||||
};
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
// TODO: Honor layering!
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Pushes the current transform in the transform stack.
|
||||
pub fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
/// 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();
|
||||
}
|
||||
|
||||
/// 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());
|
||||
|
||||
let result = f(&mut frame);
|
||||
|
||||
let origin = Point::new(region.x, region.y);
|
||||
|
||||
self.clip(frame, origin);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
|
||||
pub fn clip(&mut self, frame: Frame, at: Point) {
|
||||
let size = frame.size();
|
||||
let primitives = frame.into_primitives();
|
||||
let transformation = Transformation::translate(at.x, at.y);
|
||||
|
||||
let (text, meshes) = primitives
|
||||
.into_iter()
|
||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
||||
|
||||
self.primitives.push(Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
||||
},
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Clip {
|
||||
bounds: Rectangle::with_size(size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: text,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/// 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,
|
||||
));
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
/// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
|
||||
pub fn into_primitive(self) -> Primitive {
|
||||
Primitive::Group {
|
||||
primitives: self.into_primitives(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.primitives
|
||||
}
|
||||
}
|
||||
|
||||
struct GradientVertex2DBuilder {
|
||||
gradient: gradient::Packed,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(device) = device {
|
||||
break Some(device);
|
||||
match result {
|
||||
Ok((device, queue)) => {
|
||||
return Ok(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
})
|
||||
}
|
||||
}?;
|
||||
Err(error) => {
|
||||
errors.push((required_limits, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
})
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
/// 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 [`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 Appearance {
|
||||
fn default_style(&self, _status: Status) -> Appearance {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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;
|
||||
}
|
||||
|
||||
/// The style of a [`Container`].
|
||||
pub type Style<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Appearance + 'a>;
|
||||
/// A styling function for a [`Container`].
|
||||
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
|
||||
|
||||
/// The default style of a [`Container`].
|
||||
pub trait DefaultStyle {
|
||||
/// Returns the default style of a [`Container`].
|
||||
fn default_style(&self, status: Status) -> Appearance;
|
||||
}
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
impl DefaultStyle for Theme {
|
||||
fn default_style(&self, status: Status) -> Appearance {
|
||||
transparent(self, status)
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(transparent)
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultStyle for Appearance {
|
||||
fn default_style(&self, _status: Status) -> Appearance {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultStyle for Color {
|
||||
fn default_style(&self, _status: Status) -> Appearance {
|
||||
Appearance::from(*self)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 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;
|
||||
}
|
||||
|
||||
/// The default style of a [`Menu`].
|
||||
pub trait DefaultStyle: Sized {
|
||||
/// Returns the default style of a [`Menu`].
|
||||
fn default_style() -> Style<'static, Self>;
|
||||
}
|
||||
/// A styling function for a [`Menu`].
|
||||
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
|
||||
|
||||
impl DefaultStyle for Theme {
|
||||
fn default_style() -> Style<'static, Self> {
|
||||
Style {
|
||||
list: Box::new(default),
|
||||
scrollable: Box::new(scrollable::default),
|
||||
}
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 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;
|
||||
}
|
||||
|
||||
/// The default style of a [`PickList`].
|
||||
pub trait DefaultStyle: Sized {
|
||||
/// Returns the default style of a [`PickList`].
|
||||
fn default_style() -> Style<'static, Self>;
|
||||
}
|
||||
/// 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 DefaultStyle for Theme {
|
||||
fn default_style() -> Style<'static, Self> {
|
||||
Style {
|
||||
field: Box::new(default),
|
||||
menu: menu::DefaultStyle::default_style(),
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
///
|
||||
/// 
|
||||
#[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 style of a [`ProgressBar`].
|
||||
pub trait DefaultStyle {
|
||||
/// Returns the default style of a [`ProgressBar`].
|
||||
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 {
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
///
|
||||
/// 
|
||||
#[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,
|
||||
|
|
|
|||
|
|
@ -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) -> Appearance + 'a,
|
||||
) -> Self {
|
||||
self.style = Box::new(style);
|
||||
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 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 style of an [`Svg`].
|
||||
pub trait DefaultStyle {
|
||||
/// Returns the default style of an [`Svg`].
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue