summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/lib.rs1
-rw-r--r--core/src/text.rs29
-rw-r--r--examples/custom_widget/Cargo.toml1
-rw-r--r--examples/custom_widget/src/main.rs33
-rw-r--r--examples/geometry/src/main.rs130
-rw-r--r--examples/integration_opengl/src/controls.rs8
-rw-r--r--examples/integration_opengl/src/main.rs18
-rw-r--r--examples/integration_wgpu/src/controls.rs7
-rw-r--r--examples/integration_wgpu/src/main.rs31
-rw-r--r--examples/pane_grid/src/main.rs34
-rw-r--r--examples/scrollable/src/style.rs6
-rw-r--r--examples/styling/src/main.rs14
-rw-r--r--examples/todos/src/main.rs36
-rw-r--r--glow/src/backend.rs19
-rw-r--r--glow/src/lib.rs4
-rw-r--r--glow/src/widget.rs9
-rw-r--r--glow/src/widget/button.rs5
-rw-r--r--glow/src/widget/checkbox.rs3
-rw-r--r--glow/src/widget/container.rs3
-rw-r--r--glow/src/widget/pane_grid.rs7
-rw-r--r--glow/src/widget/pick_list.rs4
-rw-r--r--glow/src/widget/progress_bar.rs9
-rw-r--r--glow/src/widget/radio.rs2
-rw-r--r--glow/src/widget/rule.rs9
-rw-r--r--glow/src/widget/scrollable.rs4
-rw-r--r--glow/src/widget/slider.rs10
-rw-r--r--glow/src/widget/text_input.rs5
-rw-r--r--glow/src/widget/toggler.rs3
-rw-r--r--glow/src/widget/tooltip.rs4
-rw-r--r--glow/src/window/compositor.rs10
-rw-r--r--glutin/src/application.rs39
-rw-r--r--graphics/src/defaults.rs32
-rw-r--r--graphics/src/layer.rs25
-rw-r--r--graphics/src/lib.rs4
-rw-r--r--graphics/src/overlay/menu.rs113
-rw-r--r--graphics/src/primitive.rs8
-rw-r--r--graphics/src/renderer.rs201
-rw-r--r--graphics/src/widget/button.rs105
-rw-r--r--graphics/src/widget/canvas.rs48
-rw-r--r--graphics/src/widget/checkbox.rs73
-rw-r--r--graphics/src/widget/column.rs48
-rw-r--r--graphics/src/widget/container.rs71
-rw-r--r--graphics/src/widget/image.rs21
-rw-r--r--graphics/src/widget/image/viewer.rs55
-rw-r--r--graphics/src/widget/pane_grid.rs284
-rw-r--r--graphics/src/widget/pick_list.rs100
-rw-r--r--graphics/src/widget/progress_bar.rs71
-rw-r--r--graphics/src/widget/qr_code.rs28
-rw-r--r--graphics/src/widget/radio.rs73
-rw-r--r--graphics/src/widget/row.rs48
-rw-r--r--graphics/src/widget/rule.rs72
-rw-r--r--graphics/src/widget/scrollable.rs151
-rw-r--r--graphics/src/widget/slider.rs120
-rw-r--r--graphics/src/widget/space.rs16
-rw-r--r--graphics/src/widget/svg.rs21
-rw-r--r--graphics/src/widget/text.rs89
-rw-r--r--graphics/src/widget/text_input.rs259
-rw-r--r--graphics/src/widget/toggler.rs95
-rw-r--r--graphics/src/widget/tooltip.rs163
-rw-r--r--graphics/src/window/compositor.rs18
-rw-r--r--graphics/src/window/gl_compositor.rs9
-rw-r--r--native/Cargo.toml4
-rw-r--r--native/src/element.rs90
-rw-r--r--native/src/image.rs124
-rw-r--r--native/src/layout.rs2
-rw-r--r--native/src/layout/debugger.rs24
-rw-r--r--native/src/lib.rs5
-rw-r--r--native/src/overlay.rs20
-rw-r--r--native/src/overlay/element.rs39
-rw-r--r--native/src/overlay/menu.rs204
-rw-r--r--native/src/program/state.rs30
-rw-r--r--native/src/renderer.rs69
-rw-r--r--native/src/renderer/null.rs291
-rw-r--r--native/src/svg.rs88
-rw-r--r--native/src/text.rs114
-rw-r--r--native/src/user_interface.rs97
-rw-r--r--native/src/widget.rs26
-rw-r--r--native/src/widget/button.rs150
-rw-r--r--native/src/widget/checkbox.rs178
-rw-r--r--native/src/widget/column.rs63
-rw-r--r--native/src/widget/container.rs106
-rw-r--r--native/src/widget/image.rs140
-rw-r--r--native/src/widget/image/viewer.rs84
-rw-r--r--native/src/widget/pane_grid.rs229
-rw-r--r--native/src/widget/pane_grid/configuration.rs2
-rw-r--r--native/src/widget/pane_grid/content.rs100
-rw-r--r--native/src/widget/pane_grid/node.rs6
-rw-r--r--native/src/widget/pane_grid/state.rs6
-rw-r--r--native/src/widget/pane_grid/title_bar.rs94
-rw-r--r--native/src/widget/pick_list.rs153
-rw-r--r--native/src/widget/progress_bar.rs126
-rw-r--r--native/src/widget/radio.rs184
-rw-r--r--native/src/widget/row.rs63
-rw-r--r--native/src/widget/rule.rs105
-rw-r--r--native/src/widget/scrollable.rs294
-rw-r--r--native/src/widget/slider.rs194
-rw-r--r--native/src/widget/space.rs29
-rw-r--r--native/src/widget/svg.rs104
-rw-r--r--native/src/widget/text.rs133
-rw-r--r--native/src/widget/text_input.rs435
-rw-r--r--native/src/widget/text_input/editor.rs2
-rw-r--r--native/src/widget/toggler.rs193
-rw-r--r--native/src/widget/tooltip.rs186
-rw-r--r--src/widget.rs8
-rw-r--r--style/src/button.rs10
-rw-r--r--style/src/checkbox.rs10
-rw-r--r--style/src/container.rs10
-rw-r--r--style/src/pane_grid.rs10
-rw-r--r--style/src/pick_list.rs6
-rw-r--r--style/src/progress_bar.rs6
-rw-r--r--style/src/radio.rs10
-rw-r--r--style/src/rule.rs7
-rw-r--r--style/src/scrollable.rs10
-rw-r--r--style/src/slider.rs10
-rw-r--r--style/src/text_input.rs10
-rw-r--r--style/src/toggler.rs6
-rw-r--r--web/src/widget/button.rs4
-rw-r--r--web/src/widget/checkbox.rs21
-rw-r--r--web/src/widget/container.rs4
-rw-r--r--web/src/widget/progress_bar.rs12
-rw-r--r--web/src/widget/radio.rs21
-rw-r--r--web/src/widget/scrollable.rs11
-rw-r--r--web/src/widget/slider.rs11
-rw-r--r--web/src/widget/text_input.rs9
-rw-r--r--wgpu/src/backend.rs17
-rw-r--r--wgpu/src/lib.rs4
-rw-r--r--wgpu/src/widget.rs9
-rw-r--r--wgpu/src/widget/button.rs5
-rw-r--r--wgpu/src/widget/checkbox.rs3
-rw-r--r--wgpu/src/widget/container.rs3
-rw-r--r--wgpu/src/widget/pane_grid.rs7
-rw-r--r--wgpu/src/widget/pick_list.rs4
-rw-r--r--wgpu/src/widget/progress_bar.rs10
-rw-r--r--wgpu/src/widget/radio.rs2
-rw-r--r--wgpu/src/widget/rule.rs9
-rw-r--r--wgpu/src/widget/scrollable.rs4
-rw-r--r--wgpu/src/widget/slider.rs10
-rw-r--r--wgpu/src/widget/text_input.rs5
-rw-r--r--wgpu/src/widget/toggler.rs3
-rw-r--r--wgpu/src/widget/tooltip.rs4
-rw-r--r--wgpu/src/window/compositor.rs29
-rw-r--r--winit/src/application.rs38
142 files changed, 3316 insertions, 4560 deletions
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 1f58a8cd..cd505239 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -17,7 +17,6 @@
pub mod alignment;
pub mod keyboard;
pub mod mouse;
-pub mod text;
mod background;
mod color;
diff --git a/core/src/text.rs b/core/src/text.rs
deleted file mode 100644
index e5d0092e..00000000
--- a/core/src/text.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-//! Draw and interact with text.
-use crate::Vector;
-
-/// The result of hit testing on text.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Hit {
- /// The point was within the bounds of the returned character index.
- CharOffset(usize),
- /// The provided point was not within the bounds of a glyph. The index
- /// of the character with the closest centeroid position is returned,
- /// as well as its delta.
- NearestCharOffset(usize, Vector),
-}
-
-impl Hit {
- /// Computes the cursor position corresponding to this [`HitTestResult`] .
- pub fn cursor(self) -> usize {
- match self {
- Self::CharOffset(i) => i,
- Self::NearestCharOffset(i, delta) => {
- if delta.x > f32::EPSILON {
- i + 1
- } else {
- i
- }
- }
- }
- }
-}
diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml
index 3942538d..86b0d2a9 100644
--- a/examples/custom_widget/Cargo.toml
+++ b/examples/custom_widget/Cargo.toml
@@ -8,4 +8,3 @@ publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
-iced_graphics = { path = "../../graphics" }
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index c9ad1905..b32cb83d 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -9,10 +9,10 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
- use iced_graphics::{Backend, Defaults, Primitive, Renderer};
+ use iced_native::layout::{self, Layout};
+ use iced_native::renderer;
use iced_native::{
- layout, mouse, Background, Color, Element, Hasher, Layout, Length,
- Point, Rectangle, Size, Widget,
+ Color, Element, Hasher, Length, Point, Rectangle, Size, Widget,
};
pub struct Circle {
@@ -25,9 +25,9 @@ mod circle {
}
}
- impl<Message, B> Widget<Message, Renderer<B>> for Circle
+ impl<Message, Renderer> Widget<Message, Renderer> for Circle
where
- B: Backend,
+ Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
@@ -39,7 +39,7 @@ mod circle {
fn layout(
&self,
- _renderer: &Renderer<B>,
+ _renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
@@ -53,30 +53,29 @@ mod circle {
fn draw(
&self,
- _renderer: &mut Renderer<B>,
- _defaults: &Defaults,
+ renderer: &mut Renderer,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> (Primitive, mouse::Interaction) {
- (
- Primitive::Quad {
+ ) {
+ renderer.fill_quad(
+ renderer::Quad {
bounds: layout.bounds(),
- background: Background::Color(Color::BLACK),
border_radius: self.radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
- mouse::Interaction::default(),
- )
+ Color::BLACK,
+ );
}
}
- impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Circle
+ impl<'a, Message, Renderer> Into<Element<'a, Message, Renderer>> for Circle
where
- B: Backend,
+ Renderer: renderer::Renderer,
{
- fn into(self) -> Element<'a, Message, Renderer<B>> {
+ fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self)
}
}
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index e5115493..6ef12013 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -10,12 +10,11 @@ mod rainbow {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
- use iced_graphics::{
- triangle::{Mesh2D, Vertex2D},
- Backend, Defaults, Primitive, Renderer,
- };
+ use iced_graphics::renderer::{self, Renderer};
+ use iced_graphics::{Backend, Primitive};
+
use iced_native::{
- layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
+ layout, Element, Hasher, Layout, Length, Point, Rectangle, Size,
Vector, Widget,
};
@@ -53,12 +52,15 @@ mod rainbow {
fn draw(
&self,
- _renderer: &mut Renderer<B>,
- _defaults: &Defaults,
+ renderer: &mut Renderer<B>,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> (Primitive, mouse::Interaction) {
+ ) {
+ use iced_graphics::triangle::{Mesh2D, Vertex2D};
+ use iced_native::Renderer as _;
+
let b = layout.bounds();
// R O Y G B I V
@@ -88,65 +90,63 @@ mod rainbow {
let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0];
- (
- Primitive::Translate {
- translation: Vector::new(b.x, b.y),
- content: Box::new(Primitive::Mesh2D {
- size: b.size(),
- buffers: Mesh2D {
- vertices: vec![
- Vertex2D {
- position: posn_center,
- color: [1.0, 1.0, 1.0, 1.0],
- },
- Vertex2D {
- position: posn_tl,
- color: color_r,
- },
- Vertex2D {
- position: posn_t,
- color: color_o,
- },
- Vertex2D {
- position: posn_tr,
- color: color_y,
- },
- Vertex2D {
- position: posn_r,
- color: color_g,
- },
- Vertex2D {
- position: posn_br,
- color: color_gb,
- },
- Vertex2D {
- position: posn_b,
- color: color_b,
- },
- Vertex2D {
- position: posn_bl,
- color: color_i,
- },
- Vertex2D {
- position: posn_l,
- color: color_v,
- },
- ],
- indices: vec![
- 0, 1, 2, // TL
- 0, 2, 3, // T
- 0, 3, 4, // TR
- 0, 4, 5, // R
- 0, 5, 6, // BR
- 0, 6, 7, // B
- 0, 7, 8, // BL
- 0, 8, 1, // L
- ],
+ let mesh = Primitive::Mesh2D {
+ size: b.size(),
+ buffers: Mesh2D {
+ vertices: vec![
+ Vertex2D {
+ position: posn_center,
+ color: [1.0, 1.0, 1.0, 1.0],
+ },
+ Vertex2D {
+ position: posn_tl,
+ color: color_r,
+ },
+ Vertex2D {
+ position: posn_t,
+ color: color_o,
+ },
+ Vertex2D {
+ position: posn_tr,
+ color: color_y,
+ },
+ Vertex2D {
+ position: posn_r,
+ color: color_g,
},
- }),
+ Vertex2D {
+ position: posn_br,
+ color: color_gb,
+ },
+ Vertex2D {
+ position: posn_b,
+ color: color_b,
+ },
+ Vertex2D {
+ position: posn_bl,
+ color: color_i,
+ },
+ Vertex2D {
+ position: posn_l,
+ color: color_v,
+ },
+ ],
+ indices: vec![
+ 0, 1, 2, // TL
+ 0, 2, 3, // T
+ 0, 3, 4, // TR
+ 0, 4, 5, // R
+ 0, 5, 6, // BR
+ 0, 6, 7, // B
+ 0, 7, 8, // BL
+ 0, 8, 1, // L
+ ],
},
- mouse::Interaction::default(),
- )
+ };
+
+ renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
+ renderer.draw_primitive(mesh);
+ });
}
}
diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs
index fa5aa91d..f387b4e5 100644
--- a/examples/integration_opengl/src/controls.rs
+++ b/examples/integration_opengl/src/controls.rs
@@ -1,9 +1,7 @@
use iced_glow::Renderer;
-use iced_glutin::slider;
-use iced_glutin::{
- Alignment, Color, Column, Command, Element, Length, Program, Row, Slider,
- Text,
-};
+use iced_glutin::widget::slider::{self, Slider};
+use iced_glutin::widget::{Column, Row, Text};
+use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs
index f80915d2..3b63f22e 100644
--- a/examples/integration_opengl/src/main.rs
+++ b/examples/integration_opengl/src/main.rs
@@ -68,7 +68,6 @@ pub fn main() {
let mut state = program::State::new(
controls,
viewport.logical_size(),
- conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer,
&mut debug,
);
@@ -160,16 +159,19 @@ pub fn main() {
}
// And then iced on top
- let mouse_interaction = renderer.backend_mut().draw(
- &gl,
- &viewport,
- state.primitive(),
- &debug.overlay(),
- );
+ renderer.with_primitives(|backend, primitive| {
+ backend.present(
+ &gl,
+ primitive,
+ &viewport,
+ &debug.overlay(),
+ );
+ });
+
// Update the mouse cursor
windowed_context.window().set_cursor_icon(
iced_winit::conversion::mouse_interaction(
- mouse_interaction,
+ state.mouse_interaction(),
),
);
diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs
index 414eb9ce..4f110bd2 100644
--- a/examples/integration_wgpu/src/controls.rs
+++ b/examples/integration_wgpu/src/controls.rs
@@ -1,8 +1,7 @@
use iced_wgpu::Renderer;
-use iced_winit::{
- slider, Alignment, Color, Column, Command, Element, Length, Program, Row,
- Slider, Text,
-};
+use iced_winit::widget::slider::{self, Slider};
+use iced_winit::widget::{Column, Row, Text};
+use iced_winit::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs
index bf36c7a5..35a69a7d 100644
--- a/examples/integration_wgpu/src/main.rs
+++ b/examples/integration_wgpu/src/main.rs
@@ -94,7 +94,6 @@ pub fn main() {
let mut state = program::State::new(
controls,
viewport.logical_size(),
- conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer,
&mut debug,
);
@@ -196,15 +195,17 @@ pub fn main() {
}
// And then iced on top
- let mouse_interaction = renderer.backend_mut().draw(
- &mut device,
- &mut staging_belt,
- &mut encoder,
- &view,
- &viewport,
- state.primitive(),
- &debug.overlay(),
- );
+ renderer.with_primitives(|backend, primitive| {
+ backend.present(
+ &mut device,
+ &mut staging_belt,
+ &mut encoder,
+ &view,
+ primitive,
+ &viewport,
+ &debug.overlay(),
+ );
+ });
// Then we submit the work
staging_belt.finish();
@@ -212,11 +213,11 @@ pub fn main() {
frame.present();
// Update the mouse cursor
- window.set_cursor_icon(
- iced_winit::conversion::mouse_interaction(
- mouse_interaction,
- ),
- );
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ state.mouse_interaction(),
+ ),
+ );
// And recall staging buffers
local_pool
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index 69872bad..8225e9e7 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -177,7 +177,11 @@ impl Application for Example {
let title_bar = pane_grid::TitleBar::new(title)
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
.padding(10)
- .style(style::TitleBar { is_focused });
+ .style(if is_focused {
+ style::TitleBar::Focused
+ } else {
+ style::TitleBar::Active
+ });
pane_grid::Content::new(pane.content.view(
id,
@@ -185,7 +189,11 @@ impl Application for Example {
pane.is_pinned,
))
.title_bar(title_bar)
- .style(style::Pane { is_focused })
+ .style(if is_focused {
+ style::Pane::Focused
+ } else {
+ style::Pane::Active
+ })
})
.width(Length::Fill)
.height(Length::Fill)
@@ -387,14 +395,16 @@ mod style {
0xC4 as f32 / 255.0,
);
- pub struct TitleBar {
- pub is_focused: bool,
+ pub enum TitleBar {
+ Active,
+ Focused,
}
impl container::StyleSheet for TitleBar {
fn style(&self) -> container::Style {
- let pane = Pane {
- is_focused: self.is_focused,
+ let pane = match self {
+ Self::Active => Pane::Active,
+ Self::Focused => Pane::Focused,
}
.style();
@@ -406,8 +416,9 @@ mod style {
}
}
- pub struct Pane {
- pub is_focused: bool,
+ pub enum Pane {
+ Active,
+ Focused,
}
impl container::StyleSheet for Pane {
@@ -415,10 +426,9 @@ mod style {
container::Style {
background: Some(Background::Color(SURFACE)),
border_width: 2.0,
- border_color: if self.is_focused {
- Color::BLACK
- } else {
- Color::from_rgb(0.7, 0.7, 0.7)
+ border_color: match self {
+ Self::Active => Color::from_rgb(0.7, 0.7, 0.7),
+ Self::Focused => Color::BLACK,
},
..Default::default()
}
diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs
index ae449141..ec1f13db 100644
--- a/examples/scrollable/src/style.rs
+++ b/examples/scrollable/src/style.rs
@@ -16,7 +16,7 @@ impl Default for Theme {
}
}
-impl From<Theme> for Box<dyn container::StyleSheet> {
+impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -25,7 +25,7 @@ impl From<Theme> for Box<dyn container::StyleSheet> {
}
}
-impl From<Theme> for Box<dyn radio::StyleSheet> {
+impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -34,7 +34,7 @@ impl From<Theme> for Box<dyn radio::StyleSheet> {
}
}
-impl From<Theme> for Box<dyn scrollable::StyleSheet> {
+impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index bf9cb403..4e319285 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -176,7 +176,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn container::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -185,7 +185,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn radio::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -194,7 +194,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn text_input::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn text_input::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -203,7 +203,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn button::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn button::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => light::Button.into(),
@@ -212,7 +212,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn scrollable::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -221,7 +221,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn slider::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn slider::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@@ -239,7 +239,7 @@ mod style {
}
}
- impl From<Theme> for Box<dyn checkbox::StyleSheet> {
+ impl<'a> From<Theme> for Box<dyn checkbox::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 4d657cc0..0b889407 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -363,8 +363,10 @@ impl Controls {
let filter_button = |state, label, filter, current_filter| {
let label = Text::new(label).size(16);
let button =
- Button::new(state, label).style(style::Button::Filter {
- selected: filter == current_filter,
+ Button::new(state, label).style(if filter == current_filter {
+ style::Button::FilterSelected
+ } else {
+ style::Button::FilterActive
});
button.on_press(Message::FilterChanged(filter)).padding(8)
@@ -602,7 +604,8 @@ mod style {
use iced::{button, Background, Color, Vector};
pub enum Button {
- Filter { selected: bool },
+ FilterActive,
+ FilterSelected,
Icon,
Destructive,
}
@@ -610,20 +613,15 @@ mod style {
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
match self {
- Button::Filter { selected } => {
- if *selected {
- button::Style {
- background: Some(Background::Color(
- Color::from_rgb(0.2, 0.2, 0.7),
- )),
- border_radius: 10.0,
- text_color: Color::WHITE,
- ..button::Style::default()
- }
- } else {
- button::Style::default()
- }
- }
+ Button::FilterActive => button::Style::default(),
+ Button::FilterSelected => button::Style {
+ background: Some(Background::Color(Color::from_rgb(
+ 0.2, 0.2, 0.7,
+ ))),
+ border_radius: 10.0,
+ text_color: Color::WHITE,
+ ..button::Style::default()
+ },
Button::Icon => button::Style {
text_color: Color::from_rgb(0.5, 0.5, 0.5),
..button::Style::default()
@@ -646,9 +644,7 @@ mod style {
button::Style {
text_color: match self {
Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
- Button::Filter { selected } if !selected => {
- Color::from_rgb(0.2, 0.2, 0.7)
- }
+ Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7),
_ => active.text_color,
},
shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
diff --git a/glow/src/backend.rs b/glow/src/backend.rs
index 9a9457cf..5ab7f922 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -5,10 +5,8 @@ use crate::{Settings, Transformation, Viewport};
use iced_graphics::backend;
use iced_graphics::font;
-use iced_graphics::Layer;
-use iced_graphics::Primitive;
+use iced_graphics::{Layer, Primitive};
use iced_native::alignment;
-use iced_native::mouse;
use iced_native::{Font, Size};
/// A [`glow`] graphics backend for [`iced`].
@@ -47,18 +45,18 @@ impl Backend {
///
/// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information.
- pub fn draw<T: AsRef<str>>(
+ pub fn present<T: AsRef<str>>(
&mut self,
gl: &glow::Context,
+ primitives: &[Primitive],
viewport: &Viewport,
- (primitive, mouse_interaction): &(Primitive, mouse::Interaction),
overlay_text: &[T],
- ) -> mouse::Interaction {
+ ) {
let viewport_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let projection = viewport.projection();
- let mut layers = Layer::generate(primitive, viewport);
+ let mut layers = Layer::generate(primitives, viewport);
layers.push(Layer::overlay(overlay_text, viewport));
for layer in layers {
@@ -70,8 +68,6 @@ impl Backend {
viewport_size.height,
);
}
-
- *mouse_interaction
}
fn flush(
@@ -83,6 +79,11 @@ impl Backend {
target_height: u32,
) {
let mut bounds = (layer.bounds * scale_factor).snap();
+
+ if bounds.width < 1 || bounds.height < 1 {
+ return;
+ }
+
bounds.height = bounds.height.min(target_height);
if !layer.quads.is_empty() {
diff --git a/glow/src/lib.rs b/glow/src/lib.rs
index 418f7e3d..c531169b 100644
--- a/glow/src/lib.rs
+++ b/glow/src/lib.rs
@@ -4,14 +4,14 @@
//!
//! [`glow`]: https://github.com/grovesNL/glow
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
-//#![deny(missing_docs)]
+#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![forbid(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod backend;
-pub mod program;
+mod program;
mod quad;
mod text;
mod triangle;
diff --git a/glow/src/widget.rs b/glow/src/widget.rs
index a77511e8..ee2810f9 100644
--- a/glow/src/widget.rs
+++ b/glow/src/widget.rs
@@ -66,13 +66,14 @@ pub mod qr_code;
#[doc(no_inline)]
pub use qr_code::QRCode;
-pub use iced_native::{Image, Space};
+pub use iced_native::widget::{Image, Space};
/// A container that distributes its contents vertically.
-pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>;
+pub type Column<'a, Message> =
+ iced_native::widget::Column<'a, Message, Renderer>;
/// A container that distributes its contents horizontally.
-pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>;
+pub type Row<'a, Message> = iced_native::widget::Row<'a, Message, Renderer>;
/// A paragraph of text.
-pub type Text = iced_native::Text<Renderer>;
+pub type Text = iced_native::widget::Text<Renderer>;
diff --git a/glow/src/widget/button.rs b/glow/src/widget/button.rs
index fc729cd5..f11ff25e 100644
--- a/glow/src/widget/button.rs
+++ b/glow/src/widget/button.rs
@@ -4,9 +4,10 @@
use crate::Renderer;
pub use iced_graphics::button::{Style, StyleSheet};
-pub use iced_native::button::State;
+pub use iced_native::widget::button::State;
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
-pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>;
+pub type Button<'a, Message> =
+ iced_native::widget::Button<'a, Message, Renderer>;
diff --git a/glow/src/widget/checkbox.rs b/glow/src/widget/checkbox.rs
index d27d77cc..76d572d9 100644
--- a/glow/src/widget/checkbox.rs
+++ b/glow/src/widget/checkbox.rs
@@ -6,4 +6,5 @@ pub use iced_graphics::checkbox::{Style, StyleSheet};
/// A box that can be checked.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
-pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>;
+pub type Checkbox<'a, Message> =
+ iced_native::widget::Checkbox<'a, Message, Renderer>;
diff --git a/glow/src/widget/container.rs b/glow/src/widget/container.rs
index bc26cef2..c16db50d 100644
--- a/glow/src/widget/container.rs
+++ b/glow/src/widget/container.rs
@@ -7,4 +7,5 @@ pub use iced_graphics::container::{Style, StyleSheet};
///
/// This is an alias of an `iced_native` container with a default
/// `Renderer`.
-pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>;
+pub type Container<'a, Message> =
+ iced_native::widget::Container<'a, Message, Renderer>;
diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs
index fc36862c..3c47acf0 100644
--- a/glow/src/widget/pane_grid.rs
+++ b/glow/src/widget/pane_grid.rs
@@ -20,12 +20,13 @@ pub use iced_graphics::pane_grid::{
/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
///
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
-pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
+pub type PaneGrid<'a, Message> =
+ iced_native::widget::PaneGrid<'a, Message, Renderer>;
/// The content of a [`Pane`].
pub type Content<'a, Message> =
- iced_native::pane_grid::Content<'a, Message, Renderer>;
+ iced_native::widget::pane_grid::Content<'a, Message, Renderer>;
/// The title bar of a [`Pane`].
pub type TitleBar<'a, Message> =
- iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
+ iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>;
diff --git a/glow/src/widget/pick_list.rs b/glow/src/widget/pick_list.rs
index fccc68c9..4d93be68 100644
--- a/glow/src/widget/pick_list.rs
+++ b/glow/src/widget/pick_list.rs
@@ -1,9 +1,9 @@
//! Display a dropdown list of selectable values.
-pub use iced_native::pick_list::State;
+pub use iced_native::widget::pick_list::State;
pub use iced_graphics::overlay::menu::Style as Menu;
pub use iced_graphics::pick_list::{Style, StyleSheet};
/// A widget allowing the selection of a single value from a list of options.
pub type PickList<'a, T, Message> =
- iced_native::PickList<'a, T, Message, crate::Renderer>;
+ iced_native::widget::PickList<'a, T, Message, crate::Renderer>;
diff --git a/glow/src/widget/progress_bar.rs b/glow/src/widget/progress_bar.rs
index 45a25d00..413e6fb7 100644
--- a/glow/src/widget/progress_bar.rs
+++ b/glow/src/widget/progress_bar.rs
@@ -2,12 +2,5 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-use crate::Renderer;
-pub use iced_graphics::progress_bar::{Style, StyleSheet};
-
-/// A bar that displays progress.
-///
-/// This is an alias of an `iced_native` progress bar with an
-/// `iced_wgpu::Renderer`.
-pub type ProgressBar = iced_native::ProgressBar<Renderer>;
+pub use iced_graphics::progress_bar::*;
diff --git a/glow/src/widget/radio.rs b/glow/src/widget/radio.rs
index 0b843d1f..9ef1d7a5 100644
--- a/glow/src/widget/radio.rs
+++ b/glow/src/widget/radio.rs
@@ -7,4 +7,4 @@ pub use iced_graphics::radio::{Style, StyleSheet};
///
/// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`.
-pub type Radio<Message> = iced_native::Radio<Message, Renderer>;
+pub type Radio<'a, Message> = iced_native::widget::Radio<'a, Message, Renderer>;
diff --git a/glow/src/widget/rule.rs b/glow/src/widget/rule.rs
index faa2be86..40281773 100644
--- a/glow/src/widget/rule.rs
+++ b/glow/src/widget/rule.rs
@@ -1,10 +1,3 @@
//! Display a horizontal or vertical rule for dividing content.
-use crate::Renderer;
-
-pub use iced_graphics::rule::{FillMode, Style, StyleSheet};
-
-/// Display a horizontal or vertical rule for dividing content.
-///
-/// This is an alias of an `iced_native` rule with an `iced_glow::Renderer`.
-pub type Rule = iced_native::Rule<Renderer>;
+pub use iced_graphics::rule::*;
diff --git a/glow/src/widget/scrollable.rs b/glow/src/widget/scrollable.rs
index fabb4318..d5635ec5 100644
--- a/glow/src/widget/scrollable.rs
+++ b/glow/src/widget/scrollable.rs
@@ -2,7 +2,7 @@
use crate::Renderer;
pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet};
-pub use iced_native::scrollable::State;
+pub use iced_native::widget::scrollable::State;
/// A widget that can vertically display an infinite amount of content
/// with a scrollbar.
@@ -10,4 +10,4 @@ pub use iced_native::scrollable::State;
/// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`.
pub type Scrollable<'a, Message> =
- iced_native::Scrollable<'a, Message, Renderer>;
+ iced_native::widget::Scrollable<'a, Message, Renderer>;
diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs
index 9a269858..2fb3d5d9 100644
--- a/glow/src/widget/slider.rs
+++ b/glow/src/widget/slider.rs
@@ -1,13 +1,5 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-use crate::Renderer;
-
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
-pub use iced_native::slider::State;
-
-/// An horizontal bar and a handle that selects a single value from a range of
-/// values.
-///
-/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
-pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
+pub use iced_native::widget::slider::{Slider, State};
diff --git a/glow/src/widget/text_input.rs b/glow/src/widget/text_input.rs
index db18b1cc..5560e3e0 100644
--- a/glow/src/widget/text_input.rs
+++ b/glow/src/widget/text_input.rs
@@ -4,9 +4,10 @@
use crate::Renderer;
pub use iced_graphics::text_input::{Style, StyleSheet};
-pub use iced_native::text_input::State;
+pub use iced_native::widget::text_input::State;
/// A field that can be filled with text.
///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
-pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>;
+pub type TextInput<'a, Message> =
+ iced_native::widget::TextInput<'a, Message, Renderer>;
diff --git a/glow/src/widget/toggler.rs b/glow/src/widget/toggler.rs
index 1cd8711b..40379025 100644
--- a/glow/src/widget/toggler.rs
+++ b/glow/src/widget/toggler.rs
@@ -6,4 +6,5 @@ pub use iced_graphics::toggler::{Style, StyleSheet};
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
-pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
+pub type Toggler<'a, Message> =
+ iced_native::widget::Toggler<'a, Message, Renderer>;
diff --git a/glow/src/widget/tooltip.rs b/glow/src/widget/tooltip.rs
index 89ab3a15..c6af3903 100644
--- a/glow/src/widget/tooltip.rs
+++ b/glow/src/widget/tooltip.rs
@@ -1,6 +1,6 @@
//! Display a widget over another.
/// A widget allowing the selection of a single value from a list of options.
pub type Tooltip<'a, Message> =
- iced_native::Tooltip<'a, Message, crate::Renderer>;
+ iced_native::widget::Tooltip<'a, Message, crate::Renderer>;
-pub use iced_native::tooltip::Position;
+pub use iced_native::widget::tooltip::Position;
diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs
index b8157f78..a85a4560 100644
--- a/glow/src/window/compositor.rs
+++ b/glow/src/window/compositor.rs
@@ -3,7 +3,6 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
use core::ffi::c_void;
use glow::HasContext;
use iced_graphics::{Antialiasing, Size};
-use iced_native::mouse;
/// A window graphics backend for iced powered by `glow`.
#[allow(missing_debug_implementations)]
@@ -59,14 +58,13 @@ impl iced_graphics::window::GLCompositor for Compositor {
}
}
- fn draw<T: AsRef<str>>(
+ fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
color: Color,
- output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
- ) -> mouse::Interaction {
+ ) {
let gl = &self.gl;
let [r, g, b, a] = color.into_linear();
@@ -76,6 +74,8 @@ impl iced_graphics::window::GLCompositor for Compositor {
gl.clear(glow::COLOR_BUFFER_BIT);
}
- renderer.backend_mut().draw(gl, viewport, output, overlay)
+ renderer.with_primitives(|backend, primitive| {
+ backend.present(gl, primitive, viewport, overlay);
+ });
}
}
diff --git a/glutin/src/application.rs b/glutin/src/application.rs
index fa2192d8..5d10e96a 100644
--- a/glutin/src/application.rs
+++ b/glutin/src/application.rs
@@ -1,5 +1,6 @@
//! Create interactive, native cross-platform applications.
-use crate::{mouse, Error, Executor, Runtime};
+use crate::mouse;
+use crate::{Error, Executor, Runtime};
pub use iced_winit::Application;
@@ -179,10 +180,7 @@ async fn run_instance<A, E, C>(
&mut debug,
));
- let mut primitive =
- user_interface.draw(&mut renderer, state.cursor_position());
let mut mouse_interaction = mouse::Interaction::default();
-
let mut events = Vec::new();
let mut messages = Vec::new();
@@ -246,10 +244,18 @@ async fn run_instance<A, E, C>(
}
debug.draw_started();
- primitive =
+ let new_mouse_interaction =
user_interface.draw(&mut renderer, state.cursor_position());
debug.draw_finished();
+ if new_mouse_interaction != mouse_interaction {
+ context.window().set_cursor_icon(
+ conversion::mouse_interaction(new_mouse_interaction),
+ );
+
+ mouse_interaction = new_mouse_interaction;
+ }
+
context.window().request_redraw();
}
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
@@ -291,10 +297,20 @@ async fn run_instance<A, E, C>(
debug.layout_finished();
debug.draw_started();
- primitive = user_interface
+ let new_mouse_interaction = user_interface
.draw(&mut renderer, state.cursor_position());
debug.draw_finished();
+ if new_mouse_interaction != mouse_interaction {
+ context.window().set_cursor_icon(
+ conversion::mouse_interaction(
+ new_mouse_interaction,
+ ),
+ );
+
+ mouse_interaction = new_mouse_interaction;
+ }
+
context.resize(glutin::dpi::PhysicalSize::new(
physical_size.width,
physical_size.height,
@@ -305,11 +321,10 @@ async fn run_instance<A, E, C>(
viewport_version = current_viewport_version;
}
- let new_mouse_interaction = compositor.draw(
+ compositor.present(
&mut renderer,
state.viewport(),
state.background_color(),
- &primitive,
&debug.overlay(),
);
@@ -317,14 +332,6 @@ async fn run_instance<A, E, C>(
debug.render_finished();
- if new_mouse_interaction != mouse_interaction {
- context.window().set_cursor_icon(
- conversion::mouse_interaction(new_mouse_interaction),
- );
-
- mouse_interaction = new_mouse_interaction;
- }
-
// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
}
diff --git a/graphics/src/defaults.rs b/graphics/src/defaults.rs
deleted file mode 100644
index 11718a87..00000000
--- a/graphics/src/defaults.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-//! Use default styling attributes to inherit styles.
-use iced_native::Color;
-
-/// Some default styling attributes.
-#[derive(Debug, Clone, Copy)]
-pub struct Defaults {
- /// Text styling
- pub text: Text,
-}
-
-impl Default for Defaults {
- fn default() -> Defaults {
- Defaults {
- text: Text::default(),
- }
- }
-}
-
-/// Some default text styling attributes.
-#[derive(Debug, Clone, Copy)]
-pub struct Text {
- /// The default color of text
- pub color: Color,
-}
-
-impl Default for Text {
- fn default() -> Text {
- Text {
- color: Color::BLACK,
- }
- }
-}
diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs
index 9653a2e4..7a32c850 100644
--- a/graphics/src/layer.rs
+++ b/graphics/src/layer.rs
@@ -74,7 +74,7 @@ impl<'a> Layer<'a> {
/// Distributes the given [`Primitive`] and generates a list of layers based
/// on its contents.
pub fn generate(
- primitive: &'a Primitive,
+ primitives: &'a [Primitive],
viewport: &Viewport,
) -> Vec<Self> {
let first_layer =
@@ -82,12 +82,14 @@ impl<'a> Layer<'a> {
let mut layers = vec![first_layer];
- Self::process_primitive(
- &mut layers,
- Vector::new(0.0, 0.0),
- primitive,
- 0,
- );
+ for primitive in primitives {
+ Self::process_primitive(
+ &mut layers,
+ Vector::new(0.0, 0.0),
+ primitive,
+ 0,
+ );
+ }
layers
}
@@ -173,11 +175,7 @@ impl<'a> Layer<'a> {
});
}
}
- Primitive::Clip {
- bounds,
- offset,
- content,
- } => {
+ Primitive::Clip { bounds, content } => {
let layer = &mut layers[current_layer];
let translated_bounds = *bounds + translation;
@@ -190,8 +188,7 @@ impl<'a> Layer<'a> {
Self::process_primitive(
layers,
- translation
- - Vector::new(offset.x as f32, offset.y as f32),
+ translation,
content,
layers.len() - 1,
);
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 54cdcb77..5b8defc5 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -13,15 +13,14 @@
mod antialiasing;
mod error;
mod primitive;
-mod renderer;
mod transformation;
mod viewport;
pub mod backend;
-pub mod defaults;
pub mod font;
pub mod layer;
pub mod overlay;
+pub mod renderer;
pub mod triangle;
pub mod widget;
pub mod window;
@@ -31,7 +30,6 @@ pub use widget::*;
pub use antialiasing::Antialiasing;
pub use backend::Backend;
-pub use defaults::Defaults;
pub use error::Error;
pub use layer::Layer;
pub use primitive::Primitive;
diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs
index 53f47984..c5ff093d 100644
--- a/graphics/src/overlay/menu.rs
+++ b/graphics/src/overlay/menu.rs
@@ -1,116 +1,3 @@
//! Build and show dropdown menus.
-use crate::alignment;
-use crate::backend::{self, Backend};
-use crate::{Primitive, Renderer};
-
-use iced_native::{mouse, overlay, Color, Font, Padding, Point, Rectangle};
pub use iced_style::menu::Style;
-
-impl<B> overlay::menu::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- type Style = Style;
-
- fn decorate(
- &mut self,
- bounds: Rectangle,
- _cursor_position: Point,
- style: &Style,
- (primitives, mouse_cursor): Self::Output,
- ) -> Self::Output {
- (
- Primitive::Group {
- primitives: vec![
- Primitive::Quad {
- bounds,
- background: style.background,
- border_color: style.border_color,
- border_width: style.border_width,
- border_radius: 0.0,
- },
- primitives,
- ],
- },
- mouse_cursor,
- )
- }
-
- fn draw<T: ToString>(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- viewport: &Rectangle,
- options: &[T],
- hovered_option: Option<usize>,
- padding: Padding,
- text_size: u16,
- font: Font,
- style: &Style,
- ) -> Self::Output {
- use std::f32;
-
- let is_mouse_over = bounds.contains(cursor_position);
- let option_height = (text_size + padding.vertical()) as usize;
-
- let mut primitives = Vec::new();
-
- let offset = viewport.y - bounds.y;
- let start = (offset / option_height as f32) as usize;
- let end =
- ((offset + viewport.height) / option_height as f32).ceil() as usize;
-
- let visible_options = &options[start..end.min(options.len())];
-
- for (i, option) in visible_options.iter().enumerate() {
- let i = start + i;
- let is_selected = hovered_option == Some(i);
-
- let bounds = Rectangle {
- x: bounds.x,
- y: bounds.y + (option_height * i) as f32,
- width: bounds.width,
- height: f32::from(text_size + padding.vertical()),
- };
-
- if is_selected {
- primitives.push(Primitive::Quad {
- bounds,
- background: style.selected_background,
- border_color: Color::TRANSPARENT,
- border_width: 0.0,
- border_radius: 0.0,
- });
- }
-
- primitives.push(Primitive::Text {
- content: option.to_string(),
- bounds: Rectangle {
- x: bounds.x + padding.left as f32,
- y: bounds.center_y(),
- width: f32::INFINITY,
- ..bounds
- },
- size: f32::from(text_size),
- font,
- color: if is_selected {
- style.selected_text_color
- } else {
- style.text_color
- },
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Center,
- });
- }
-
- (
- Primitive::Group { primitives },
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index 32f8383d..5f7a344d 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -1,6 +1,6 @@
-use iced_native::{
- image, svg, Background, Color, Font, Rectangle, Size, Vector,
-};
+use iced_native::image;
+use iced_native::svg;
+use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
use crate::alignment;
use crate::triangle;
@@ -66,8 +66,6 @@ pub enum Primitive {
Clip {
/// The bounds of the clip
bounds: Rectangle,
- /// The offset transformation of the clip
- offset: Vector<u32>,
/// The content of the clip
content: Box<Primitive>,
},
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index fa63991b..c32eb471 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -1,30 +1,43 @@
-use crate::{Backend, Defaults, Primitive};
-use iced_native::layout::{self, Layout};
-use iced_native::mouse;
-use iced_native::{
- Background, Color, Element, Point, Rectangle, Vector, Widget,
-};
+//! Create a renderer from a [`Backend`].
+use crate::backend::{self, Backend};
+use crate::{Primitive, Vector};
+use iced_native::layout;
+use iced_native::renderer;
+use iced_native::text::{self, Text};
+use iced_native::{Background, Element, Font, Point, Rectangle, Size};
+
+pub use iced_native::renderer::Style;
/// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)]
pub struct Renderer<B: Backend> {
backend: B,
+ primitives: Vec<Primitive>,
}
impl<B: Backend> Renderer<B> {
/// Creates a new [`Renderer`] from the given [`Backend`].
pub fn new(backend: B) -> Self {
- Self { backend }
+ Self {
+ backend,
+ primitives: Vec::new(),
+ }
}
- /// Returns a reference to the [`Backend`] of the [`Renderer`].
+ /// Returns the [`Backend`] of the [`Renderer`].
pub fn backend(&self) -> &B {
&self.backend
}
- /// Returns a mutable reference to the [`Backend`] of the [`Renderer`].
- pub fn backend_mut(&mut self) -> &mut B {
- &mut self.backend
+ /// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
+ pub fn draw_primitive(&mut self, primitive: Primitive) {
+ self.primitives.push(primitive);
+ }
+
+ /// Runs the given closure with the [`Backend`] and the recorded primitives
+ /// of the [`Renderer`].
+ pub fn with_primitives(&mut self, f: impl FnOnce(&mut B, &[Primitive])) {
+ f(&mut self.backend, &self.primitives);
}
}
@@ -32,9 +45,6 @@ impl<B> iced_native::Renderer for Renderer<B>
where
B: Backend,
{
- type Output = (Primitive, mouse::Interaction);
- type Defaults = Defaults;
-
fn layout<'a, Message>(
&mut self,
element: &Element<'a, Message, Self>,
@@ -47,75 +57,114 @@ where
layout
}
- fn overlay(
+ fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
+ let current_primitives = std::mem::take(&mut self.primitives);
+
+ f(self);
+
+ let layer_primitives =
+ std::mem::replace(&mut self.primitives, current_primitives);
+
+ self.primitives.push(Primitive::Clip {
+ bounds,
+ content: Box::new(Primitive::Group {
+ primitives: layer_primitives,
+ }),
+ });
+ }
+
+ fn with_translation(
&mut self,
- (base_primitive, base_cursor): (Primitive, mouse::Interaction),
- (overlay_primitives, overlay_cursor): (Primitive, mouse::Interaction),
- overlay_bounds: Rectangle,
- ) -> (Primitive, mouse::Interaction) {
- (
- Primitive::Group {
- primitives: vec![
- base_primitive,
- Primitive::Clip {
- bounds: Rectangle {
- width: overlay_bounds.width + 0.5,
- height: overlay_bounds.height + 0.5,
- ..overlay_bounds
- },
- offset: Vector::new(0, 0),
- content: Box::new(overlay_primitives),
- },
- ],
- },
- if base_cursor > overlay_cursor {
- base_cursor
- } else {
- overlay_cursor
- },
- )
+ translation: Vector,
+ f: impl FnOnce(&mut Self),
+ ) {
+ let current_primitives = std::mem::take(&mut self.primitives);
+
+ f(self);
+
+ let layer_primitives =
+ std::mem::replace(&mut self.primitives, current_primitives);
+
+ self.primitives.push(Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Group {
+ primitives: layer_primitives,
+ }),
+ });
+ }
+
+ fn fill_quad(
+ &mut self,
+ quad: renderer::Quad,
+ background: impl Into<Background>,
+ ) {
+ self.primitives.push(Primitive::Quad {
+ bounds: quad.bounds,
+ background: background.into(),
+ border_radius: quad.border_radius,
+ border_width: quad.border_width,
+ border_color: quad.border_color,
+ });
+ }
+
+ fn clear(&mut self) {
+ self.primitives.clear();
}
}
-impl<B> layout::Debugger for Renderer<B>
+impl<B> text::Renderer for Renderer<B>
where
- B: Backend,
+ B: Backend + backend::Text,
{
- fn explain<Message>(
- &mut self,
- defaults: &Defaults,
- widget: &dyn Widget<Message, Self>,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- color: Color,
- ) -> Self::Output {
- let (primitive, cursor) =
- widget.draw(self, defaults, layout, cursor_position, viewport);
-
- let mut primitives = Vec::new();
-
- explain_layout(layout, color, &mut primitives);
- primitives.push(primitive);
-
- (Primitive::Group { primitives }, cursor)
+ type Font = Font;
+
+ const ICON_FONT: Font = B::ICON_FONT;
+ const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
+ const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON;
+
+ fn default_size(&self) -> u16 {
+ self.backend().default_size()
+ }
+
+ fn measure(
+ &self,
+ content: &str,
+ size: u16,
+ font: Font,
+ bounds: Size,
+ ) -> (f32, f32) {
+ self.backend()
+ .measure(content, f32::from(size), font, bounds)
+ }
+
+ fn hit_test(
+ &self,
+ content: &str,
+ size: f32,
+ font: Font,
+ bounds: Size,
+ point: Point,
+ nearest_only: bool,
+ ) -> Option<text::Hit> {
+ self.backend().hit_test(
+ content,
+ size,
+ font,
+ bounds,
+ point,
+ nearest_only,
+ )
}
-}
-fn explain_layout(
- layout: Layout<'_>,
- color: Color,
- primitives: &mut Vec<Primitive>,
-) {
- primitives.push(Primitive::Quad {
- bounds: layout.bounds(),
- background: Background::Color(Color::TRANSPARENT),
- border_radius: 0.0,
- border_width: 1.0,
- border_color: [0.6, 0.6, 0.6, 0.5].into(),
- });
-
- for child in layout.children() {
- explain_layout(child, color, primitives);
+ fn fill_text(&mut self, text: Text<'_, Self::Font>) {
+ self.primitives.push(Primitive::Text {
+ content: text.content.to_string(),
+ bounds: text.bounds,
+ size: text.size,
+ color: text.color,
+ font: text.font,
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ });
}
}
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs
index 60400ed8..7b40c47b 100644
--- a/graphics/src/widget/button.rs
+++ b/graphics/src/widget/button.rs
@@ -1,111 +1,12 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-use crate::defaults::{self, Defaults};
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::{
- Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
-};
+use crate::Renderer;
-pub use iced_native::button::State;
-pub use iced_style::button::{Style, StyleSheet};
+pub use iced_native::widget::button::{State, Style, StyleSheet};
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
pub type Button<'a, Message, Backend> =
- iced_native::Button<'a, Message, Renderer<Backend>>;
-
-impl<B> iced_native::button::Renderer for Renderer<B>
-where
- B: Backend,
-{
- const DEFAULT_PADDING: Padding = Padding::new(5);
-
- type Style = Box<dyn StyleSheet>;
-
- fn draw<Message>(
- &mut self,
- _defaults: &Defaults,
- bounds: Rectangle,
- cursor_position: Point,
- is_disabled: bool,
- is_pressed: bool,
- style: &Box<dyn StyleSheet>,
- content: &Element<'_, Message, Self>,
- content_layout: Layout<'_>,
- ) -> Self::Output {
- let is_mouse_over = bounds.contains(cursor_position);
-
- let styling = if is_disabled {
- style.disabled()
- } else if is_mouse_over {
- if is_pressed {
- style.pressed()
- } else {
- style.hovered()
- }
- } else {
- style.active()
- };
-
- let (content, _) = content.draw(
- self,
- &Defaults {
- text: defaults::Text {
- color: styling.text_color,
- },
- },
- content_layout,
- cursor_position,
- &bounds,
- );
-
- (
- if styling.background.is_some() || styling.border_width > 0.0 {
- let background = Primitive::Quad {
- bounds,
- background: styling
- .background
- .unwrap_or(Background::Color(Color::TRANSPARENT)),
- border_radius: styling.border_radius,
- border_width: styling.border_width,
- border_color: styling.border_color,
- };
-
- if styling.shadow_offset == Vector::default() {
- Primitive::Group {
- primitives: vec![background, content],
- }
- } else {
- // TODO: Implement proper shadow support
- let shadow = Primitive::Quad {
- bounds: Rectangle {
- x: bounds.x + styling.shadow_offset.x,
- y: bounds.y + styling.shadow_offset.y,
- ..bounds
- },
- background: Background::Color(
- [0.0, 0.0, 0.0, 0.5].into(),
- ),
- border_radius: styling.border_radius,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- };
-
- Primitive::Group {
- primitives: vec![shadow, background, content],
- }
- }
- } else {
- content
- },
- if is_mouse_over && !is_disabled {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
+ iced_native::widget::Button<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs
index 7897c8ec..639c2a9b 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -3,7 +3,9 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
-use crate::{Backend, Defaults, Primitive, Renderer};
+use crate::renderer::{self, Renderer};
+use crate::{Backend, Primitive};
+
use iced_native::layout;
use iced_native::mouse;
use iced_native::{
@@ -186,32 +188,42 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let cursor = Cursor::from_window_position(cursor_position);
+
+ self.program.mouse_interaction(bounds, cursor)
+ }
+
fn draw(
&self,
- _renderer: &mut Renderer<B>,
- _defaults: &Defaults,
+ renderer: &mut Renderer<B>,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> (Primitive, mouse::Interaction) {
+ ) {
+ use iced_native::Renderer as _;
+
let bounds = layout.bounds();
let translation = Vector::new(bounds.x, bounds.y);
let cursor = Cursor::from_window_position(cursor_position);
- (
- Primitive::Translate {
- translation,
- content: Box::new(Primitive::Group {
- primitives: self
- .program
- .draw(bounds, cursor)
- .into_iter()
- .map(Geometry::into_primitive)
- .collect(),
- }),
- },
- self.program.mouse_interaction(bounds, cursor),
- )
+ renderer.with_translation(translation, |renderer| {
+ renderer.draw_primitive(Primitive::Group {
+ primitives: self
+ .program
+ .draw(bounds, cursor)
+ .into_iter()
+ .map(Geometry::into_primitive)
+ .collect(),
+ });
+ });
}
fn hash_layout(&self, state: &mut Hasher) {
diff --git a/graphics/src/widget/checkbox.rs b/graphics/src/widget/checkbox.rs
index 620bfc9e..0d2e93f9 100644
--- a/graphics/src/widget/checkbox.rs
+++ b/graphics/src/widget/checkbox.rs
@@ -1,77 +1,10 @@
//! Show toggle controls using checkboxes.
-use crate::alignment;
-use crate::backend::{self, Backend};
-use crate::{Primitive, Rectangle, Renderer};
-
-use iced_native::checkbox;
-use iced_native::mouse;
+use crate::Renderer;
pub use iced_style::checkbox::{Style, StyleSheet};
/// A box that can be checked.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
-pub type Checkbox<Message, Backend> =
- iced_native::Checkbox<Message, Renderer<Backend>>;
-
-impl<B> checkbox::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- type Style = Box<dyn StyleSheet>;
-
- const DEFAULT_SIZE: u16 = 20;
- const DEFAULT_SPACING: u16 = 15;
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_checked: bool,
- is_mouse_over: bool,
- (label, _): Self::Output,
- style_sheet: &Self::Style,
- ) -> Self::Output {
- let style = if is_mouse_over {
- style_sheet.hovered(is_checked)
- } else {
- style_sheet.active(is_checked)
- };
-
- let checkbox = Primitive::Quad {
- bounds,
- background: style.background,
- border_radius: style.border_radius,
- border_width: style.border_width,
- border_color: style.border_color,
- };
-
- (
- Primitive::Group {
- primitives: if is_checked {
- let check = Primitive::Text {
- content: B::CHECKMARK_ICON.to_string(),
- font: B::ICON_FONT,
- size: bounds.height * 0.7,
- bounds: Rectangle {
- x: bounds.center_x(),
- y: bounds.center_y(),
- ..bounds
- },
- color: style.checkmark_color,
- horizontal_alignment: alignment::Horizontal::Center,
- vertical_alignment: alignment::Vertical::Center,
- };
-
- vec![checkbox, check, label]
- } else {
- vec![checkbox, label]
- },
- },
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
+pub type Checkbox<'a, Message, Backend> =
+ iced_native::widget::Checkbox<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/column.rs b/graphics/src/widget/column.rs
index 0cf56842..561681d5 100644
--- a/graphics/src/widget/column.rs
+++ b/graphics/src/widget/column.rs
@@ -1,49 +1,5 @@
-use crate::{Backend, Primitive, Renderer};
-use iced_native::column;
-use iced_native::mouse;
-use iced_native::{Element, Layout, Point, Rectangle};
+use crate::Renderer;
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> =
- iced_native::Column<'a, Message, Renderer<Backend>>;
-
-impl<B> column::Renderer for Renderer<B>
-where
- B: Backend,
-{
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[Element<'_, Message, Self>],
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output {
- let mut mouse_interaction = mouse::Interaction::default();
-
- (
- Primitive::Group {
- primitives: content
- .iter()
- .zip(layout.children())
- .map(|(child, layout)| {
- let (primitive, new_mouse_interaction) = child.draw(
- self,
- defaults,
- layout,
- cursor_position,
- viewport,
- );
-
- if new_mouse_interaction > mouse_interaction {
- mouse_interaction = new_mouse_interaction;
- }
-
- primitive
- })
- .collect(),
- },
- mouse_interaction,
- )
- }
-}
+ iced_native::widget::Column<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs
index aae3e1d8..99996f3b 100644
--- a/graphics/src/widget/container.rs
+++ b/graphics/src/widget/container.rs
@@ -1,8 +1,5 @@
//! Decorate content and apply alignment.
-use crate::container;
-use crate::defaults::{self, Defaults};
-use crate::{Backend, Primitive, Renderer};
-use iced_native::{Background, Color, Element, Layout, Point, Rectangle};
+use crate::Renderer;
pub use iced_style::container::{Style, StyleSheet};
@@ -11,68 +8,4 @@ pub use iced_style::container::{Style, StyleSheet};
/// This is an alias of an `iced_native` container with a default
/// `Renderer`.
pub type Container<'a, Message, Backend> =
- iced_native::Container<'a, Message, Renderer<Backend>>;
-
-impl<B> iced_native::container::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn container::StyleSheet>;
-
- fn draw<Message>(
- &mut self,
- defaults: &Defaults,
- bounds: Rectangle,
- cursor_position: Point,
- viewport: &Rectangle,
- style_sheet: &Self::Style,
- content: &Element<'_, Message, Self>,
- content_layout: Layout<'_>,
- ) -> Self::Output {
- let style = style_sheet.style();
-
- let defaults = Defaults {
- text: defaults::Text {
- color: style.text_color.unwrap_or(defaults.text.color),
- },
- };
-
- let (content, mouse_interaction) = content.draw(
- self,
- &defaults,
- content_layout,
- cursor_position,
- viewport,
- );
-
- if let Some(background) = background(bounds, &style) {
- (
- Primitive::Group {
- primitives: vec![background, content],
- },
- mouse_interaction,
- )
- } else {
- (content, mouse_interaction)
- }
- }
-}
-
-pub(crate) fn background(
- bounds: Rectangle,
- style: &container::Style,
-) -> Option<Primitive> {
- if style.background.is_some() || style.border_width > 0.0 {
- Some(Primitive::Quad {
- bounds,
- background: style
- .background
- .unwrap_or(Background::Color(Color::TRANSPARENT)),
- border_radius: style.border_radius,
- border_width: style.border_width,
- border_color: style.border_color,
- })
- } else {
- None
- }
-}
+ iced_native::widget::Container<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/image.rs b/graphics/src/widget/image.rs
index bdf03de3..ad8159de 100644
--- a/graphics/src/widget/image.rs
+++ b/graphics/src/widget/image.rs
@@ -2,13 +2,12 @@
pub mod viewer;
use crate::backend::{self, Backend};
+use crate::{Primitive, Rectangle, Renderer};
-use crate::{Primitive, Renderer};
use iced_native::image;
-use iced_native::mouse;
-use iced_native::Layout;
-pub use iced_native::image::{Handle, Image, Viewer};
+pub use iced_native::widget::image::{Image, Viewer};
+pub use image::Handle;
impl<B> image::Renderer for Renderer<B>
where
@@ -18,17 +17,7 @@ where
self.backend().dimensions(handle)
}
- fn draw(
- &mut self,
- handle: image::Handle,
- layout: Layout<'_>,
- ) -> Self::Output {
- (
- Primitive::Image {
- handle,
- bounds: layout.bounds(),
- },
- mouse::Interaction::default(),
- )
+ fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
+ self.draw_primitive(Primitive::Image { handle, bounds })
}
}
diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs
index 28dffc4f..9260990a 100644
--- a/graphics/src/widget/image/viewer.rs
+++ b/graphics/src/widget/image/viewer.rs
@@ -1,55 +1,2 @@
//! Zoom and pan on an image.
-use crate::backend::{self, Backend};
-use crate::{Primitive, Renderer};
-
-use iced_native::image;
-use iced_native::image::viewer;
-use iced_native::mouse;
-use iced_native::{Rectangle, Size, Vector};
-
-impl<B> viewer::Renderer for Renderer<B>
-where
- B: Backend + backend::Image,
-{
- fn draw(
- &mut self,
- state: &viewer::State,
- bounds: Rectangle,
- image_size: Size,
- translation: Vector,
- handle: image::Handle,
- is_mouse_over: bool,
- ) -> Self::Output {
- (
- {
- Primitive::Clip {
- bounds,
- content: Box::new(Primitive::Translate {
- translation,
- content: Box::new(Primitive::Image {
- handle,
- bounds: Rectangle {
- x: bounds.x,
- y: bounds.y,
- ..Rectangle::with_size(image_size)
- },
- }),
- }),
- offset: Vector::new(0, 0),
- }
- },
- {
- if state.is_cursor_grabbed() {
- mouse::Interaction::Grabbing
- } else if is_mouse_over
- && (image_size.width > bounds.width
- || image_size.height > bounds.height)
- {
- mouse::Interaction::Grab
- } else {
- mouse::Interaction::Idle
- }
- },
- )
- }
-}
+pub use iced_native::widget::image::Viewer;
diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs
index 92cdbb77..95189920 100644
--- a/graphics/src/widget/pane_grid.rs
+++ b/graphics/src/widget/pane_grid.rs
@@ -7,14 +7,9 @@
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
-use crate::defaults;
-use crate::{Backend, Color, Primitive, Renderer};
-use iced_native::container;
-use iced_native::mouse;
-use iced_native::pane_grid;
-use iced_native::{Element, Layout, Point, Rectangle, Vector};
+use crate::Renderer;
-pub use iced_native::pane_grid::{
+pub use iced_native::widget::pane_grid::{
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
ResizeEvent, Split, State, TitleBar,
};
@@ -28,277 +23,4 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
///
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
pub type PaneGrid<'a, Message, Backend> =
- iced_native::PaneGrid<'a, Message, Renderer<Backend>>;
-
-impl<B> pane_grid::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn StyleSheet>;
-
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[(Pane, Content<'_, Message, Self>)],
- dragging: Option<(Pane, Point)>,
- resizing: Option<(Axis, Rectangle, bool)>,
- layout: Layout<'_>,
- style_sheet: &<Self as pane_grid::Renderer>::Style,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output {
- let pane_cursor_position = if dragging.is_some() {
- // TODO: Remove once cursor availability is encoded in the type
- // system
- Point::new(-1.0, -1.0)
- } else {
- cursor_position
- };
-
- let mut mouse_interaction = mouse::Interaction::default();
- let mut dragged_pane = None;
-
- let mut panes: Vec<_> = content
- .iter()
- .zip(layout.children())
- .enumerate()
- .map(|(i, ((id, pane), layout))| {
- let (primitive, new_mouse_interaction) = pane.draw(
- self,
- defaults,
- layout,
- pane_cursor_position,
- viewport,
- );
-
- if new_mouse_interaction > mouse_interaction {
- mouse_interaction = new_mouse_interaction;
- }
-
- if let Some((dragging, origin)) = dragging {
- if *id == dragging {
- dragged_pane = Some((i, layout, origin));
- }
- }
-
- primitive
- })
- .collect();
-
- let mut primitives = if let Some((index, layout, origin)) = dragged_pane
- {
- let pane = panes.remove(index);
- let bounds = layout.bounds();
-
- // TODO: Fix once proper layering is implemented.
- // This is a pretty hacky way to achieve layering.
- let clip = Primitive::Clip {
- bounds: Rectangle {
- x: cursor_position.x - origin.x,
- y: cursor_position.y - origin.y,
- width: bounds.width + 0.5,
- height: bounds.height + 0.5,
- },
- offset: Vector::new(0, 0),
- content: Box::new(Primitive::Translate {
- translation: Vector::new(
- cursor_position.x - bounds.x - origin.x,
- cursor_position.y - bounds.y - origin.y,
- ),
- content: Box::new(pane),
- }),
- };
-
- panes.push(clip);
-
- panes
- } else {
- panes
- };
-
- let (primitives, mouse_interaction) =
- if let Some((axis, split_region, is_picked)) = resizing {
- let highlight = if is_picked {
- style_sheet.picked_split()
- } else {
- style_sheet.hovered_split()
- };
-
- if let Some(highlight) = highlight {
- primitives.push(Primitive::Quad {
- bounds: match axis {
- Axis::Horizontal => Rectangle {
- x: split_region.x,
- y: (split_region.y
- + (split_region.height - highlight.width)
- / 2.0)
- .round(),
- width: split_region.width,
- height: highlight.width,
- },
- Axis::Vertical => Rectangle {
- x: (split_region.x
- + (split_region.width - highlight.width)
- / 2.0)
- .round(),
- y: split_region.y,
- width: highlight.width,
- height: split_region.height,
- },
- },
- background: highlight.color.into(),
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- });
- }
-
- (
- primitives,
- match axis {
- Axis::Horizontal => {
- mouse::Interaction::ResizingVertically
- }
- Axis::Vertical => {
- mouse::Interaction::ResizingHorizontally
- }
- },
- )
- } else {
- (primitives, mouse_interaction)
- };
-
- (
- Primitive::Group { primitives },
- if dragging.is_some() {
- mouse::Interaction::Grabbing
- } else {
- mouse_interaction
- },
- )
- }
-
- fn draw_pane<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- style_sheet: &<Self as container::Renderer>::Style,
- title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
- body: (&Element<'_, Message, Self>, Layout<'_>),
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output {
- let style = style_sheet.style();
- let (body, body_layout) = body;
-
- let (body_primitive, body_interaction) =
- body.draw(self, defaults, body_layout, cursor_position, viewport);
-
- let background = crate::widget::container::background(bounds, &style);
-
- if let Some((title_bar, title_bar_layout)) = title_bar {
- let show_controls = bounds.contains(cursor_position);
- let is_over_pick_area =
- title_bar.is_over_pick_area(title_bar_layout, cursor_position);
-
- let (title_bar_primitive, title_bar_interaction) = title_bar.draw(
- self,
- defaults,
- title_bar_layout,
- cursor_position,
- viewport,
- show_controls,
- );
-
- (
- Primitive::Group {
- primitives: vec![
- background.unwrap_or(Primitive::None),
- title_bar_primitive,
- body_primitive,
- ],
- },
- if title_bar_interaction > body_interaction {
- title_bar_interaction
- } else if is_over_pick_area {
- mouse::Interaction::Grab
- } else {
- body_interaction
- },
- )
- } else {
- (
- if let Some(background) = background {
- Primitive::Group {
- primitives: vec![background, body_primitive],
- }
- } else {
- body_primitive
- },
- body_interaction,
- )
- }
- }
-
- fn draw_title_bar<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- style_sheet: &<Self as container::Renderer>::Style,
- content: (&Element<'_, Message, Self>, Layout<'_>),
- controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output {
- let style = style_sheet.style();
- let (title_content, title_layout) = content;
-
- let defaults = Self::Defaults {
- text: defaults::Text {
- color: style.text_color.unwrap_or(defaults.text.color),
- },
- };
-
- let background = crate::widget::container::background(bounds, &style);
-
- let (title_primitive, title_interaction) = title_content.draw(
- self,
- &defaults,
- title_layout,
- cursor_position,
- viewport,
- );
-
- if let Some((controls, controls_layout)) = controls {
- let (controls_primitive, controls_interaction) = controls.draw(
- self,
- &defaults,
- controls_layout,
- cursor_position,
- viewport,
- );
-
- (
- Primitive::Group {
- primitives: vec![
- background.unwrap_or(Primitive::None),
- title_primitive,
- controls_primitive,
- ],
- },
- controls_interaction.max(title_interaction),
- )
- } else {
- (
- if let Some(background) = background {
- Primitive::Group {
- primitives: vec![background, title_primitive],
- }
- } else {
- title_primitive
- },
- title_interaction,
- )
- }
- }
-}
+ iced_native::widget::PaneGrid<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs
index 532840b8..f3ac12b8 100644
--- a/graphics/src/widget/pick_list.rs
+++ b/graphics/src/widget/pick_list.rs
@@ -1,103 +1,9 @@
//! Display a dropdown list of selectable values.
-use crate::alignment;
-use crate::backend::{self, Backend};
-use crate::{Primitive, Renderer};
+use crate::Renderer;
-use iced_native::{mouse, Font, Padding, Point, Rectangle};
-use iced_style::menu;
-
-pub use iced_native::pick_list::State;
+pub use iced_native::widget::pick_list::State;
pub use iced_style::pick_list::{Style, StyleSheet};
/// A widget allowing the selection of a single value from a list of options.
pub type PickList<'a, T, Message, Backend> =
- iced_native::PickList<'a, T, Message, Renderer<Backend>>;
-
-impl<B> iced_native::pick_list::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- type Style = Box<dyn StyleSheet>;
-
- const DEFAULT_PADDING: Padding = Padding::new(5);
-
- fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
- style.menu()
- }
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- selected: Option<String>,
- placeholder: Option<&str>,
- padding: Padding,
- text_size: u16,
- font: Font,
- style: &Box<dyn StyleSheet>,
- ) -> Self::Output {
- let is_mouse_over = bounds.contains(cursor_position);
- let is_selected = selected.is_some();
-
- let style = if is_mouse_over {
- style.hovered()
- } else {
- style.active()
- };
-
- let background = Primitive::Quad {
- bounds,
- background: style.background,
- border_color: style.border_color,
- border_width: style.border_width,
- border_radius: style.border_radius,
- };
-
- let arrow_down = Primitive::Text {
- content: B::ARROW_DOWN_ICON.to_string(),
- font: B::ICON_FONT,
- size: bounds.height * style.icon_size,
- bounds: Rectangle {
- x: bounds.x + bounds.width - f32::from(padding.horizontal()),
- y: bounds.center_y(),
- ..bounds
- },
- color: style.text_color,
- horizontal_alignment: alignment::Horizontal::Right,
- vertical_alignment: alignment::Vertical::Center,
- };
-
- (
- Primitive::Group {
- primitives: if let Some(label) =
- selected.or_else(|| placeholder.map(str::to_string))
- {
- let label = Primitive::Text {
- content: label,
- size: f32::from(text_size),
- font,
- color: is_selected
- .then(|| style.text_color)
- .unwrap_or(style.placeholder_color),
- bounds: Rectangle {
- x: bounds.x + f32::from(padding.left),
- y: bounds.center_y(),
- ..bounds
- },
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Center,
- };
-
- vec![background, label, arrow_down]
- } else {
- vec![background, arrow_down]
- },
- },
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
+ iced_native::widget::PickList<'a, T, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs
index 32ee42c6..3666ecfd 100644
--- a/graphics/src/widget/progress_bar.rs
+++ b/graphics/src/widget/progress_bar.rs
@@ -2,73 +2,4 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::progress_bar;
-use iced_native::{Color, Rectangle};
-
-pub use iced_style::progress_bar::{Style, StyleSheet};
-
-/// A bar that displays progress.
-///
-/// This is an alias of an `iced_native` progress bar with an
-/// `iced_wgpu::Renderer`.
-pub type ProgressBar<Backend> = iced_native::ProgressBar<Renderer<Backend>>;
-
-impl<B> progress_bar::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn StyleSheet>;
-
- const DEFAULT_HEIGHT: u16 = 30;
-
- fn draw(
- &self,
- bounds: Rectangle,
- range: std::ops::RangeInclusive<f32>,
- value: f32,
- style_sheet: &Self::Style,
- ) -> Self::Output {
- let style = style_sheet.style();
- let (range_start, range_end) = range.into_inner();
-
- let active_progress_width = if range_start >= range_end {
- 0.0
- } else {
- bounds.width * (value - range_start) / (range_end - range_start)
- };
-
- let background = Primitive::Group {
- primitives: vec![Primitive::Quad {
- bounds: Rectangle { ..bounds },
- background: style.background,
- border_radius: style.border_radius,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- }],
- };
-
- (
- if active_progress_width > 0.0 {
- let bar = Primitive::Quad {
- bounds: Rectangle {
- width: active_progress_width,
- ..bounds
- },
- background: style.bar,
- border_radius: style.border_radius,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- };
-
- Primitive::Group {
- primitives: vec![background, bar],
- }
- } else {
- background
- },
- mouse::Interaction::default(),
- )
- }
-}
+pub use iced_native::widget::progress_bar::*;
diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs
index b3a01dd7..285b8622 100644
--- a/graphics/src/widget/qr_code.rs
+++ b/graphics/src/widget/qr_code.rs
@@ -1,10 +1,12 @@
//! Encode and display information in a QR code.
use crate::canvas;
-use crate::{Backend, Defaults, Primitive, Renderer, Vector};
+use crate::renderer::{self, Renderer};
+use crate::Backend;
+use iced_native::layout;
use iced_native::{
- layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle,
- Size, Widget,
+ Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
use thiserror::Error;
@@ -80,12 +82,14 @@ where
fn draw(
&self,
- _renderer: &mut Renderer<B>,
- _defaults: &Defaults,
+ renderer: &mut Renderer<B>,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> (Primitive, mouse::Interaction) {
+ ) {
+ use iced_native::Renderer as _;
+
let bounds = layout.bounds();
let side_length = self.state.width + 2 * QUIET_ZONE;
@@ -122,13 +126,11 @@ where
});
});
- (
- Primitive::Translate {
- translation: Vector::new(bounds.x, bounds.y),
- content: Box::new(geometry.into_primitive()),
- },
- mouse::Interaction::default(),
- )
+ let translation = Vector::new(bounds.x, bounds.y);
+
+ renderer.with_translation(translation, |renderer| {
+ renderer.draw_primitive(geometry.into_primitive());
+ });
}
}
diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs
index fd3d8145..20d72747 100644
--- a/graphics/src/widget/radio.rs
+++ b/graphics/src/widget/radio.rs
@@ -1,8 +1,5 @@
//! Create choices using radio buttons.
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::radio;
-use iced_native::{Background, Color, Rectangle};
+use crate::Renderer;
pub use iced_style::radio::{Style, StyleSheet};
@@ -10,69 +7,5 @@ pub use iced_style::radio::{Style, StyleSheet};
///
/// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`.
-pub type Radio<Message, Backend> =
- iced_native::Radio<Message, Renderer<Backend>>;
-
-impl<B> radio::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn StyleSheet>;
-
- const DEFAULT_SIZE: u16 = 28;
- const DEFAULT_SPACING: u16 = 15;
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_selected: bool,
- is_mouse_over: bool,
- (label, _): Self::Output,
- style_sheet: &Self::Style,
- ) -> Self::Output {
- let style = if is_mouse_over {
- style_sheet.hovered()
- } else {
- style_sheet.active()
- };
-
- let size = bounds.width;
- let dot_size = size / 2.0;
-
- let radio = Primitive::Quad {
- bounds,
- background: style.background,
- border_radius: size / 2.0,
- border_width: style.border_width,
- border_color: style.border_color,
- };
-
- (
- Primitive::Group {
- primitives: if is_selected {
- let radio_circle = Primitive::Quad {
- bounds: Rectangle {
- x: bounds.x + dot_size / 2.0,
- y: bounds.y + dot_size / 2.0,
- width: bounds.width - dot_size,
- height: bounds.height - dot_size,
- },
- background: Background::Color(style.dot_color),
- border_radius: dot_size / 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- };
-
- vec![radio, radio_circle, label]
- } else {
- vec![radio, label]
- },
- },
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
+pub type Radio<'a, Message, Backend> =
+ iced_native::widget::Radio<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/row.rs b/graphics/src/widget/row.rs
index 397d80bf..5bee3fd5 100644
--- a/graphics/src/widget/row.rs
+++ b/graphics/src/widget/row.rs
@@ -1,49 +1,5 @@
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::row;
-use iced_native::{Element, Layout, Point, Rectangle};
+use crate::Renderer;
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> =
- iced_native::Row<'a, Message, Renderer<Backend>>;
-
-impl<B> row::Renderer for Renderer<B>
-where
- B: Backend,
-{
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[Element<'_, Message, Self>],
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output {
- let mut mouse_interaction = mouse::Interaction::default();
-
- (
- Primitive::Group {
- primitives: content
- .iter()
- .zip(layout.children())
- .map(|(child, layout)| {
- let (primitive, new_mouse_interaction) = child.draw(
- self,
- defaults,
- layout,
- cursor_position,
- viewport,
- );
-
- if new_mouse_interaction > mouse_interaction {
- mouse_interaction = new_mouse_interaction;
- }
-
- primitive
- })
- .collect(),
- },
- mouse_interaction,
- )
- }
-}
+ iced_native::widget::Row<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/rule.rs b/graphics/src/widget/rule.rs
index 835ebed8..b96924fa 100644
--- a/graphics/src/widget/rule.rs
+++ b/graphics/src/widget/rule.rs
@@ -1,73 +1,3 @@
//! Display a horizontal or vertical rule for dividing content.
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::rule;
-use iced_native::{Background, Color, Rectangle};
-
-pub use iced_style::rule::{FillMode, Style, StyleSheet};
-
-/// Display a horizontal or vertical rule for dividing content.
-///
-/// This is an alias of an `iced_native` rule with an `iced_graphics::Renderer`.
-pub type Rule<Backend> = iced_native::Rule<Renderer<Backend>>;
-
-impl<B> rule::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn StyleSheet>;
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- style_sheet: &Self::Style,
- is_horizontal: bool,
- ) -> Self::Output {
- let style = style_sheet.style();
-
- let line = if is_horizontal {
- let line_y = (bounds.y + (bounds.height / 2.0)
- - (style.width as f32 / 2.0))
- .round();
-
- let (offset, line_width) = style.fill_mode.fill(bounds.width);
- let line_x = bounds.x + offset;
-
- Primitive::Quad {
- bounds: Rectangle {
- x: line_x,
- y: line_y,
- width: line_width,
- height: style.width as f32,
- },
- background: Background::Color(style.color),
- border_radius: style.radius,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- }
- } else {
- let line_x = (bounds.x + (bounds.width / 2.0)
- - (style.width as f32 / 2.0))
- .round();
-
- let (offset, line_height) = style.fill_mode.fill(bounds.height);
- let line_y = bounds.y + offset;
-
- Primitive::Quad {
- bounds: Rectangle {
- x: line_x,
- y: line_y,
- width: style.width as f32,
- height: line_height,
- },
- background: Background::Color(style.color),
- border_radius: style.radius,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- }
- };
-
- (line, mouse::Interaction::default())
- }
-}
+pub use iced_native::widget::rule::*;
diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs
index 2220e4b8..3fdaf668 100644
--- a/graphics/src/widget/scrollable.rs
+++ b/graphics/src/widget/scrollable.rs
@@ -1,10 +1,7 @@
//! Navigate an endless amount of content with a scrollbar.
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::scrollable;
-use iced_native::{Background, Color, Rectangle, Vector};
+use crate::Renderer;
-pub use iced_native::scrollable::State;
+pub use iced_native::widget::scrollable::State;
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
/// A widget that can vertically display an infinite amount of content
@@ -13,146 +10,4 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
/// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`.
pub type Scrollable<'a, Message, Backend> =
- iced_native::Scrollable<'a, Message, Renderer<Backend>>;
-
-impl<B> scrollable::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn iced_style::scrollable::StyleSheet>;
-
- fn scrollbar(
- &self,
- bounds: Rectangle,
- content_bounds: Rectangle,
- offset: u32,
- scrollbar_width: u16,
- scrollbar_margin: u16,
- scroller_width: u16,
- ) -> Option<scrollable::Scrollbar> {
- if content_bounds.height > bounds.height {
- let outer_width =
- scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
-
- let outer_bounds = Rectangle {
- x: bounds.x + bounds.width - outer_width as f32,
- y: bounds.y,
- width: outer_width as f32,
- height: bounds.height,
- };
-
- let scrollbar_bounds = Rectangle {
- x: bounds.x + bounds.width
- - f32::from(outer_width / 2 + scrollbar_width / 2),
- y: bounds.y,
- width: scrollbar_width as f32,
- height: bounds.height,
- };
-
- let ratio = bounds.height / content_bounds.height;
- let scroller_height = bounds.height * ratio;
- let y_offset = offset as f32 * ratio;
-
- let scroller_bounds = Rectangle {
- x: bounds.x + bounds.width
- - f32::from(outer_width / 2 + scroller_width / 2),
- y: scrollbar_bounds.y + y_offset,
- width: scroller_width as f32,
- height: scroller_height,
- };
-
- Some(scrollable::Scrollbar {
- outer_bounds,
- bounds: scrollbar_bounds,
- margin: scrollbar_margin,
- scroller: scrollable::Scroller {
- bounds: scroller_bounds,
- },
- })
- } else {
- None
- }
- }
-
- fn draw(
- &mut self,
- state: &scrollable::State,
- bounds: Rectangle,
- _content_bounds: Rectangle,
- is_mouse_over: bool,
- is_mouse_over_scrollbar: bool,
- scrollbar: Option<scrollable::Scrollbar>,
- offset: u32,
- style_sheet: &Self::Style,
- (content, mouse_interaction): Self::Output,
- ) -> Self::Output {
- (
- if let Some(scrollbar) = scrollbar {
- let clip = Primitive::Clip {
- bounds,
- offset: Vector::new(0, offset),
- content: Box::new(content),
- };
-
- let style = if state.is_scroller_grabbed() {
- style_sheet.dragging()
- } else if is_mouse_over_scrollbar {
- style_sheet.hovered()
- } else {
- style_sheet.active()
- };
-
- let is_scrollbar_visible =
- style.background.is_some() || style.border_width > 0.0;
-
- let scroller = if is_mouse_over
- || state.is_scroller_grabbed()
- || is_scrollbar_visible
- {
- Primitive::Quad {
- bounds: scrollbar.scroller.bounds,
- background: Background::Color(style.scroller.color),
- border_radius: style.scroller.border_radius,
- border_width: style.scroller.border_width,
- border_color: style.scroller.border_color,
- }
- } else {
- Primitive::None
- };
-
- let scrollbar = if is_scrollbar_visible {
- Primitive::Quad {
- bounds: scrollbar.bounds,
- background: style
- .background
- .unwrap_or(Background::Color(Color::TRANSPARENT)),
- border_radius: style.border_radius,
- border_width: style.border_width,
- border_color: style.border_color,
- }
- } else {
- Primitive::None
- };
-
- let scroll = Primitive::Clip {
- bounds,
- offset: Vector::new(0, 0),
- content: Box::new(Primitive::Group {
- primitives: vec![scrollbar, scroller],
- }),
- };
-
- Primitive::Group {
- primitives: vec![clip, scroll],
- }
- } else {
- content
- },
- if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
- mouse::Interaction::Idle
- } else {
- mouse_interaction
- },
- )
- }
-}
+ iced_native::widget::Scrollable<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs
index aeceec3f..96dc6ec4 100644
--- a/graphics/src/widget/slider.rs
+++ b/graphics/src/widget/slider.rs
@@ -1,123 +1,5 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::slider;
-use iced_native::{Background, Color, Point, Rectangle};
-
-pub use iced_native::slider::State;
+pub use iced_native::widget::slider::{Slider, State};
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
-
-/// An horizontal bar and a handle that selects a single value from a range of
-/// values.
-///
-/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
-pub type Slider<'a, T, Message, Backend> =
- iced_native::Slider<'a, T, Message, Renderer<Backend>>;
-
-impl<B> slider::Renderer for Renderer<B>
-where
- B: Backend,
-{
- type Style = Box<dyn StyleSheet>;
-
- const DEFAULT_HEIGHT: u16 = 22;
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- range: std::ops::RangeInclusive<f32>,
- value: f32,
- is_dragging: bool,
- style_sheet: &Self::Style,
- ) -> Self::Output {
- let is_mouse_over = bounds.contains(cursor_position);
-
- let style = if is_dragging {
- style_sheet.dragging()
- } else if is_mouse_over {
- style_sheet.hovered()
- } else {
- style_sheet.active()
- };
-
- let rail_y = bounds.y + (bounds.height / 2.0).round();
-
- let (rail_top, rail_bottom) = (
- Primitive::Quad {
- bounds: Rectangle {
- x: bounds.x,
- y: rail_y,
- width: bounds.width,
- height: 2.0,
- },
- background: Background::Color(style.rail_colors.0),
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- Primitive::Quad {
- bounds: Rectangle {
- x: bounds.x,
- y: rail_y + 2.0,
- width: bounds.width,
- height: 2.0,
- },
- background: Background::Color(style.rail_colors.1),
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- );
-
- let (handle_width, handle_height, handle_border_radius) = match style
- .handle
- .shape
- {
- HandleShape::Circle { radius } => {
- (radius * 2.0, radius * 2.0, radius)
- }
- HandleShape::Rectangle {
- width,
- border_radius,
- } => (f32::from(width), f32::from(bounds.height), border_radius),
- };
-
- let (range_start, range_end) = range.into_inner();
-
- let handle_offset = if range_start >= range_end {
- 0.0
- } else {
- (bounds.width - handle_width) * (value - range_start)
- / (range_end - range_start)
- };
-
- let handle = Primitive::Quad {
- bounds: Rectangle {
- x: bounds.x + handle_offset.round(),
- y: rail_y - handle_height / 2.0,
- width: handle_width,
- height: handle_height,
- },
- background: Background::Color(style.handle.color),
- border_radius: handle_border_radius,
- border_width: style.handle.border_width,
- border_color: style.handle.border_color,
- };
-
- (
- Primitive::Group {
- primitives: vec![rail_top, rail_bottom, handle],
- },
- if is_dragging {
- mouse::Interaction::Grabbing
- } else if is_mouse_over {
- mouse::Interaction::Grab
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
diff --git a/graphics/src/widget/space.rs b/graphics/src/widget/space.rs
index 1f31eabe..77e93dbb 100644
--- a/graphics/src/widget/space.rs
+++ b/graphics/src/widget/space.rs
@@ -1,15 +1 @@
-use crate::{Backend, Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::space;
-use iced_native::Rectangle;
-
-pub use iced_native::Space;
-
-impl<B> space::Renderer for Renderer<B>
-where
- B: Backend,
-{
- fn draw(&mut self, _bounds: Rectangle) -> Self::Output {
- (Primitive::None, mouse::Interaction::default())
- }
-}
+pub use iced_native::widget::Space;
diff --git a/graphics/src/widget/svg.rs b/graphics/src/widget/svg.rs
index 8b5ed66a..5817a552 100644
--- a/graphics/src/widget/svg.rs
+++ b/graphics/src/widget/svg.rs
@@ -1,9 +1,10 @@
//! Display vector graphics in your application.
use crate::backend::{self, Backend};
-use crate::{Primitive, Renderer};
-use iced_native::{mouse, svg, Layout};
+use crate::{Primitive, Rectangle, Renderer};
+use iced_native::svg;
-pub use iced_native::svg::{Handle, Svg};
+pub use iced_native::widget::svg::Svg;
+pub use svg::Handle;
impl<B> svg::Renderer for Renderer<B>
where
@@ -13,17 +14,7 @@ where
self.backend().viewport_dimensions(handle)
}
- fn draw(
- &mut self,
- handle: svg::Handle,
- layout: Layout<'_>,
- ) -> Self::Output {
- (
- Primitive::Svg {
- handle,
- bounds: layout.bounds(),
- },
- mouse::Interaction::default(),
- )
+ fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
+ self.draw_primitive(Primitive::Svg { handle, bounds })
}
}
diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs
index d6d446d3..43516fca 100644
--- a/graphics/src/widget/text.rs
+++ b/graphics/src/widget/text.rs
@@ -1,92 +1,7 @@
//! Write some text for your users to read.
-use crate::backend::{self, Backend};
-use crate::{Primitive, Renderer};
-use iced_native::alignment;
-use iced_native::mouse;
-use iced_native::text;
-use iced_native::{Color, Font, Point, Rectangle, Size};
+use crate::Renderer;
/// A paragraph of text.
///
/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`.
-pub type Text<Backend> = iced_native::Text<Renderer<Backend>>;
-
-use std::f32;
-
-impl<B> text::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- type Font = Font;
-
- fn default_size(&self) -> u16 {
- self.backend().default_size()
- }
-
- fn measure(
- &self,
- content: &str,
- size: u16,
- font: Font,
- bounds: Size,
- ) -> (f32, f32) {
- self.backend()
- .measure(content, f32::from(size), font, bounds)
- }
-
- fn hit_test(
- &self,
- content: &str,
- size: f32,
- font: Font,
- bounds: Size,
- point: Point,
- nearest_only: bool,
- ) -> Option<text::Hit> {
- self.backend().hit_test(
- content,
- size,
- font,
- bounds,
- point,
- nearest_only,
- )
- }
-
- fn draw(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- content: &str,
- size: u16,
- font: Font,
- color: Option<Color>,
- horizontal_alignment: alignment::Horizontal,
- vertical_alignment: alignment::Vertical,
- ) -> Self::Output {
- let x = match horizontal_alignment {
- alignment::Horizontal::Left => bounds.x,
- alignment::Horizontal::Center => bounds.center_x(),
- alignment::Horizontal::Right => bounds.x + bounds.width,
- };
-
- let y = match vertical_alignment {
- alignment::Vertical::Top => bounds.y,
- alignment::Vertical::Center => bounds.center_y(),
- alignment::Vertical::Bottom => bounds.y + bounds.height,
- };
-
- (
- Primitive::Text {
- content: content.to_string(),
- size: f32::from(size),
- bounds: Rectangle { x, y, ..bounds },
- color: color.unwrap_or(defaults.text.color),
- font,
- horizontal_alignment,
- vertical_alignment,
- },
- mouse::Interaction::default(),
- )
- }
-}
+pub type Text<Backend> = iced_native::widget::Text<Renderer<Backend>>;
diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs
index 1516b007..87384d7e 100644
--- a/graphics/src/widget/text_input.rs
+++ b/graphics/src/widget/text_input.rs
@@ -1,266 +1,13 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-use crate::alignment;
-use crate::backend::{self, Backend};
-use crate::{
- Background, Color, Font, Point, Primitive, Rectangle, Renderer, Size,
- Vector,
-};
+use crate::Renderer;
-use iced_native::mouse;
-use iced_native::text_input::{self, cursor};
-use std::f32;
-
-pub use iced_native::text_input::State;
+pub use iced_native::widget::text_input::State;
pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text.
///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
pub type TextInput<'a, Message, Backend> =
- iced_native::TextInput<'a, Message, Renderer<Backend>>;
-
-impl<B> text_input::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- type Style = Box<dyn StyleSheet>;
-
- fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
- let backend = self.backend();
-
- let (width, _) =
- backend.measure(value, f32::from(size), font, Size::INFINITY);
-
- width
- }
-
- fn offset(
- &self,
- text_bounds: Rectangle,
- font: Font,
- size: u16,
- value: &text_input::Value,
- state: &text_input::State,
- ) -> f32 {
- if state.is_focused() {
- let cursor = state.cursor();
-
- let focus_position = match cursor.state(value) {
- cursor::State::Index(i) => i,
- cursor::State::Selection { end, .. } => end,
- };
-
- let (_, offset) = measure_cursor_and_scroll_offset(
- self,
- text_bounds,
- value,
- size,
- focus_position,
- font,
- );
-
- offset
- } else {
- 0.0
- }
- }
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- text_bounds: Rectangle,
- cursor_position: Point,
- font: Font,
- size: u16,
- placeholder: &str,
- value: &text_input::Value,
- state: &text_input::State,
- style_sheet: &Self::Style,
- ) -> Self::Output {
- let is_mouse_over = bounds.contains(cursor_position);
-
- let style = if state.is_focused() {
- style_sheet.focused()
- } else if is_mouse_over {
- style_sheet.hovered()
- } else {
- style_sheet.active()
- };
-
- let input = Primitive::Quad {
- bounds,
- background: style.background,
- border_radius: style.border_radius,
- border_width: style.border_width,
- border_color: style.border_color,
- };
-
- let text = value.to_string();
-
- let text_value = Primitive::Text {
- content: if text.is_empty() {
- placeholder.to_string()
- } else {
- text.clone()
- },
- color: if text.is_empty() {
- style_sheet.placeholder_color()
- } else {
- style_sheet.value_color()
- },
- font,
- bounds: Rectangle {
- y: text_bounds.center_y(),
- width: f32::INFINITY,
- ..text_bounds
- },
- size: f32::from(size),
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Center,
- };
-
- let (contents_primitive, offset) = if state.is_focused() {
- let cursor = state.cursor();
-
- let (cursor_primitive, offset) = match cursor.state(value) {
- cursor::State::Index(position) => {
- let (text_value_width, offset) =
- measure_cursor_and_scroll_offset(
- self,
- text_bounds,
- value,
- size,
- position,
- font,
- );
-
- (
- Primitive::Quad {
- bounds: Rectangle {
- x: text_bounds.x + text_value_width,
- y: text_bounds.y,
- width: 1.0,
- height: text_bounds.height,
- },
- background: Background::Color(
- style_sheet.value_color(),
- ),
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- offset,
- )
- }
- cursor::State::Selection { start, end } => {
- let left = start.min(end);
- let right = end.max(start);
-
- let (left_position, left_offset) =
- measure_cursor_and_scroll_offset(
- self,
- text_bounds,
- value,
- size,
- left,
- font,
- );
-
- let (right_position, right_offset) =
- measure_cursor_and_scroll_offset(
- self,
- text_bounds,
- value,
- size,
- right,
- font,
- );
-
- let width = right_position - left_position;
-
- (
- Primitive::Quad {
- bounds: Rectangle {
- x: text_bounds.x + left_position,
- y: text_bounds.y,
- width,
- height: text_bounds.height,
- },
- background: Background::Color(
- style_sheet.selection_color(),
- ),
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- if end == right {
- right_offset
- } else {
- left_offset
- },
- )
- }
- };
-
- (
- Primitive::Group {
- primitives: vec![cursor_primitive, text_value],
- },
- Vector::new(offset as u32, 0),
- )
- } else {
- (text_value, Vector::new(0, 0))
- };
-
- let text_width = self.measure_value(
- if text.is_empty() { placeholder } else { &text },
- size,
- font,
- );
-
- let contents = if text_width > text_bounds.width {
- Primitive::Clip {
- bounds: text_bounds,
- offset,
- content: Box::new(contents_primitive),
- }
- } else {
- contents_primitive
- };
-
- (
- Primitive::Group {
- primitives: vec![input, contents],
- },
- if is_mouse_over {
- mouse::Interaction::Text
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
-
-fn measure_cursor_and_scroll_offset<B>(
- renderer: &Renderer<B>,
- text_bounds: Rectangle,
- value: &text_input::Value,
- size: u16,
- cursor_index: usize,
- font: Font,
-) -> (f32, f32)
-where
- B: Backend + backend::Text,
-{
- use iced_native::text_input::Renderer;
-
- let text_before_cursor = value.until(cursor_index).to_string();
-
- let text_value_width =
- renderer.measure_value(&text_before_cursor, size, font);
- let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
-
- (text_value_width, offset)
-}
+ iced_native::widget::TextInput<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/toggler.rs b/graphics/src/widget/toggler.rs
index 852d18ee..9053e6ed 100644
--- a/graphics/src/widget/toggler.rs
+++ b/graphics/src/widget/toggler.rs
@@ -1,99 +1,10 @@
//! Show toggle controls using togglers.
-use crate::backend::{self, Backend};
-use crate::{Primitive, Renderer};
-use iced_native::mouse;
-use iced_native::toggler;
-use iced_native::Rectangle;
+use crate::Renderer;
pub use iced_style::toggler::{Style, StyleSheet};
-/// Makes sure that the border radius of the toggler looks good at every size.
-const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
-
-/// The space ratio between the background Quad and the Toggler bounds, and
-/// between the background Quad and foreground Quad.
-const SPACE_RATIO: f32 = 0.05;
-
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
-pub type Toggler<Message, Backend> =
- iced_native::Toggler<Message, Renderer<Backend>>;
-
-impl<B> toggler::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- type Style = Box<dyn StyleSheet>;
-
- const DEFAULT_SIZE: u16 = 20;
-
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_active: bool,
- is_mouse_over: bool,
- label: Option<Self::Output>,
- style_sheet: &Self::Style,
- ) -> Self::Output {
- let style = if is_mouse_over {
- style_sheet.hovered(is_active)
- } else {
- style_sheet.active(is_active)
- };
-
- let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
- let space = SPACE_RATIO * bounds.height as f32;
-
- let toggler_background_bounds = Rectangle {
- x: bounds.x + space,
- y: bounds.y + space,
- width: bounds.width - (2.0 * space),
- height: bounds.height - (2.0 * space),
- };
-
- let toggler_background = Primitive::Quad {
- bounds: toggler_background_bounds,
- background: style.background.into(),
- border_radius,
- border_width: 1.0,
- border_color: style.background_border.unwrap_or(style.background),
- };
-
- let toggler_foreground_bounds = Rectangle {
- x: bounds.x
- + if is_active {
- bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
- } else {
- 2.0 * space
- },
- y: bounds.y + (2.0 * space),
- width: bounds.height - (4.0 * space),
- height: bounds.height - (4.0 * space),
- };
-
- let toggler_foreground = Primitive::Quad {
- bounds: toggler_foreground_bounds,
- background: style.foreground.into(),
- border_radius,
- border_width: 1.0,
- border_color: style.foreground_border.unwrap_or(style.foreground),
- };
-
- (
- Primitive::Group {
- primitives: match label {
- Some((l, _)) => {
- vec![l, toggler_background, toggler_foreground]
- }
- None => vec![toggler_background, toggler_foreground],
- },
- },
- if is_mouse_over {
- mouse::Interaction::Pointer
- } else {
- mouse::Interaction::default()
- },
- )
- }
-}
+pub type Toggler<'a, Message, Backend> =
+ iced_native::widget::Toggler<'a, Message, Renderer<Backend>>;
diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs
index 493a6389..7dc12ed4 100644
--- a/graphics/src/widget/tooltip.rs
+++ b/graphics/src/widget/tooltip.rs
@@ -1,168 +1,11 @@
//! Decorate content and apply alignment.
-use crate::backend::{self, Backend};
-use crate::defaults::{self, Defaults};
-use crate::{Primitive, Renderer, Vector};
-
-use iced_native::container;
-use iced_native::layout::{self, Layout};
-use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
+use crate::Renderer;
/// An element decorating some content.
///
/// This is an alias of an `iced_native` tooltip with a default
/// `Renderer`.
pub type Tooltip<'a, Message, Backend> =
- iced_native::Tooltip<'a, Message, Renderer<Backend>>;
-
-pub use iced_native::tooltip::Position;
-
-impl<B> iced_native::tooltip::Renderer for Renderer<B>
-where
- B: Backend + backend::Text,
-{
- const DEFAULT_PADDING: u16 = 5;
-
- fn draw<Message>(
- &mut self,
- defaults: &Defaults,
- cursor_position: Point,
- content_layout: Layout<'_>,
- viewport: &Rectangle,
- content: &Element<'_, Message, Self>,
- tooltip: &Text<Self>,
- position: Position,
- style_sheet: &<Self as container::Renderer>::Style,
- gap: u16,
- padding: u16,
- ) -> Self::Output {
- let (content, mouse_interaction) = content.draw(
- self,
- &defaults,
- content_layout,
- cursor_position,
- viewport,
- );
-
- let bounds = content_layout.bounds();
-
- if bounds.contains(cursor_position) {
- use iced_native::Widget;
-
- let gap = f32::from(gap);
- let style = style_sheet.style();
-
- let defaults = Defaults {
- text: defaults::Text {
- color: style.text_color.unwrap_or(defaults.text.color),
- },
- };
-
- let text_layout = Widget::<(), Self>::layout(
- tooltip,
- self,
- &layout::Limits::new(Size::ZERO, viewport.size())
- .pad(Padding::new(padding)),
- );
-
- let padding = f32::from(padding);
- let text_bounds = text_layout.bounds();
- let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
- let y_center =
- bounds.y + (bounds.height - text_bounds.height) / 2.0;
-
- let mut tooltip_bounds = {
- let offset = match position {
- Position::Top => Vector::new(
- x_center,
- bounds.y - text_bounds.height - gap - padding,
- ),
- Position::Bottom => Vector::new(
- x_center,
- bounds.y + bounds.height + gap + padding,
- ),
- Position::Left => Vector::new(
- bounds.x - text_bounds.width - gap - padding,
- y_center,
- ),
- Position::Right => Vector::new(
- bounds.x + bounds.width + gap + padding,
- y_center,
- ),
- Position::FollowCursor => Vector::new(
- cursor_position.x,
- cursor_position.y - text_bounds.height,
- ),
- };
-
- Rectangle {
- x: offset.x - padding,
- y: offset.y - padding,
- width: text_bounds.width + padding * 2.0,
- height: text_bounds.height + padding * 2.0,
- }
- };
-
- if tooltip_bounds.x < viewport.x {
- tooltip_bounds.x = viewport.x;
- } else if viewport.x + viewport.width
- < tooltip_bounds.x + tooltip_bounds.width
- {
- tooltip_bounds.x =
- viewport.x + viewport.width - tooltip_bounds.width;
- }
-
- if tooltip_bounds.y < viewport.y {
- tooltip_bounds.y = viewport.y;
- } else if viewport.y + viewport.height
- < tooltip_bounds.y + tooltip_bounds.height
- {
- tooltip_bounds.y =
- viewport.y + viewport.height - tooltip_bounds.height;
- }
-
- let (tooltip, _) = Widget::<(), Self>::draw(
- tooltip,
- self,
- &defaults,
- Layout::with_offset(
- Vector::new(
- tooltip_bounds.x + padding,
- tooltip_bounds.y + padding,
- ),
- &text_layout,
- ),
- cursor_position,
- viewport,
- );
+ iced_native::widget::Tooltip<'a, Message, Renderer<Backend>>;
- (
- Primitive::Group {
- primitives: vec![
- content,
- Primitive::Clip {
- bounds: *viewport,
- offset: Vector::new(0, 0),
- content: Box::new(
- if let Some(background) =
- crate::container::background(
- tooltip_bounds,
- &style,
- )
- {
- Primitive::Group {
- primitives: vec![background, tooltip],
- }
- } else {
- tooltip
- },
- ),
- },
- ],
- },
- mouse_interaction,
- )
- } else {
- (content, mouse_interaction)
- }
- }
-}
+pub use iced_native::widget::tooltip::Position;
diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs
index 37edef1d..9ea040cd 100644
--- a/graphics/src/window/compositor.rs
+++ b/graphics/src/window/compositor.rs
@@ -1,7 +1,5 @@
use crate::{Color, Error, Viewport};
-use iced_native::mouse;
-
use raw_window_handle::HasRawWindowHandle;
use thiserror::Error;
@@ -30,9 +28,8 @@ pub trait Compositor: Sized {
window: &W,
) -> Self::Surface;
- /// Crates a new [`SwapChain`] for the given [`Surface`].
+ /// Configures a new [`Surface`] with the given dimensions.
///
- /// [`SwapChain`]: Self::SwapChain
/// [`Surface`]: Self::Surface
fn configure_surface(
&mut self,
@@ -41,18 +38,17 @@ pub trait Compositor: Sized {
height: u32,
);
- /// Draws the output primitives to the next frame of the given [`SwapChain`].
+ /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
///
/// [`SwapChain`]: Self::SwapChain
- fn draw<T: AsRef<str>>(
+ fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
- output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
- ) -> Result<mouse::Interaction, SurfaceError>;
+ ) -> Result<(), SurfaceError>;
}
/// Result of an unsuccessful call to [`Compositor::draw`].
@@ -63,13 +59,13 @@ pub enum SurfaceError {
"A timeout was encountered while trying to acquire the next frame"
)]
Timeout,
- /// The underlying surface has changed, and therefore the swap chain must be updated.
+ /// The underlying surface has changed, and therefore the surface must be updated.
#[error(
- "The underlying surface has changed, and therefore the swap chain must be updated."
+ "The underlying surface has changed, and therefore the surface must be updated."
)]
Outdated,
/// The swap chain has been lost and needs to be recreated.
- #[error("The swap chain has been lost and needs to be recreated")]
+ #[error("The surface has been lost and needs to be recreated")]
Lost,
/// There is no more memory left to allocate a new frame.
#[error("There is no more memory left to allocate a new frame")]
diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs
index 34d70be3..b1b995f1 100644
--- a/graphics/src/window/gl_compositor.rs
+++ b/graphics/src/window/gl_compositor.rs
@@ -1,5 +1,4 @@
use crate::{Color, Error, Size, Viewport};
-use iced_native::mouse;
use core::ffi::c_void;
@@ -49,15 +48,15 @@ pub trait GLCompositor: Sized {
/// Resizes the viewport of the [`GLCompositor`].
fn resize_viewport(&mut self, physical_size: Size<u32>);
- /// Draws the provided output with the given [`Renderer`].
+ /// Presents the primitives of the [`Renderer`] to the next frame of the
+ /// [`GLCompositor`].
///
/// [`Renderer`]: crate::Renderer
- fn draw<T: AsRef<str>>(
+ fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
background_color: Color,
- output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
- ) -> mouse::Interaction;
+ );
}
diff --git a/native/Cargo.toml b/native/Cargo.toml
index a3134ef4..5de99b2e 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -23,3 +23,7 @@ path = "../core"
version = "0.3"
path = "../futures"
features = ["thread-pool"]
+
+[dependencies.iced_style]
+version = "0.3"
+path = "../style"
diff --git a/native/src/element.rs b/native/src/element.rs
index 5c84a388..ee404a1c 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -1,6 +1,8 @@
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
};
@@ -77,7 +79,7 @@ where
///
/// ```
/// # mod counter {
- /// # type Text = iced_native::Text<iced_native::renderer::Null>;
+ /// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
@@ -104,7 +106,8 @@ where
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
- /// use iced_native::{Element, Row};
+ /// use iced_native::Element;
+ /// use iced_native::widget::Row;
/// use iced_wgpu::Renderer;
///
/// impl ManyCounters {
@@ -189,7 +192,7 @@ where
) -> Element<'a, Message, Renderer>
where
Message: 'static,
- Renderer: 'a + layout::Debugger,
+ Renderer: 'a,
{
Element {
widget: Box::new(Explain::new(self, color.into())),
@@ -241,13 +244,24 @@ where
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.widget
+ .draw(renderer, style, layout, cursor_position, viewport)
+ }
+
+ /// Returns the current [`mouse::Interaction`] of the [`Element`].
+ pub fn mouse_interaction(
+ &self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
self.widget
- .draw(renderer, defaults, layout, cursor_position, viewport)
+ .mouse_interaction(layout, cursor_position, viewport)
}
/// Computes the _layout_ hash of the [`Element`].
@@ -336,13 +350,23 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.widget
+ .draw(renderer, style, layout, cursor_position, viewport)
+ }
+
+ fn mouse_interaction(
+ &self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
self.widget
- .draw(renderer, defaults, layout, cursor_position, viewport)
+ .mouse_interaction(layout, cursor_position, viewport)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -378,7 +402,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
- Renderer: crate::Renderer + layout::Debugger,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.element.widget.width()
@@ -418,19 +442,51 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.explain(
- defaults,
- self.element.widget.as_ref(),
+ ) {
+ fn explain_layout<Renderer: crate::Renderer>(
+ renderer: &mut Renderer,
+ color: Color,
+ layout: Layout<'_>,
+ ) {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: layout.bounds(),
+ border_color: color,
+ border_width: 1.0,
+ border_radius: 0.0,
+ },
+ Color::TRANSPARENT,
+ );
+
+ for child in layout.children() {
+ explain_layout(renderer, color, child);
+ }
+ }
+
+ self.element.widget.draw(
+ renderer,
+ style,
layout,
cursor_position,
viewport,
- self.color,
- )
+ );
+
+ explain_layout(renderer, self.color, layout);
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.element
+ .widget
+ .mouse_interaction(layout, cursor_position, viewport)
}
fn hash_layout(&self, state: &mut Hasher) {
diff --git a/native/src/image.rs b/native/src/image.rs
new file mode 100644
index 00000000..00379417
--- /dev/null
+++ b/native/src/image.rs
@@ -0,0 +1,124 @@
+//! Load and draw raster graphics.
+use crate::{Hasher, Rectangle};
+
+use std::hash::{Hash, Hasher as _};
+use std::path::PathBuf;
+use std::sync::Arc;
+
+/// An [`Image`] handle.
+#[derive(Debug, Clone)]
+pub struct Handle {
+ id: u64,
+ data: Arc<Data>,
+}
+
+impl Handle {
+ /// Creates an image [`Handle`] pointing to the image of the given path.
+ ///
+ /// Makes an educated guess about the image format by examining the data in the file.
+ pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
+ Self::from_data(Data::Path(path.into()))
+ }
+
+ /// Creates an image [`Handle`] containing the image pixels directly. This
+ /// function expects the input data to be provided as a `Vec<u8>` of BGRA
+ /// pixels.
+ ///
+ /// This is useful if you have already decoded your image.
+ pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
+ Self::from_data(Data::Pixels {
+ width,
+ height,
+ pixels,
+ })
+ }
+
+ /// Creates an image [`Handle`] containing the image data directly.
+ ///
+ /// Makes an educated guess about the image format by examining the given data.
+ ///
+ /// This is useful if you already have your image loaded in-memory, maybe
+ /// because you downloaded or generated it procedurally.
+ pub fn from_memory(bytes: Vec<u8>) -> Handle {
+ Self::from_data(Data::Bytes(bytes))
+ }
+
+ fn from_data(data: Data) -> Handle {
+ let mut hasher = Hasher::default();
+ data.hash(&mut hasher);
+
+ Handle {
+ id: hasher.finish(),
+ data: Arc::new(data),
+ }
+ }
+
+ /// Returns the unique identifier of the [`Handle`].
+ pub fn id(&self) -> u64 {
+ self.id
+ }
+
+ /// Returns a reference to the image [`Data`].
+ pub fn data(&self) -> &Data {
+ &self.data
+ }
+}
+
+impl<T> From<T> for Handle
+where
+ T: Into<PathBuf>,
+{
+ fn from(path: T) -> Handle {
+ Handle::from_path(path.into())
+ }
+}
+
+impl Hash for Handle {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ }
+}
+
+/// The data of an [`Image`].
+#[derive(Clone, Hash)]
+pub enum Data {
+ /// File data
+ Path(PathBuf),
+
+ /// In-memory data
+ Bytes(Vec<u8>),
+
+ /// Decoded image pixels in BGRA format.
+ Pixels {
+ /// The width of the image.
+ width: u32,
+ /// The height of the image.
+ height: u32,
+ /// The pixels.
+ pixels: Vec<u8>,
+ },
+}
+
+impl std::fmt::Debug for Data {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Bytes(_) => write!(f, "Bytes(...)"),
+ Data::Pixels { width, height, .. } => {
+ write!(f, "Pixels({} * {})", width, height)
+ }
+ }
+ }
+}
+
+/// A [`Renderer`] that can render raster graphics.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer: crate::Renderer {
+ /// Returns the dimensions of an image for the given [`Handle`].
+ fn dimensions(&self, handle: &Handle) -> (u32, u32);
+
+ /// Draws an image with the given [`Handle`] and inside the provided
+ /// `bounds`.
+ fn draw(&mut self, handle: Handle, bounds: Rectangle);
+}
diff --git a/native/src/layout.rs b/native/src/layout.rs
index b4b4a021..04954fb9 100644
--- a/native/src/layout.rs
+++ b/native/src/layout.rs
@@ -1,11 +1,9 @@
//! Position your widgets properly.
-mod debugger;
mod limits;
mod node;
pub mod flex;
-pub use debugger::Debugger;
pub use limits::Limits;
pub use node::Node;
diff --git a/native/src/layout/debugger.rs b/native/src/layout/debugger.rs
deleted file mode 100644
index 0759613f..00000000
--- a/native/src/layout/debugger.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use crate::{Color, Layout, Point, Rectangle, Renderer, Widget};
-
-/// A renderer able to graphically explain a [`Layout`].
-pub trait Debugger: Renderer {
- /// Explains the [`Layout`] of an [`Element`] for debugging purposes.
- ///
- /// This will be called when [`Element::explain`] has been used. It should
- /// _explain_ the given [`Layout`] graphically.
- ///
- /// A common approach consists in recursively rendering the bounds of the
- /// [`Layout`] and its children.
- ///
- /// [`Element`]: crate::Element
- /// [`Element::explain`]: crate::Element::explain
- fn explain<Message>(
- &mut self,
- defaults: &Self::Defaults,
- widget: &dyn Widget<Message, Self>,
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- color: Color,
- ) -> Self::Output;
-}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 170a588b..51b232e9 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -36,6 +36,7 @@
pub mod clipboard;
pub mod command;
pub mod event;
+pub mod image;
pub mod keyboard;
pub mod layout;
pub mod mouse;
@@ -43,6 +44,8 @@ pub mod overlay;
pub mod program;
pub mod renderer;
pub mod subscription;
+pub mod svg;
+pub mod text;
pub mod touch;
pub mod widget;
pub mod window;
@@ -84,4 +87,4 @@ pub use renderer::Renderer;
pub use runtime::Runtime;
pub use subscription::Subscription;
pub use user_interface::{Cache, UserInterface};
-pub use widget::*;
+pub use widget::Widget;
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 84145e7f..1ac3cea5 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -8,7 +8,9 @@ pub use menu::Menu;
use crate::event::{self, Event};
use crate::layout;
-use crate::{Clipboard, Hasher, Layout, Point, Size};
+use crate::mouse;
+use crate::renderer;
+use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
@@ -32,10 +34,10 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output;
+ );
/// Computes the _layout_ hash of the [`Overlay`].
///
@@ -73,4 +75,16 @@ where
) -> event::Status {
event::Status::Ignored
}
+
+ /// Returns the current [`mouse::Interaction`] of the [`Widget`].
+ ///
+ /// By default, it returns [`mouse::Interaction::Idle`].
+ fn mouse_interaction(
+ &self,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ mouse::Interaction::Idle
+ }
}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
index e4819037..f418a518 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -2,7 +2,9 @@ pub use crate::Overlay;
use crate::event::{self, Event};
use crate::layout;
-use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
+use crate::mouse;
+use crate::renderer;
+use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size, Vector};
/// A generic [`Overlay`].
#[allow(missing_debug_implementations)]
@@ -67,16 +69,26 @@ where
)
}
+ /// Returns the current [`mouse::Interaction`] of the [`Element`].
+ pub fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.overlay
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
/// Draws the [`Element`] and its children using the given [`Layout`].
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output {
- self.overlay
- .draw(renderer, defaults, layout, cursor_position)
+ ) {
+ self.overlay.draw(renderer, style, layout, cursor_position)
}
/// Computes the _layout_ hash of the [`Element`].
@@ -139,15 +151,24 @@ where
event_status
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.content
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output {
- self.content
- .draw(renderer, defaults, layout, cursor_position)
+ ) {
+ self.content.draw(renderer, style, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher, position: Point) {
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index f62dcb46..ee3bee6e 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -1,20 +1,24 @@
//! Build and show dropdown menus.
-use crate::container;
+use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
-use crate::scrollable;
-use crate::text;
+use crate::renderer;
+use crate::text::{self, Text};
use crate::touch;
+use crate::widget::scrollable::{self, Scrollable};
+use crate::widget::Container;
use crate::{
- Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
- Rectangle, Scrollable, Size, Vector, Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Size, Vector, Widget,
};
+pub use iced_style::menu::Style;
+
/// A list of selectable options.
#[allow(missing_debug_implementations)]
-pub struct Menu<'a, T, Renderer: self::Renderer> {
+pub struct Menu<'a, T, Renderer: text::Renderer> {
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
@@ -23,13 +27,13 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- style: <Renderer as self::Renderer>::Style,
+ style: Style,
}
impl<'a, T, Renderer> Menu<'a, T, Renderer>
where
T: ToString + Clone,
- Renderer: self::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
{
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
/// the message to produced when an option is selected.
@@ -77,10 +81,7 @@ where
}
/// Sets the style of the [`Menu`].
- pub fn style(
- mut self,
- style: impl Into<<Renderer as self::Renderer>::Style>,
- ) -> Self {
+ pub fn style(mut self, style: impl Into<Style>) -> Self {
self.style = style.into();
self
}
@@ -116,14 +117,14 @@ impl State {
}
}
-struct Overlay<'a, Message, Renderer: self::Renderer> {
+struct Overlay<'a, Message, Renderer: text::Renderer> {
container: Container<'a, Message, Renderer>,
width: u16,
target_height: f32,
- style: <Renderer as self::Renderer>::Style,
+ style: Style,
}
-impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer>
+impl<'a, Message, Renderer: text::Renderer> Overlay<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a,
@@ -168,7 +169,7 @@ where
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn layout(
&self,
@@ -233,45 +234,55 @@ where
)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.container
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
- ) -> Renderer::Output {
- let primitives = self.container.draw(
- renderer,
- defaults,
- layout,
- cursor_position,
- &layout.bounds(),
+ ) {
+ let bounds = layout.bounds();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: self.style.border_color,
+ border_width: self.style.border_width,
+ border_radius: 0.0,
+ },
+ self.style.background,
);
- renderer.decorate(
- layout.bounds(),
- cursor_position,
- &self.style,
- primitives,
- )
+ self.container
+ .draw(renderer, style, layout, cursor_position, &bounds);
}
}
-struct List<'a, T, Renderer: self::Renderer> {
+struct List<'a, T, Renderer: text::Renderer> {
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- style: <Renderer as self::Renderer>::Style,
+ style: Style,
}
-impl<'a, T, Message, Renderer: self::Renderer> Widget<Message, Renderer>
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for List<'a, T, Renderer>
where
T: Clone + ToString,
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
Length::Fill
@@ -376,65 +387,84 @@ where
event::Status::Ignored
}
- fn draw(
+ fn mouse_interaction(
&self,
- renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
- viewport: &Rectangle,
- ) -> Renderer::Output {
- self::Renderer::draw(
- renderer,
- layout.bounds(),
- cursor_position,
- viewport,
- self.options,
- *self.hovered_option,
- self.padding,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- &self.style,
- )
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let is_mouse_over = layout.bounds().contains(cursor_position);
+
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
}
-}
-/// The renderer of a [`Menu`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Menu`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer:
- scrollable::Renderer + container::Renderer + text::Renderer
-{
- /// The [`Menu`] style supported by this renderer.
- type Style: Default + Clone;
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
- /// Decorates a the list of options of a [`Menu`].
- ///
- /// This method can be used to draw a background for the [`Menu`].
- fn decorate(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- style: &<Self as Renderer>::Style,
- primitive: Self::Output,
- ) -> Self::Output;
+ let text_size = self.text_size.unwrap_or(renderer.default_size());
+ let option_height = (text_size + self.padding.vertical()) as usize;
+
+ let offset = viewport.y - bounds.y;
+ let start = (offset / option_height as f32) as usize;
+ let end =
+ ((offset + viewport.height) / option_height as f32).ceil() as usize;
+
+ let visible_options = &self.options[start..end.min(self.options.len())];
+
+ for (i, option) in visible_options.iter().enumerate() {
+ let i = start + i;
+ let is_selected = *self.hovered_option == Some(i);
+
+ let bounds = Rectangle {
+ x: bounds.x,
+ y: bounds.y + (option_height * i) as f32,
+ width: bounds.width,
+ height: f32::from(text_size + self.padding.vertical()),
+ };
+
+ if is_selected {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: Color::TRANSPARENT,
+ border_width: 0.0,
+ border_radius: 0.0,
+ },
+ self.style.selected_background,
+ );
+ }
- /// Draws the list of options of a [`Menu`].
- fn draw<T: ToString>(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- viewport: &Rectangle,
- options: &[T],
- hovered_option: Option<usize>,
- padding: Padding,
- text_size: u16,
- font: Self::Font,
- style: &<Self as Renderer>::Style,
- ) -> Self::Output;
+ renderer.fill_text(Text {
+ content: &option.to_string(),
+ bounds: Rectangle {
+ x: bounds.x + self.padding.left as f32,
+ y: bounds.center_y(),
+ width: f32::INFINITY,
+ ..bounds
+ },
+ size: f32::from(text_size),
+ font: self.font,
+ color: if is_selected {
+ self.style.selected_text_color
+ } else {
+ self.style.text_color
+ },
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ }
+ }
}
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
@@ -442,7 +472,7 @@ impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
where
T: ToString + Clone,
Message: 'a,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + text::Renderer,
{
fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self)
diff --git a/native/src/program/state.rs b/native/src/program/state.rs
index 3f5f6069..26c0eb21 100644
--- a/native/src/program/state.rs
+++ b/native/src/program/state.rs
@@ -1,5 +1,6 @@
+use crate::mouse;
use crate::{
- Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
+ Cache, Clipboard, Command, Debug, Event, Point, Program, Size,
UserInterface,
};
@@ -12,9 +13,9 @@ where
{
program: P,
cache: Option<Cache>,
- primitive: <P::Renderer as Renderer>::Output,
queued_events: Vec<Event>,
queued_messages: Vec<P::Message>,
+ mouse_interaction: mouse::Interaction,
}
impl<P> State<P>
@@ -26,11 +27,10 @@ where
pub fn new(
mut program: P,
bounds: Size,
- cursor_position: Point,
renderer: &mut P::Renderer,
debug: &mut Debug,
) -> Self {
- let mut user_interface = build_user_interface(
+ let user_interface = build_user_interface(
&mut program,
Cache::default(),
renderer,
@@ -38,18 +38,14 @@ where
debug,
);
- debug.draw_started();
- let primitive = user_interface.draw(renderer, cursor_position);
- debug.draw_finished();
-
let cache = Some(user_interface.into_cache());
State {
program,
cache,
- primitive,
queued_events: Vec::new(),
queued_messages: Vec::new(),
+ mouse_interaction: mouse::Interaction::Idle,
}
}
@@ -58,11 +54,6 @@ where
&self.program
}
- /// Returns a reference to the current rendering primitive of the [`State`].
- pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
- &self.primitive
- }
-
/// Queues an event in the [`State`] for processing during an [`update`].
///
/// [`update`]: Self::update
@@ -82,6 +73,11 @@ where
self.queued_events.is_empty() && self.queued_messages.is_empty()
}
+ /// Returns the current [`mouse::Interaction`] of the [`State`].
+ pub fn mouse_interaction(&self) -> mouse::Interaction {
+ self.mouse_interaction
+ }
+
/// Processes all the queued events and messages, rebuilding and redrawing
/// the widgets of the linked [`Program`] if necessary.
///
@@ -120,7 +116,8 @@ where
if messages.is_empty() {
debug.draw_started();
- self.primitive = user_interface.draw(renderer, cursor_position);
+ self.mouse_interaction =
+ user_interface.draw(renderer, cursor_position);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -151,7 +148,8 @@ where
);
debug.draw_started();
- self.primitive = user_interface.draw(renderer, cursor_position);
+ self.mouse_interaction =
+ user_interface.draw(renderer, cursor_position);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
diff --git a/native/src/renderer.rs b/native/src/renderer.rs
index 39a6cff1..ca7ad5a2 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -19,28 +19,17 @@
//! [`text::Renderer`]: crate::widget::text::Renderer
//! [`Checkbox`]: crate::widget::Checkbox
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
-
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
-use crate::{layout, Element, Rectangle};
+use crate::layout;
+use crate::{Background, Color, Element, Rectangle, Vector};
/// A component that can take the state of a user interface and produce an
/// output for its users.
pub trait Renderer: Sized {
- /// The type of output of the [`Renderer`].
- ///
- /// If you are implementing a graphical renderer, your output will most
- /// likely be a tree of visual primitives.
- type Output;
-
- /// The default styling attributes of the [`Renderer`].
- ///
- /// This type can be leveraged to implement style inheritance.
- type Defaults: Default;
-
/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
@@ -53,12 +42,52 @@ pub trait Renderer: Sized {
element.layout(self, limits)
}
- /// Overlays the `overlay` output with the given bounds on top of the `base`
- /// output.
- fn overlay(
+ /// 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));
+
+ /// Applies a `translation` to the primitives recorded in the given closure.
+ fn with_translation(
&mut self,
- base: Self::Output,
- overlay: Self::Output,
- overlay_bounds: Rectangle,
- ) -> Self::Output;
+ translation: Vector,
+ f: impl FnOnce(&mut Self),
+ );
+
+ /// Clears all of the recorded primitives in the [`Renderer`].
+ fn clear(&mut self);
+
+ /// Fills a [`Quad`] with the provided [`Background`].
+ fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
+}
+
+/// A polygon with four sides.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Quad {
+ /// The bounds of the [`Quad`].
+ pub bounds: Rectangle,
+
+ /// The border radius of the [`Quad`].
+ pub border_radius: f32,
+
+ /// The border width of the [`Quad`].
+ pub border_width: f32,
+
+ /// The border color of the [`Quad`].
+ pub border_color: Color,
+}
+
+/// The styling attributes of a [`Renderer`].
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Style {
+ /// The text color
+ pub text_color: Color,
+}
+
+impl Default for Style {
+ fn default() -> Self {
+ Style {
+ text_color: Color::BLACK,
+ }
+ }
}
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index b5921582..a5b2f277 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -1,20 +1,6 @@
-use crate::alignment;
-use crate::button;
-use crate::checkbox;
-use crate::column;
-use crate::container;
-use crate::pane_grid;
-use crate::progress_bar;
-use crate::radio;
-use crate::row;
-use crate::scrollable;
-use crate::slider;
-use crate::text;
-use crate::text_input;
-use crate::toggler;
-use crate::{
- Color, Element, Font, Layout, Padding, Point, Rectangle, Renderer, Size,
-};
+use crate::renderer::{self, Renderer};
+use crate::text::{self, Text};
+use crate::{Background, Font, Point, Rectangle, Size, Vector};
/// A renderer that does nothing.
///
@@ -30,33 +16,21 @@ impl Null {
}
impl Renderer for Null {
- type Output = ();
- type Defaults = ();
+ fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
- fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) {
- }
-}
-
-impl column::Renderer for Null {
- fn draw<Message>(
+ fn with_translation(
&mut self,
- _defaults: &Self::Defaults,
- _content: &[Element<'_, Message, Self>],
- _layout: Layout<'_>,
- _cursor_position: Point,
- _viewport: &Rectangle,
+ _translation: Vector,
+ _f: impl FnOnce(&mut Self),
) {
}
-}
-impl row::Renderer for Null {
- fn draw<Message>(
+ fn clear(&mut self) {}
+
+ fn fill_quad(
&mut self,
- _defaults: &Self::Defaults,
- _content: &[Element<'_, Message, Self>],
- _layout: Layout<'_>,
- _cursor_position: Point,
- _viewport: &Rectangle,
+ _quad: renderer::Quad,
+ _background: impl Into<Background>,
) {
}
}
@@ -64,6 +38,10 @@ impl row::Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
+ const ICON_FONT: Font = Font::Default;
+ const CHECKMARK_ICON: char = '0';
+ const ARROW_DOWN_ICON: char = '0';
+
fn default_size(&self) -> u16 {
20
}
@@ -90,240 +68,5 @@ impl text::Renderer for Null {
None
}
- fn draw(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _content: &str,
- _size: u16,
- _font: Font,
- _color: Option<Color>,
- _horizontal_alignment: alignment::Horizontal,
- _vertical_alignment: alignment::Vertical,
- ) {
- }
-}
-
-impl scrollable::Renderer for Null {
- type Style = ();
-
- fn scrollbar(
- &self,
- _bounds: Rectangle,
- _content_bounds: Rectangle,
- _offset: u32,
- _scrollbar_width: u16,
- _scrollbar_margin: u16,
- _scroller_width: u16,
- ) -> Option<scrollable::Scrollbar> {
- None
- }
-
- fn draw(
- &mut self,
- _scrollable: &scrollable::State,
- _bounds: Rectangle,
- _content_bounds: Rectangle,
- _is_mouse_over: bool,
- _is_mouse_over_scrollbar: bool,
- _scrollbar: Option<scrollable::Scrollbar>,
- _offset: u32,
- _style: &Self::Style,
- _content: Self::Output,
- ) {
- }
-}
-
-impl text_input::Renderer for Null {
- type Style = ();
-
- fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
- 0.0
- }
-
- fn offset(
- &self,
- _text_bounds: Rectangle,
- _font: Font,
- _size: u16,
- _value: &text_input::Value,
- _state: &text_input::State,
- ) -> f32 {
- 0.0
- }
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _text_bounds: Rectangle,
- _cursor_position: Point,
- _font: Font,
- _size: u16,
- _placeholder: &str,
- _value: &text_input::Value,
- _state: &text_input::State,
- _style: &Self::Style,
- ) -> Self::Output {
- }
-}
-
-impl button::Renderer for Null {
- const DEFAULT_PADDING: Padding = Padding::ZERO;
-
- type Style = ();
-
- fn draw<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _cursor_position: Point,
- _is_disabled: bool,
- _is_pressed: bool,
- _style: &Self::Style,
- _content: &Element<'_, Message, Self>,
- _content_layout: Layout<'_>,
- ) -> Self::Output {
- }
-}
-
-impl radio::Renderer for Null {
- type Style = ();
-
- const DEFAULT_SIZE: u16 = 20;
- const DEFAULT_SPACING: u16 = 15;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _is_selected: bool,
- _is_mouse_over: bool,
- _label: Self::Output,
- _style: &Self::Style,
- ) {
- }
-}
-
-impl checkbox::Renderer for Null {
- type Style = ();
-
- const DEFAULT_SIZE: u16 = 20;
- const DEFAULT_SPACING: u16 = 15;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _is_checked: bool,
- _is_mouse_over: bool,
- _label: Self::Output,
- _style: &Self::Style,
- ) {
- }
-}
-
-impl slider::Renderer for Null {
- type Style = ();
-
- const DEFAULT_HEIGHT: u16 = 30;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _cursor_position: Point,
- _range: std::ops::RangeInclusive<f32>,
- _value: f32,
- _is_dragging: bool,
- _style_sheet: &Self::Style,
- ) {
- }
-}
-
-impl progress_bar::Renderer for Null {
- type Style = ();
-
- const DEFAULT_HEIGHT: u16 = 30;
-
- fn draw(
- &self,
- _bounds: Rectangle,
- _range: std::ops::RangeInclusive<f32>,
- _value: f32,
- _style: &Self::Style,
- ) {
- }
-}
-
-impl container::Renderer for Null {
- type Style = ();
-
- fn draw<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _cursor_position: Point,
- _viewport: &Rectangle,
- _style: &Self::Style,
- _content: &Element<'_, Message, Self>,
- _content_layout: Layout<'_>,
- ) {
- }
-}
-
-impl pane_grid::Renderer for Null {
- type Style = ();
-
- fn draw<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
- _dragging: Option<(pane_grid::Pane, Point)>,
- _resizing: Option<(pane_grid::Axis, Rectangle, bool)>,
- _layout: Layout<'_>,
- _style: &<Self as pane_grid::Renderer>::Style,
- _cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- }
-
- fn draw_pane<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _style: &<Self as container::Renderer>::Style,
- _title_bar: Option<(
- &pane_grid::TitleBar<'_, Message, Self>,
- Layout<'_>,
- )>,
- _body: (&Element<'_, Message, Self>, Layout<'_>),
- _cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- }
-
- fn draw_title_bar<Message>(
- &mut self,
- _defaults: &Self::Defaults,
- _bounds: Rectangle,
- _style: &<Self as container::Renderer>::Style,
- _content: (&Element<'_, Message, Self>, Layout<'_>),
- _controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
- _cursor_position: Point,
- _viewport: &Rectangle,
- ) {
- }
-}
-
-impl toggler::Renderer for Null {
- type Style = ();
-
- const DEFAULT_SIZE: u16 = 20;
-
- fn draw(
- &mut self,
- _bounds: Rectangle,
- _is_checked: bool,
- _is_mouse_over: bool,
- _label: Option<Self::Output>,
- _style: &Self::Style,
- ) {
- }
+ fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
}
diff --git a/native/src/svg.rs b/native/src/svg.rs
new file mode 100644
index 00000000..90eff87e
--- /dev/null
+++ b/native/src/svg.rs
@@ -0,0 +1,88 @@
+//! Load and draw vector graphics.
+use crate::{Hasher, Rectangle};
+
+use std::hash::{Hash, Hasher as _};
+use std::path::PathBuf;
+use std::sync::Arc;
+
+/// An [`Svg`] handle.
+#[derive(Debug, Clone)]
+pub struct Handle {
+ id: u64,
+ data: Arc<Data>,
+}
+
+impl Handle {
+ /// Creates an SVG [`Handle`] pointing to the vector image of the given
+ /// path.
+ pub fn from_path(path: impl Into<PathBuf>) -> Handle {
+ Self::from_data(Data::Path(path.into()))
+ }
+
+ /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
+ /// or gzip compressed data.
+ ///
+ /// This is useful if you already have your SVG data in-memory, maybe
+ /// because you downloaded or generated it procedurally.
+ pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
+ Self::from_data(Data::Bytes(bytes.into()))
+ }
+
+ fn from_data(data: Data) -> Handle {
+ let mut hasher = Hasher::default();
+ data.hash(&mut hasher);
+
+ Handle {
+ id: hasher.finish(),
+ data: Arc::new(data),
+ }
+ }
+
+ /// Returns the unique identifier of the [`Handle`].
+ pub fn id(&self) -> u64 {
+ self.id
+ }
+
+ /// Returns a reference to the SVG [`Data`].
+ pub fn data(&self) -> &Data {
+ &self.data
+ }
+}
+
+impl Hash for Handle {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ }
+}
+
+/// The data of an [`Svg`].
+#[derive(Clone, Hash)]
+pub enum Data {
+ /// File data
+ Path(PathBuf),
+
+ /// In-memory data
+ ///
+ /// Can contain an SVG string or a gzip compressed data.
+ Bytes(Vec<u8>),
+}
+
+impl std::fmt::Debug for Data {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Data::Path(path) => write!(f, "Path({:?})", path),
+ Data::Bytes(_) => write!(f, "Bytes(...)"),
+ }
+ }
+}
+
+/// A [`Renderer`] that can render vector graphics.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer: crate::Renderer {
+ /// Returns the default dimensions of an SVG for the given [`Handle`].
+ fn dimensions(&self, handle: &Handle) -> (u32, u32);
+
+ /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`.
+ fn draw(&mut self, handle: Handle, bounds: Rectangle);
+}
diff --git a/native/src/text.rs b/native/src/text.rs
new file mode 100644
index 00000000..8b9205e3
--- /dev/null
+++ b/native/src/text.rs
@@ -0,0 +1,114 @@
+//! Draw and interact with text.
+use crate::alignment;
+use crate::{Color, Point, Rectangle, Size, Vector};
+
+/// A paragraph.
+#[derive(Debug, Clone, Copy)]
+pub struct Text<'a, Font> {
+ /// The content of the paragraph.
+ pub content: &'a str,
+
+ /// The bounds of the paragraph.
+ pub bounds: Rectangle,
+
+ /// The size of the [`Text`].
+ pub size: f32,
+
+ /// The color of the [`Text`].
+ pub color: Color,
+
+ /// The font of the [`Text`].
+ pub font: Font,
+
+ /// The horizontal alignment of the [`Text`].
+ pub horizontal_alignment: alignment::Horizontal,
+
+ /// The vertical alignment of the [`Text`].
+ pub vertical_alignment: alignment::Vertical,
+}
+
+/// The result of hit testing on text.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Hit {
+ /// The point was within the bounds of the returned character index.
+ CharOffset(usize),
+ /// The provided point was not within the bounds of a glyph. The index
+ /// of the character with the closest centeroid position is returned,
+ /// as well as its delta.
+ NearestCharOffset(usize, Vector),
+}
+
+impl Hit {
+ /// Computes the cursor position corresponding to this [`HitTestResult`] .
+ pub fn cursor(self) -> usize {
+ match self {
+ Self::CharOffset(i) => i,
+ Self::NearestCharOffset(i, delta) => {
+ if delta.x > f32::EPSILON {
+ i + 1
+ } else {
+ i
+ }
+ }
+ }
+ }
+}
+
+/// A renderer capable of measuring and drawing [`Text`].
+pub trait Renderer: crate::Renderer {
+ /// The font type used.
+ type Font: Default + Copy;
+
+ /// The icon font of the backend.
+ const ICON_FONT: Self::Font;
+
+ /// The `char` representing a ✔ icon in the [`ICON_FONT`].
+ ///
+ /// [`ICON_FONT`]: Self::ICON_FONT
+ const CHECKMARK_ICON: char;
+
+ /// The `char` representing a â–¼ icon in the built-in [`ICON_FONT`].
+ ///
+ /// [`ICON_FONT`]: Self::ICON_FONT
+ const ARROW_DOWN_ICON: char;
+
+ /// Returns the default size of [`Text`].
+ fn default_size(&self) -> u16;
+
+ /// Measures the text in the given bounds and returns the minimum boundaries
+ /// that can fit the contents.
+ fn measure(
+ &self,
+ content: &str,
+ size: u16,
+ font: Self::Font,
+ bounds: Size,
+ ) -> (f32, f32);
+
+ /// Measures the width of the text as if it were laid out in a single line.
+ fn measure_width(&self, content: &str, size: u16, font: Self::Font) -> f32 {
+ let (width, _) = self.measure(content, size, font, Size::INFINITY);
+
+ width
+ }
+
+ /// Tests whether the provided point is within the boundaries of text
+ /// laid out with the given parameters, returning information about
+ /// the nearest character.
+ ///
+ /// If `nearest_only` is true, the hit test does not consider whether the
+ /// the point is interior to any glyph bounds, returning only the character
+ /// with the nearest centeroid.
+ fn hit_test(
+ &self,
+ contents: &str,
+ size: f32,
+ font: Self::Font,
+ bounds: Size,
+ point: Point,
+ nearest_only: bool,
+ ) -> Option<Hit>;
+
+ /// Draws the given [`Text`].
+ fn fill_text(&mut self, text: Text<'_, Self::Font>);
+}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 8e0d7d1c..39cac559 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,6 +1,8 @@
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@@ -47,7 +49,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::Column;
+ /// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@@ -141,7 +143,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::Column;
+ /// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@@ -277,7 +279,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
- /// # use iced_native::Column;
+ /// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@@ -333,10 +335,13 @@ where
&mut self,
renderer: &mut Renderer,
cursor_position: Point,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
+ // TODO: Move to shell level (?)
+ renderer.clear();
+
let viewport = Rectangle::with_size(self.bounds);
- let overlay = if let Some(mut overlay) =
+ if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
let layer = Self::overlay_layer(
@@ -346,51 +351,81 @@ where
renderer,
);
- let overlay_bounds = layer.layout.bounds();
-
- let overlay_primitives = overlay.draw(
- renderer,
- &Renderer::Defaults::default(),
- Layout::new(&layer.layout),
- cursor_position,
- );
-
self.overlay = Some(layer);
-
- Some((overlay_primitives, overlay_bounds))
- } else {
- None
};
- if let Some((overlay_primitives, overlay_bounds)) = overlay {
- let base_cursor = if overlay_bounds.contains(cursor_position) {
+ if let Some(layer) = &self.overlay {
+ let base_cursor = if layer.layout.bounds().contains(cursor_position)
+ {
Point::new(-1.0, -1.0)
} else {
cursor_position
};
- let base_primitives = self.root.widget.draw(
+ self.root.widget.draw(
renderer,
- &Renderer::Defaults::default(),
+ &renderer::Style::default(),
Layout::new(&self.base.layout),
base_cursor,
&viewport,
);
-
- renderer.overlay(
- base_primitives,
- overlay_primitives,
- overlay_bounds,
- )
} else {
self.root.widget.draw(
renderer,
- &Renderer::Defaults::default(),
+ &renderer::Style::default(),
Layout::new(&self.base.layout),
cursor_position,
&viewport,
- )
- }
+ );
+ };
+
+ let base_interaction = self.root.widget.mouse_interaction(
+ Layout::new(&self.base.layout),
+ cursor_position,
+ &viewport,
+ );
+
+ let Self {
+ overlay,
+ root,
+ base,
+ ..
+ } = self;
+
+ // TODO: Currently, we need to call Widget::overlay twice to
+ // implement the painter's algorithm properly.
+ //
+ // Once we have a proper persistent widget tree, we should be able to
+ // avoid this additional call.
+ overlay
+ .as_ref()
+ .and_then(|layer| {
+ root.overlay(Layout::new(&base.layout)).map(|overlay| {
+ let overlay_interaction = overlay.mouse_interaction(
+ Layout::new(&layer.layout),
+ cursor_position,
+ &viewport,
+ );
+
+ let overlay_bounds = layer.layout.bounds();
+
+ renderer.with_layer(viewport, |renderer| {
+ overlay.draw(
+ renderer,
+ &renderer::Style::default(),
+ Layout::new(&layer.layout),
+ cursor_position,
+ );
+ });
+
+ if overlay_bounds.contains(cursor_position) {
+ overlay_interaction
+ } else {
+ base_interaction
+ }
+ })
+ })
+ .unwrap_or(base_interaction)
}
/// Relayouts and returns a new [`UserInterface`] using the provided
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 43c1b023..07214b16 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -10,14 +10,6 @@
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
//! source of inspiration.
//!
-//! # Re-exports
-//! For convenience, the contents of this module are available at the root
-//! module. Therefore, you can directly type:
-//!
-//! ```
-//! use iced_native::{button, Button, Widget};
-//! ```
-//!
//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
@@ -80,7 +72,9 @@ pub use tooltip::Tooltip;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction.
@@ -131,11 +125,11 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output;
+ );
/// Computes the _layout_ hash of the [`Widget`].
///
@@ -174,6 +168,18 @@ where
event::Status::Ignored
}
+ /// Returns the current [`mouse::Interaction`] of the [`Widget`].
+ ///
+ /// By default, it returns [`mouse::Interaction::Idle`].
+ fn mouse_interaction(
+ &self,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ mouse::Interaction::Idle
+ }
+
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay(
&mut self,
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index c469a0e5..1d785f35 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -5,20 +5,24 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Widget,
+ Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
+ Point, Rectangle, Vector, Widget,
};
+
use std::hash::Hash;
+pub use iced_style::button::{Style, StyleSheet};
+
/// A generic widget that produces a message when pressed.
///
/// ```
-/// # use iced_native::{button, Text};
+/// # use iced_native::widget::{button, Text};
/// #
/// # type Button<'a, Message> =
-/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
@@ -34,10 +38,10 @@ use std::hash::Hash;
/// be disabled:
///
/// ```
-/// # use iced_native::{button, Text};
+/// # use iced_native::widget::{button, Text};
/// #
/// # type Button<'a, Message> =
-/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
@@ -53,7 +57,7 @@ use std::hash::Hash;
/// }
/// ```
#[allow(missing_debug_implementations)]
-pub struct Button<'a, Message, Renderer: self::Renderer> {
+pub struct Button<'a, Message, Renderer> {
state: &'a mut State,
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
@@ -62,13 +66,13 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
min_width: u32,
min_height: u32,
padding: Padding,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
@@ -84,8 +88,8 @@ where
height: Length::Shrink,
min_width: 0,
min_height: 0,
- padding: Renderer::DEFAULT_PADDING,
- style: Renderer::Style::default(),
+ padding: Padding::new(5),
+ style_sheet: Default::default(),
}
}
@@ -127,8 +131,11 @@ where
}
/// Sets the style of the [`Button`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -150,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -241,24 +248,88 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let is_mouse_over = layout.bounds().contains(cursor_position);
+ let is_disabled = self.on_press.is_none();
+
+ if is_mouse_over && !is_disabled {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- layout.bounds(),
+ ) {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_disabled = self.on_press.is_none();
+
+ let styling = if is_disabled {
+ self.style_sheet.disabled()
+ } else if is_mouse_over {
+ if self.state.is_pressed {
+ self.style_sheet.pressed()
+ } else {
+ self.style_sheet.hovered()
+ }
+ } else {
+ self.style_sheet.active()
+ };
+
+ if styling.background.is_some() || styling.border_width > 0.0 {
+ if styling.shadow_offset != Vector::default() {
+ // TODO: Implement proper shadow support
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + styling.shadow_offset.x,
+ y: bounds.y + styling.shadow_offset.y,
+ ..bounds
+ },
+ border_radius: styling.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color([0.0, 0.0, 0.0, 0.5].into()),
+ );
+ }
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: styling.border_radius,
+ border_width: styling.border_width,
+ border_color: styling.border_color,
+ },
+ styling
+ .background
+ .unwrap_or(Background::Color(Color::TRANSPARENT)),
+ );
+ }
+
+ self.content.draw(
+ renderer,
+ &renderer::Style {
+ text_color: styling.text_color,
+ },
+ content_layout,
cursor_position,
- self.on_press.is_none(),
- self.state.is_pressed,
- &self.style,
- &self.content,
- layout.children().next().unwrap(),
- )
+ &bounds,
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -277,38 +348,11 @@ where
}
}
-/// The renderer of a [`Button`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Button`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// The default padding of a [`Button`].
- const DEFAULT_PADDING: Padding;
-
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`Button`].
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- cursor_position: Point,
- is_disabled: bool,
- is_pressed: bool,
- style: &Self::Style,
- content: &Element<'_, Message, Self>,
- content_layout: Layout<'_>,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
{
fn from(
button: Button<'a, Message, Renderer>,
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 8bdb6b78..0d4a43ec 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -1,24 +1,27 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
-use crate::alignment::{self, Alignment};
+use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
-use crate::row;
+use crate::renderer;
use crate::text;
use crate::touch;
+use crate::widget::{self, Row, Text};
use crate::{
- Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
- Text, Widget,
+ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
+ Rectangle, Widget,
};
+pub use iced_style::checkbox::{Style, StyleSheet};
+
/// A box that can be checked.
///
/// # Example
///
/// ```
-/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>;
+/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// CheckboxToggled(bool),
@@ -31,7 +34,7 @@ use crate::{
///
/// ![Checkbox drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
+pub struct Checkbox<'a, Message, Renderer: text::Renderer> {
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: String,
@@ -41,12 +44,16 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
text_size: Option<u16>,
font: Renderer::Font,
text_color: Option<Color>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message, Renderer: self::Renderer + text::Renderer>
- Checkbox<Message, Renderer>
-{
+impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> {
+ /// The default size of a [`Checkbox`].
+ const DEFAULT_SIZE: u16 = 20;
+
+ /// The default spacing of a [`Checkbox`].
+ const DEFAULT_SPACING: u16 = 15;
+
/// Creates a new [`Checkbox`].
///
/// It expects:
@@ -64,12 +71,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
on_toggle: Box::new(f),
label: label.into(),
width: Length::Shrink,
- size: <Renderer as self::Renderer>::DEFAULT_SIZE,
- spacing: Renderer::DEFAULT_SPACING,
+ size: Self::DEFAULT_SIZE,
+ spacing: Self::DEFAULT_SPACING,
text_size: None,
font: Renderer::Font::default(),
text_color: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -112,16 +119,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
}
/// Sets the style of the [`Checkbox`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer>
- for Checkbox<Message, Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Checkbox<'a, Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -180,43 +190,84 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
let mut children = layout.children();
- let checkbox_layout = children.next().unwrap();
- let label_layout = children.next().unwrap();
- let checkbox_bounds = checkbox_layout.bounds();
-
- let label = text::Renderer::draw(
- renderer,
- defaults,
- label_layout.bounds(),
- &self.label,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- self.text_color,
- alignment::Horizontal::Left,
- alignment::Vertical::Center,
- );
+ {
+ let layout = children.next().unwrap();
+ let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let style = if is_mouse_over {
+ self.style_sheet.hovered(self.is_checked)
+ } else {
+ self.style_sheet.active(self.is_checked)
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
- self::Renderer::draw(
- renderer,
- checkbox_bounds,
- self.is_checked,
- is_mouse_over,
- label,
- &self.style,
- )
+ if self.is_checked {
+ renderer.fill_text(text::Text {
+ content: &Renderer::CHECKMARK_ICON.to_string(),
+ font: Renderer::ICON_FONT,
+ size: bounds.height * 0.7,
+ bounds: Rectangle {
+ x: bounds.center_x(),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ color: style.checkmark_color,
+ horizontal_alignment: alignment::Horizontal::Center,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ }
+ }
+
+ {
+ let label_layout = children.next().unwrap();
+
+ widget::text::draw(
+ renderer,
+ style,
+ label_layout,
+ &self.label,
+ self.font,
+ self.text_size,
+ self.text_color,
+ alignment::Horizontal::Left,
+ alignment::Vertical::Center,
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -227,47 +278,14 @@ where
}
}
-/// The renderer of a [`Checkbox`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Checkbox`] in your user interface.
-///
-/// [renderer]: crate::Renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default size of a [`Checkbox`].
- const DEFAULT_SIZE: u16;
-
- /// The default spacing of a [`Checkbox`].
- const DEFAULT_SPACING: u16;
-
- /// Draws a [`Checkbox`].
- ///
- /// It receives:
- /// * the bounds of the [`Checkbox`]
- /// * whether the [`Checkbox`] is selected or not
- /// * whether the mouse is over the [`Checkbox`] or not
- /// * the drawn label of the [`Checkbox`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_checked: bool,
- is_mouse_over: bool,
- label: Self::Output,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>>
+impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Renderer: 'a + text::Renderer,
Message: 'a,
{
fn from(
- checkbox: Checkbox<Message, Renderer>,
+ checkbox: Checkbox<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(checkbox)
}
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 30cf0781..0d4d6fa7 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -3,7 +3,9 @@ use std::hash::Hash;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget,
@@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -162,21 +164,37 @@ where
.fold(event::Status::Ignored, event::Status::merge)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ child.widget.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- &self.children,
- layout,
- cursor_position,
- viewport,
- )
+ ) {
+ for (child, layout) in self.children.iter().zip(layout.children()) {
+ child.draw(renderer, style, layout, cursor_position, viewport);
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -208,33 +226,10 @@ where
}
}
-/// The renderer of a [`Column`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Column`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// Draws a [`Column`].
- ///
- /// It receives:
- /// - the children of the [`Column`]
- /// - the [`Layout`] of the [`Column`] and its children
- /// - the cursor position
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[Element<'_, Message, Self>],
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 0e86ab62..596af7fd 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -4,19 +4,23 @@ use std::hash::Hash;
use crate::alignment::{self, Alignment};
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Widget,
+ Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
+ Point, Rectangle, Widget,
};
use std::u32;
+pub use iced_style::container::{Style, StyleSheet};
+
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
-pub struct Container<'a, Message, Renderer: self::Renderer> {
+pub struct Container<'a, Message, Renderer> {
padding: Padding,
width: Length,
height: Length,
@@ -24,13 +28,13 @@ pub struct Container<'a, Message, Renderer: self::Renderer> {
max_height: u32,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
content: Element<'a, Message, Renderer>,
}
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates an empty [`Container`].
pub fn new<T>(content: T) -> Self
@@ -45,7 +49,7 @@ where
max_height: u32::MAX,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
content: content.into(),
}
}
@@ -105,8 +109,11 @@ where
}
/// Sets the style of the [`Container`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -114,7 +121,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -172,23 +179,42 @@ where
)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.content.widget.mouse_interaction(
+ layout.children().next().unwrap(),
+ cursor_position,
+ viewport,
+ )
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- layout.bounds(),
+ ) {
+ let style = self.style_sheet.style();
+
+ draw_background(renderer, &style, layout.bounds());
+
+ self.content.draw(
+ renderer,
+ &renderer::Style {
+ text_color: style
+ .text_color
+ .unwrap_or(renderer_style.text_color),
+ },
+ layout.children().next().unwrap(),
cursor_position,
viewport,
- &self.style,
- &self.content,
- layout.children().next().unwrap(),
- )
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -212,33 +238,33 @@ where
}
}
-/// The renderer of a [`Container`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Container`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`Container`].
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- cursor_position: Point,
- viewport: &Rectangle,
- style: &Self::Style,
- content: &Element<'_, Message, Self>,
- content_layout: Layout<'_>,
- ) -> Self::Output;
+/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
+pub fn draw_background<Renderer>(
+ renderer: &mut Renderer,
+ style: &Style,
+ bounds: Rectangle,
+) where
+ Renderer: crate::Renderer,
+{
+ if style.background.is_some() || style.border_width > 0.0 {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style
+ .background
+ .unwrap_or(Background::Color(Color::TRANSPARENT)),
+ );
+ }
}
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 4d8e0a3f..66e95265 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -2,21 +2,19 @@
pub mod viewer;
pub use viewer::Viewer;
+use crate::image::{self, Handle};
use crate::layout;
+use crate::renderer;
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
-use std::{
- hash::{Hash, Hasher as _},
- path::PathBuf,
- sync::Arc,
-};
+use std::hash::Hash;
/// A frame that displays an image while keeping aspect ratio.
///
/// # Example
///
/// ```
-/// # use iced_native::Image;
+/// # use iced_native::widget::Image;
/// #
/// let image = Image::new("resources/ferris.png");
/// ```
@@ -54,7 +52,7 @@ impl Image {
impl<Message, Renderer> Widget<Message, Renderer> for Image
where
- Renderer: self::Renderer,
+ Renderer: image::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -92,12 +90,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(self.handle.clone(), layout)
+ ) {
+ renderer.draw(self.handle.clone(), layout.bounds());
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -110,129 +108,9 @@ where
}
}
-/// An [`Image`] handle.
-#[derive(Debug, Clone)]
-pub struct Handle {
- id: u64,
- data: Arc<Data>,
-}
-
-impl Handle {
- /// Creates an image [`Handle`] pointing to the image of the given path.
- ///
- /// Makes an educated guess about the image format by examining the data in the file.
- pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
- Self::from_data(Data::Path(path.into()))
- }
-
- /// Creates an image [`Handle`] containing the image pixels directly. This
- /// function expects the input data to be provided as a `Vec<u8>` of BGRA
- /// pixels.
- ///
- /// This is useful if you have already decoded your image.
- pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
- Self::from_data(Data::Pixels {
- width,
- height,
- pixels,
- })
- }
-
- /// Creates an image [`Handle`] containing the image data directly.
- ///
- /// Makes an educated guess about the image format by examining the given data.
- ///
- /// This is useful if you already have your image loaded in-memory, maybe
- /// because you downloaded or generated it procedurally.
- pub fn from_memory(bytes: Vec<u8>) -> Handle {
- Self::from_data(Data::Bytes(bytes))
- }
-
- fn from_data(data: Data) -> Handle {
- let mut hasher = Hasher::default();
- data.hash(&mut hasher);
-
- Handle {
- id: hasher.finish(),
- data: Arc::new(data),
- }
- }
-
- /// Returns the unique identifier of the [`Handle`].
- pub fn id(&self) -> u64 {
- self.id
- }
-
- /// Returns a reference to the image [`Data`].
- pub fn data(&self) -> &Data {
- &self.data
- }
-}
-
-impl<T> From<T> for Handle
-where
- T: Into<PathBuf>,
-{
- fn from(path: T) -> Handle {
- Handle::from_path(path.into())
- }
-}
-
-impl Hash for Handle {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
-
-/// The data of an [`Image`].
-#[derive(Clone, Hash)]
-pub enum Data {
- /// File data
- Path(PathBuf),
-
- /// In-memory data
- Bytes(Vec<u8>),
-
- /// Decoded image pixels in BGRA format.
- Pixels {
- /// The width of the image.
- width: u32,
- /// The height of the image.
- height: u32,
- /// The pixels.
- pixels: Vec<u8>,
- },
-}
-
-impl std::fmt::Debug for Data {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Data::Path(path) => write!(f, "Path({:?})", path),
- Data::Bytes(_) => write!(f, "Bytes(...)"),
- Data::Pixels { width, height, .. } => {
- write!(f, "Pixels({} * {})", width, height)
- }
- }
- }
-}
-
-/// The renderer of an [`Image`].
-///
-/// Your [renderer] will need to implement this trait before being able to use
-/// an [`Image`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// Returns the dimensions of an [`Image`] located on the given path.
- fn dimensions(&self, handle: &Handle) -> (u32, u32);
-
- /// Draws an [`Image`].
- fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: image::Renderer,
{
fn from(image: Image) -> Element<'a, Message, Renderer> {
Element::new(image)
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index 405daf00..95e5c6e4 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -3,6 +3,7 @@ use crate::event::{self, Event};
use crate::image;
use crate::layout;
use crate::mouse;
+use crate::renderer;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Widget,
@@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {
/// will be respected.
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
where
- Renderer: self::Renderer + image::Renderer,
+ Renderer: image::Renderer,
{
let (width, height) = renderer.dimensions(&self.handle);
@@ -115,7 +116,7 @@ impl<'a> Viewer<'a> {
impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
where
- Renderer: self::Renderer + image::Renderer,
+ Renderer: image::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -280,14 +281,32 @@ where
}
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if self.state.is_cursor_grabbed() {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::Idle
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ _cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
let bounds = layout.bounds();
let image_size = self.image_size(renderer, bounds.size());
@@ -301,17 +320,19 @@ where
image_top_left - self.state.offset(bounds, image_size)
};
- let is_mouse_over = bounds.contains(cursor_position);
-
- self::Renderer::draw(
- renderer,
- &self.state,
- bounds,
- image_size,
- translation,
- self.handle.clone(),
- is_mouse_over,
- )
+ renderer.with_layer(bounds, |renderer| {
+ renderer.with_translation(translation, |renderer| {
+ image::Renderer::draw(
+ renderer,
+ self.handle.clone(),
+ Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ ..Rectangle::with_size(image_size)
+ },
+ )
+ });
+ });
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -373,38 +394,9 @@ impl State {
}
}
-/// The renderer of an [`Viewer`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Viewer`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// Draws the [`Viewer`].
- ///
- /// It receives:
- /// - the [`State`] of the [`Viewer`]
- /// - the bounds of the [`Viewer`] widget
- /// - the [`Size`] of the scaled [`Viewer`] image
- /// - the translation of the clipped image
- /// - the [`Handle`] to the underlying image
- /// - whether the mouse is over the [`Viewer`] or not
- ///
- /// [`Handle`]: image::Handle
- fn draw(
- &mut self,
- state: &State,
- bounds: Rectangle,
- image_size: Size,
- translation: Vector,
- handle: image::Handle,
- is_mouse_over: bool,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + image::Renderer,
+ Renderer: 'a + image::Renderer,
Message: 'a,
{
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 26a72409..20616ed4 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -27,18 +27,19 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
-use crate::container;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
-use crate::row;
+use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
- Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
+ Vector, Widget,
};
+pub use iced_style::pane_grid::{Line, StyleSheet};
+
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@@ -61,10 +62,10 @@ use crate::{
/// ## Example
///
/// ```
-/// # use iced_native::{pane_grid, Text};
+/// # use iced_native::widget::{pane_grid, Text};
/// #
/// # type PaneGrid<'a, Message> =
-/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>;
+/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
/// #
/// enum PaneState {
/// SomePane,
@@ -89,7 +90,7 @@ use crate::{
/// .on_resize(10, Message::PaneResized);
/// ```
#[allow(missing_debug_implementations)]
-pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
+pub struct PaneGrid<'a, Message, Renderer> {
state: &'a mut state::Internal,
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length,
@@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
- style: <Renderer as self::Renderer>::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
@@ -130,7 +131,7 @@ where
on_click: None,
on_drag: None,
on_resize: None,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -190,18 +191,15 @@ where
}
/// Sets the style of the [`PaneGrid`].
- pub fn style(
- mut self,
- style: impl Into<<Renderer as self::Renderer>::Style>,
- ) -> Self {
- self.style = style.into();
+ pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
+ self.style_sheet = style.into();
self
}
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn click_pane(
&mut self,
@@ -318,7 +316,7 @@ pub struct ResizeEvent {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer + container::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -473,14 +471,43 @@ where
.fold(event_status, event::Status::merge)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if self.state.picked_pane().is_some() {
+ return mouse::Interaction::Grab;
+ }
+
+ if let Some((_, axis)) = self.state.picked_split() {
+ return match axis {
+ Axis::Horizontal => mouse::Interaction::ResizingHorizontally,
+ Axis::Vertical => mouse::Interaction::ResizingVertically,
+ };
+ }
+
+ self.elements
+ .iter()
+ .zip(layout.children())
+ .map(|((_pane, content), layout)| {
+ content.mouse_interaction(layout, cursor_position, viewport)
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
+ let picked_pane = self.state.picked_pane();
+
let picked_split = self
.state
.picked_split()
@@ -529,17 +556,89 @@ where
None => None,
});
- self::Renderer::draw(
- renderer,
- defaults,
- &self.elements,
- self.state.picked_pane(),
- picked_split,
- layout,
- &self.style,
- cursor_position,
- viewport,
- )
+ let pane_cursor_position = if picked_pane.is_some() {
+ // TODO: Remove once cursor availability is encoded in the type
+ // system
+ Point::new(-1.0, -1.0)
+ } else {
+ cursor_position
+ };
+
+ for ((id, pane), layout) in self.elements.iter().zip(layout.children())
+ {
+ match picked_pane {
+ Some((dragging, origin)) if *id == dragging => {
+ let bounds = layout.bounds();
+
+ renderer.with_translation(
+ cursor_position
+ - Point::new(
+ bounds.x + origin.x,
+ bounds.y + origin.y,
+ ),
+ |renderer| {
+ renderer.with_layer(bounds, |renderer| {
+ pane.draw(
+ renderer,
+ style,
+ layout,
+ pane_cursor_position,
+ viewport,
+ );
+ });
+ },
+ );
+ }
+ _ => {
+ pane.draw(
+ renderer,
+ style,
+ layout,
+ pane_cursor_position,
+ viewport,
+ );
+ }
+ }
+ }
+
+ if let Some((axis, split_region, is_picked)) = picked_split {
+ let highlight = if is_picked {
+ self.style_sheet.picked_split()
+ } else {
+ self.style_sheet.hovered_split()
+ };
+
+ if let Some(highlight) = highlight {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: match axis {
+ Axis::Horizontal => Rectangle {
+ x: split_region.x,
+ y: (split_region.y
+ + (split_region.height - highlight.width)
+ / 2.0)
+ .round(),
+ width: split_region.width,
+ height: highlight.width,
+ },
+ Axis::Vertical => Rectangle {
+ x: (split_region.x
+ + (split_region.width - highlight.width)
+ / 2.0)
+ .round(),
+ y: split_region.y,
+ width: highlight.width,
+ height: split_region.height,
+ },
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ highlight.color,
+ );
+ }
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -569,78 +668,10 @@ where
}
}
-/// The renderer of a [`PaneGrid`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`PaneGrid`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + container::Renderer + Sized {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`PaneGrid`].
- ///
- /// It receives:
- /// - the elements of the [`PaneGrid`]
- /// - the [`Pane`] that is currently being dragged
- /// - the [`Axis`] that is currently being resized
- /// - the [`Layout`] of the [`PaneGrid`] and its elements
- /// - the cursor position
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- content: &[(Pane, Content<'_, Message, Self>)],
- dragging: Option<(Pane, Point)>,
- resizing: Option<(Axis, Rectangle, bool)>,
- layout: Layout<'_>,
- style: &<Self as self::Renderer>::Style,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-
- /// Draws a [`Pane`].
- ///
- /// It receives:
- /// - the [`TitleBar`] of the [`Pane`], if any
- /// - the [`Content`] of the [`Pane`]
- /// - the [`Layout`] of the [`Pane`] and its elements
- /// - the cursor position
- fn draw_pane<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- style: &<Self as container::Renderer>::Style,
- title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
- body: (&Element<'_, Message, Self>, Layout<'_>),
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-
- /// Draws a [`TitleBar`].
- ///
- /// It receives:
- /// - the bounds, style of the [`TitleBar`]
- /// - the style of the [`TitleBar`]
- /// - the content of the [`TitleBar`] with its layout
- /// - the controls of the [`TitleBar`] with their [`Layout`], if any
- /// - the cursor position
- fn draw_title_bar<Message>(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- style: &<Self as container::Renderer>::Style,
- content: (&Element<'_, Message, Self>, Layout<'_>),
- controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + row::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs
index 4c43826e..4c52bad4 100644
--- a/native/src/widget/pane_grid/configuration.rs
+++ b/native/src/widget/pane_grid/configuration.rs
@@ -1,4 +1,4 @@
-use crate::pane_grid::Axis;
+use crate::widget::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`].
///
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index bac9fdd4..c44506dd 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -1,30 +1,32 @@
-use crate::container;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
-use crate::pane_grid::{self, TitleBar};
+use crate::renderer;
+use crate::widget::container;
+use crate::widget::pane_grid::TitleBar;
use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
/// The content of a [`Pane`].
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
-pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
+pub struct Content<'a, Message, Renderer> {
title_bar: Option<TitleBar<'a, Message, Renderer>>,
body: Element<'a, Message, Renderer>,
- style: <Renderer as container::Renderer>::Style,
+ style_sheet: Box<dyn container::StyleSheet + 'a>,
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a new [`Content`] with the provided body.
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
Self {
title_bar: None,
body: body.into(),
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -40,16 +42,16 @@ where
/// Sets the style of the [`Content`].
pub fn style(
mut self,
- style: impl Into<<Renderer as container::Renderer>::Style>,
+ style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style_sheet.into();
self
}
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
@@ -57,35 +59,45 @@ where
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
+ let bounds = layout.bounds();
+
+ {
+ let style = self.style_sheet.style();
+
+ container::draw_background(renderer, &style, bounds);
+ }
+
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
let title_bar_layout = children.next().unwrap();
let body_layout = children.next().unwrap();
- renderer.draw_pane(
- defaults,
- layout.bounds(),
- &self.style,
- Some((title_bar, title_bar_layout)),
- (&self.body, body_layout),
+ let show_controls = bounds.contains(cursor_position);
+
+ title_bar.draw(
+ renderer,
+ style,
+ title_bar_layout,
cursor_position,
viewport,
- )
- } else {
- renderer.draw_pane(
- defaults,
- layout.bounds(),
- &self.style,
- None,
- (&self.body, layout),
+ show_controls,
+ );
+
+ self.body.draw(
+ renderer,
+ style,
+ body_layout,
cursor_position,
viewport,
- )
+ );
+ } else {
+ self.body
+ .draw(renderer, style, layout, cursor_position, viewport);
}
}
@@ -186,6 +198,40 @@ where
event_status.merge(body_status)
}
+ pub(crate) fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let (body_layout, title_bar_interaction) =
+ if let Some(title_bar) = &self.title_bar {
+ let mut children = layout.children();
+ let title_bar_layout = children.next().unwrap();
+
+ let is_over_pick_area = title_bar
+ .is_over_pick_area(title_bar_layout, cursor_position);
+
+ if is_over_pick_area {
+ return mouse::Interaction::Grab;
+ }
+
+ let mouse_interaction = title_bar.mouse_interaction(
+ title_bar_layout,
+ cursor_position,
+ viewport,
+ );
+
+ (children.next().unwrap(), mouse_interaction)
+ } else {
+ (layout, mouse::Interaction::default())
+ };
+
+ self.body
+ .mouse_interaction(body_layout, cursor_position, viewport)
+ .max(title_bar_interaction)
+ }
+
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
if let Some(title_bar) = &self.title_bar {
title_bar.hash_layout(state);
@@ -215,7 +261,7 @@ where
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
where
T: Into<Element<'a, Message, Renderer>>,
- Renderer: pane_grid::Renderer + container::Renderer,
+ Renderer: crate::Renderer,
{
fn from(element: T) -> Self {
Self::new(element)
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index 84714e00..af6573a0 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -1,7 +1,5 @@
-use crate::{
- pane_grid::{Axis, Pane, Split},
- Rectangle, Size,
-};
+use crate::widget::pane_grid::{Axis, Pane, Split};
+use crate::{Rectangle, Size};
use std::collections::BTreeMap;
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index fb96f89f..bcc724a8 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -1,7 +1,7 @@
-use crate::{
- pane_grid::{Axis, Configuration, Direction, Node, Pane, Split},
- Hasher, Point, Rectangle, Size,
+use crate::widget::pane_grid::{
+ Axis, Configuration, Direction, Node, Pane, Split,
};
+use crate::{Hasher, Point, Rectangle, Size};
use std::collections::{BTreeMap, HashMap};
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index 070010f8..070cf404 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,8 +1,9 @@
-use crate::container;
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
-use crate::pane_grid;
+use crate::renderer;
+use crate::widget::container;
use crate::{
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
};
@@ -11,17 +12,17 @@ use crate::{
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
-pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
+pub struct TitleBar<'a, Message, Renderer> {
content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
padding: Padding,
always_show_controls: bool,
- style: <Renderer as container::Renderer>::Style,
+ style_sheet: Box<dyn container::StyleSheet + 'a>,
}
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Creates a new [`TitleBar`] with the given content.
pub fn new<E>(content: E) -> Self
@@ -33,7 +34,7 @@ where
controls: None,
padding: Padding::ZERO,
always_show_controls: false,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -55,9 +56,9 @@ where
/// Sets the style of the [`TitleBar`].
pub fn style(
mut self,
- style: impl Into<<Renderer as container::Renderer>::Style>,
+ style: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style.into();
self
}
@@ -77,7 +78,7 @@ where
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
- Renderer: pane_grid::Renderer,
+ Renderer: crate::Renderer,
{
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
@@ -85,39 +86,47 @@ where
pub fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
show_controls: bool,
- ) -> Renderer::Output {
+ ) {
+ let bounds = layout.bounds();
+ let style = self.style_sheet.style();
+ let inherited_style = renderer::Style {
+ text_color: style.text_color.unwrap_or(inherited_style.text_color),
+ };
+
+ container::draw_background(renderer, &style, bounds);
+
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
- let controls = if let Some(controls) = &self.controls {
+ self.content.draw(
+ renderer,
+ &inherited_style,
+ title_layout,
+ cursor_position,
+ viewport,
+ );
+
+ if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
if show_controls || self.always_show_controls {
- Some((controls, controls_layout))
- } else {
- None
+ controls.draw(
+ renderer,
+ &inherited_style,
+ controls_layout,
+ cursor_position,
+ viewport,
+ );
}
- } else {
- None
- };
-
- renderer.draw_title_bar(
- defaults,
- layout.bounds(),
- &self.style,
- (&self.content, title_layout),
- controls,
- cursor_position,
- viewport,
- )
+ }
}
/// Returns whether the mouse cursor is over the pick area of the
@@ -244,6 +253,35 @@ where
control_status.merge(title_status)
}
+ pub(crate) fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
+
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let title_interaction = self.content.mouse_interaction(
+ title_layout,
+ cursor_position,
+ viewport,
+ );
+
+ if let Some(controls) = &self.controls {
+ let controls_layout = children.next().unwrap();
+
+ controls
+ .mouse_interaction(controls_layout, cursor_position, viewport)
+ .max(title_interaction)
+ } else {
+ title_interaction
+ }
+ }
+
pub(crate) fn overlay(
&mut self,
layout: Layout<'_>,
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index d7792000..9d1a86ec 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -1,12 +1,13 @@
//! Display a dropdown list of selectable values.
+use crate::alignment;
use crate::event::{self, Event};
use crate::keyboard;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::overlay::menu::{self, Menu};
-use crate::scrollable;
-use crate::text;
+use crate::renderer;
+use crate::text::{self, Text};
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
@@ -14,9 +15,11 @@ use crate::{
};
use std::borrow::Cow;
+pub use iced_style::pick_list::{Style, StyleSheet};
+
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
-pub struct PickList<'a, T, Message, Renderer: self::Renderer>
+pub struct PickList<'a, T, Message, Renderer: text::Renderer>
where
[T]: ToOwned<Owned = Vec<T>>,
{
@@ -33,7 +36,7 @@ where
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
- style: <Renderer as self::Renderer>::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
/// The local state of a [`PickList`].
@@ -58,12 +61,15 @@ impl<T> Default for State<T> {
}
}
-impl<'a, T: 'a, Message, Renderer: self::Renderer>
+impl<'a, T: 'a, Message, Renderer: text::Renderer>
PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
{
+ /// The default padding of a [`PickList`].
+ pub const DEFAULT_PADDING: Padding = Padding::new(5);
+
/// Creates a new [`PickList`] with the given [`State`], a list of options,
/// the current selected value, and the message to produce when an option is
/// selected.
@@ -93,9 +99,9 @@ where
selected,
width: Length::Shrink,
text_size: None,
- padding: Renderer::DEFAULT_PADDING,
+ padding: Self::DEFAULT_PADDING,
font: Default::default(),
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -132,9 +138,9 @@ where
/// Sets the style of the [`PickList`].
pub fn style(
mut self,
- style: impl Into<<Renderer as self::Renderer>::Style>,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -145,7 +151,7 @@ where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'static,
- Renderer: self::Renderer + scrollable::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
{
fn width(&self) -> Length {
self.width
@@ -320,25 +326,90 @@ where
}
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- self::Renderer::draw(
- renderer,
- layout.bounds(),
- cursor_position,
- self.selected.as_ref().map(ToString::to_string),
- self.placeholder.as_ref().map(String::as_str),
- self.padding,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- &self.style,
- )
+ ) {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_selected = self.selected.is_some();
+
+ let style = if is_mouse_over {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_color: style.border_color,
+ border_width: style.border_width,
+ border_radius: style.border_radius,
+ },
+ style.background,
+ );
+
+ renderer.fill_text(Text {
+ content: &Renderer::ARROW_DOWN_ICON.to_string(),
+ font: Renderer::ICON_FONT,
+ size: bounds.height * style.icon_size,
+ bounds: Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(self.padding.horizontal()),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ color: style.text_color,
+ horizontal_alignment: alignment::Horizontal::Right,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+
+ if let Some(label) = self
+ .selected
+ .as_ref()
+ .map(ToString::to_string)
+ .as_ref()
+ .or_else(|| self.placeholder.as_ref())
+ {
+ renderer.fill_text(Text {
+ content: label,
+ size: f32::from(
+ self.text_size.unwrap_or(renderer.default_size()),
+ ),
+ font: self.font,
+ color: is_selected
+ .then(|| style.text_color)
+ .unwrap_or(style.placeholder_color),
+ bounds: Rectangle {
+ x: bounds.x + f32::from(self.padding.left),
+ y: bounds.center_y(),
+ ..bounds
+ },
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ })
+ }
}
fn overlay(
@@ -357,7 +428,7 @@ where
.width(bounds.width.round() as u16)
.padding(self.padding)
.font(self.font)
- .style(Renderer::menu_style(&self.style));
+ .style(self.style_sheet.menu());
if let Some(text_size) = self.text_size {
menu = menu.text_size(text_size);
@@ -370,44 +441,12 @@ where
}
}
-/// The renderer of a [`PickList`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`PickList`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: text::Renderer + menu::Renderer {
- /// The default padding of a [`PickList`].
- const DEFAULT_PADDING: Padding;
-
- /// The [`PickList`] style supported by this renderer.
- type Style: Default;
-
- /// Returns the style of the [`Menu`] of the [`PickList`].
- fn menu_style(
- style: &<Self as Renderer>::Style,
- ) -> <Self as menu::Renderer>::Style;
-
- /// Draws a [`PickList`].
- fn draw(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- selected: Option<String>,
- placeholder: Option<&str>,
- padding: Padding,
- text_size: u16,
- font: Self::Font,
- style: &<Self as Renderer>::Style,
- ) -> Self::Output;
-}
-
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
for PickList<'a, T, Message, Renderer>
where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
- Renderer: self::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
Message: 'static,
{
fn into(self) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index d294f198..69eb8c09 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -1,17 +1,19 @@
//! Provide progress feedback to your users.
+use crate::layout;
+use crate::renderer;
use crate::{
- layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
+pub use iced_style::progress_bar::{Style, StyleSheet};
+
/// A bar that displays progress.
///
/// # Example
/// ```
-/// # use iced_native::renderer::Null;
-/// #
-/// # pub type ProgressBar = iced_native::ProgressBar<Null>;
+/// # use iced_native::widget::ProgressBar;
/// let value = 50.0;
///
/// ProgressBar::new(0.0..=100.0, value);
@@ -19,15 +21,18 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
-pub struct ProgressBar<Renderer: self::Renderer> {
+pub struct ProgressBar<'a> {
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Renderer: self::Renderer> ProgressBar<Renderer> {
+impl<'a> ProgressBar<'a> {
+ /// The default height of a [`ProgressBar`].
+ pub const DEFAULT_HEIGHT: u16 = 30;
+
/// Creates a new [`ProgressBar`].
///
/// It expects:
@@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
range,
width: Length::Fill,
height: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -56,23 +61,25 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
}
/// Sets the style of the [`ProgressBar`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
- self.height
- .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT))
+ self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))
}
fn layout(
@@ -80,10 +87,9 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits.width(self.width).height(
- self.height
- .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)),
- );
+ let limits = limits
+ .width(self.width)
+ .height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));
let size = limits.resolve(Size::ZERO);
@@ -93,17 +99,47 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- layout.bounds(),
- self.range.clone(),
- self.value,
- &self.style,
- )
+ ) {
+ let bounds = layout.bounds();
+ let (range_start, range_end) = self.range.clone().into_inner();
+
+ let active_progress_width = if range_start >= range_end {
+ 0.0
+ } else {
+ bounds.width * (self.value - range_start)
+ / (range_end - range_start)
+ };
+
+ let style = self.style_sheet.style();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle { ..bounds },
+ border_radius: style.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.background,
+ );
+
+ if active_progress_width > 0.0 {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ width: active_progress_width,
+ ..bounds
+ },
+ border_radius: style.border_radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.bar,
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -115,45 +151,13 @@ where
}
}
-/// The renderer of a [`ProgressBar`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`ProgressBar`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default height of a [`ProgressBar`].
- const DEFAULT_HEIGHT: u16;
-
- /// Draws a [`ProgressBar`].
- ///
- /// It receives:
- /// * the bounds of the [`ProgressBar`]
- /// * the range of values of the [`ProgressBar`]
- /// * the current value of the [`ProgressBar`]
- /// * maybe a specific background of the [`ProgressBar`]
- /// * maybe a specific active color of the [`ProgressBar`]
- fn draw(
- &self,
- bounds: Rectangle,
- range: RangeInclusive<f32>,
- value: f32,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
+impl<'a, Message, Renderer> From<ProgressBar<'a>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
- fn from(
- progress_bar: ProgressBar<Renderer>,
- ) -> Element<'a, Message, Renderer> {
+ fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {
Element::new(progress_bar)
}
}
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 513b2fce..86ad4c4e 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,24 +1,27 @@
//! Create choices using radio buttons.
use std::hash::Hash;
-use crate::alignment::{self, Alignment};
+use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
-use crate::row;
+use crate::renderer;
use crate::text;
use crate::touch;
+use crate::widget::{self, Row, Text};
use crate::{
- Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
- Text, Widget,
+ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
+ Rectangle, Widget,
};
+pub use iced_style::radio::{Style, StyleSheet};
+
/// A circular button representing a choice.
///
/// # Example
/// ```
-/// # type Radio<Message> =
-/// # iced_native::Radio<Message, iced_native::renderer::Null>;
+/// # type Radio<'a, Message> =
+/// # iced_native::widget::Radio<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
@@ -40,7 +43,7 @@ use crate::{
///
/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
+pub struct Radio<'a, Message, Renderer: text::Renderer> {
is_selected: bool,
on_click: Message,
label: String,
@@ -50,14 +53,19 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
text_size: Option<u16>,
text_color: Option<Color>,
font: Renderer::Font,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message, Renderer: self::Renderer + text::Renderer>
- Radio<Message, Renderer>
+impl<'a, Message, Renderer: text::Renderer> Radio<'a, Message, Renderer>
where
Message: Clone,
{
+ /// The default size of a [`Radio`] button.
+ pub const DEFAULT_SIZE: u16 = 28;
+
+ /// The default spacing of a [`Radio`] button.
+ pub const DEFAULT_SPACING: u16 = 15;
+
/// Creates a new [`Radio`] button.
///
/// It expects:
@@ -81,12 +89,12 @@ where
on_click: f(value),
label: label.into(),
width: Length::Shrink,
- size: <Renderer as self::Renderer>::DEFAULT_SIZE,
- spacing: Renderer::DEFAULT_SPACING, //15
+ size: Self::DEFAULT_SIZE,
+ spacing: Self::DEFAULT_SPACING, //15
text_size: None,
text_color: None,
font: Default::default(),
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -127,16 +135,20 @@ where
}
/// Sets the style of the [`Radio`] button.
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Radio<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer + text::Renderer + row::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -192,43 +204,88 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
let mut children = layout.children();
- let radio_layout = children.next().unwrap();
- let label_layout = children.next().unwrap();
- let radio_bounds = radio_layout.bounds();
-
- let label = text::Renderer::draw(
- renderer,
- defaults,
- label_layout.bounds(),
- &self.label,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- self.text_color,
- alignment::Horizontal::Left,
- alignment::Vertical::Center,
- );
+ {
+ let layout = children.next().unwrap();
+ let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let size = bounds.width;
+ let dot_size = size / 2.0;
+
+ let style = if is_mouse_over {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: size / 2.0,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
- self::Renderer::draw(
- renderer,
- radio_bounds,
- self.is_selected,
- is_mouse_over,
- label,
- &self.style,
- )
+ if self.is_selected {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + dot_size / 2.0,
+ y: bounds.y + dot_size / 2.0,
+ width: bounds.width - dot_size,
+ height: bounds.height - dot_size,
+ },
+ border_radius: dot_size / 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.dot_color,
+ );
+ }
+ }
+
+ {
+ let label_layout = children.next().unwrap();
+
+ widget::text::draw(
+ renderer,
+ style,
+ label_layout,
+ &self.label,
+ self.font,
+ self.text_size,
+ self.text_color,
+ alignment::Horizontal::Left,
+ alignment::Vertical::Center,
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -239,46 +296,15 @@ where
}
}
-/// The renderer of a [`Radio`] button.
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Radio`] button in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default size of a [`Radio`] button.
- const DEFAULT_SIZE: u16;
-
- /// The default spacing of a [`Radio`] button.
- const DEFAULT_SPACING: u16;
-
- /// Draws a [`Radio`] button.
- ///
- /// It receives:
- /// * the bounds of the [`Radio`]
- /// * whether the [`Radio`] is selected or not
- /// * whether the mouse is over the [`Radio`] or not
- /// * the drawn label of the [`Radio`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_selected: bool,
- is_mouse_over: bool,
- label: Self::Output,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
+impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
+ Renderer: 'a + text::Renderer,
{
- fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
+ fn from(
+ radio: Radio<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
Element::new(radio)
}
}
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 1923f213..6fe3284b 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,7 +1,9 @@
//! Distribute content horizontally.
use crate::event::{self, Event};
use crate::layout;
+use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget,
@@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -161,21 +163,37 @@ where
.fold(event::Status::Ignored, event::Status::merge)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ child.widget.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ )
+ })
+ .max()
+ .unwrap_or_default()
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- &self.children,
- layout,
- cursor_position,
- viewport,
- )
+ ) {
+ for (child, layout) in self.children.iter().zip(layout.children()) {
+ child.draw(renderer, style, layout, cursor_position, viewport);
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -207,33 +225,10 @@ where
}
}
-/// The renderer of a [`Row`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Row`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer + Sized {
- /// Draws a [`Row`].
- ///
- /// It receives:
- /// - the children of the [`Row`]
- /// - the [`Layout`] of the [`Row`] and its children
- /// - the cursor position
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- children: &[Element<'_, Message, Self>],
- layout: Layout<'_>,
- cursor_position: Point,
- viewport: &Rectangle,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
index 18c88658..7c8c5dbc 100644
--- a/native/src/widget/rule.rs
+++ b/native/src/widget/rule.rs
@@ -1,28 +1,31 @@
//! Display a horizontal or vertical rule for dividing content.
+use crate::layout;
+use crate::renderer;
+use crate::{
+ Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+};
use std::hash::Hash;
-use crate::{
- layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
-};
+pub use iced_style::rule::{FillMode, Style, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
-#[derive(Debug, Copy, Clone)]
-pub struct Rule<Renderer: self::Renderer> {
+#[allow(missing_debug_implementations)]
+pub struct Rule<'a> {
width: Length,
height: Length,
- style: Renderer::Style,
is_horizontal: bool,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Renderer: self::Renderer> Rule<Renderer> {
+impl<'a> Rule<'a> {
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
pub fn horizontal(spacing: u16) -> Self {
Rule {
width: Length::Fill,
height: Length::from(Length::Units(spacing)),
- style: Renderer::Style::default(),
is_horizontal: true,
+ style_sheet: Default::default(),
}
}
@@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
Rule {
width: Length::from(Length::Units(spacing)),
height: Length::Fill,
- style: Renderer::Style::default(),
is_horizontal: false,
+ style_sheet: Default::default(),
}
}
/// Sets the style of the [`Rule`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -68,12 +74,53 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
+ ) {
+ let bounds = layout.bounds();
+ let style = self.style_sheet.style();
+
+ let bounds = if self.is_horizontal {
+ let line_y = (bounds.y + (bounds.height / 2.0)
+ - (style.width as f32 / 2.0))
+ .round();
+
+ 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: style.width as f32,
+ }
+ } else {
+ let line_x = (bounds.x + (bounds.width / 2.0)
+ - (style.width as f32 / 2.0))
+ .round();
+
+ let (offset, line_height) = style.fill_mode.fill(bounds.height);
+ let line_y = bounds.y + offset;
+
+ Rectangle {
+ x: line_x,
+ y: line_y,
+ width: style.width as f32,
+ height: line_height,
+ }
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: style.radius,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.color,
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -85,32 +132,12 @@ where
}
}
-/// The renderer of a [`Rule`].
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Draws a [`Rule`].
- ///
- /// It receives:
- /// * the bounds of the [`Rule`]
- /// * the style of the [`Rule`]
- /// * whether the [`Rule`] is horizontal (true) or vertical (false)
- fn draw(
- &mut self,
- bounds: Rectangle,
- style: &Self::Style,
- is_horizontal: bool,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Rule<Renderer>>
- for Element<'a, Message, Renderer>
+impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
- fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
+ fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {
Element::new(rule)
}
}
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index a8e467d3..2bf2ea5e 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,21 +1,24 @@
//! Navigate an endless amount of content with a scrollbar.
-use crate::column;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
+use crate::renderer;
use crate::touch;
+use crate::widget::Column;
use crate::{
- Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding,
- Point, Rectangle, Size, Vector, Widget,
+ Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
+ Padding, Point, Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
+pub use iced_style::scrollable::StyleSheet;
+
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
#[allow(missing_debug_implementations)]
-pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
+pub struct Scrollable<'a, Message, Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
@@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scroller_width: u16,
content: Column<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
+impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
pub fn new(state: &'a mut State) -> Self {
Scrollable {
@@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scroller_width: 10,
content: Column::new(),
on_scroll: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -120,8 +123,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the style of the [`Scrollable`] .
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -151,12 +157,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
));
}
}
+
+ fn scrollbar(
+ &self,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ ) -> Option<Scrollbar> {
+ let offset = self.state.offset(bounds, content_bounds);
+
+ if content_bounds.height > bounds.height {
+ let outer_width = self.scrollbar_width.max(self.scroller_width)
+ + 2 * self.scrollbar_margin;
+
+ let outer_bounds = Rectangle {
+ x: bounds.x + bounds.width - outer_width as f32,
+ y: bounds.y,
+ width: outer_width as f32,
+ height: bounds.height,
+ };
+
+ let scrollbar_bounds = Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + self.scrollbar_width / 2),
+ y: bounds.y,
+ width: self.scrollbar_width as f32,
+ height: bounds.height,
+ };
+
+ let ratio = bounds.height / content_bounds.height;
+ let scroller_height = bounds.height * ratio;
+ let y_offset = offset as f32 * ratio;
+
+ let scroller_bounds = Rectangle {
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + self.scroller_width / 2),
+ y: scrollbar_bounds.y + y_offset,
+ width: self.scroller_width as f32,
+ height: scroller_height,
+ };
+
+ Some(Scrollbar {
+ outer_bounds,
+ bounds: scrollbar_bounds,
+ margin: self.scrollbar_margin,
+ scroller: Scroller {
+ bounds: scroller_bounds,
+ },
+ })
+ } else {
+ None
+ }
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content)
@@ -202,15 +259,7 @@ where
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(
- bounds,
- content_bounds,
- offset,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- );
+ let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over_scrollbar = scrollbar
.as_ref()
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
@@ -374,26 +423,16 @@ where
event::Status::Ignored
}
- fn draw(
+ fn mouse_interaction(
&self,
- renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) -> mouse::Interaction {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(
- bounds,
- content_bounds,
- offset,
- self.scrollbar_width,
- self.scrollbar_margin,
- self.scroller_width,
- );
+ let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@@ -401,16 +440,18 @@ where
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
.unwrap_or(false);
- let content = {
+ if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() {
+ mouse::Interaction::Idle
+ } else {
+ let offset = self.state.offset(bounds, content_bounds);
+
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else {
Point::new(cursor_position.x, -1.0)
};
- self.content.draw(
- renderer,
- defaults,
+ self.content.mouse_interaction(
content_layout,
cursor_position,
&Rectangle {
@@ -418,20 +459,114 @@ where
..bounds
},
)
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let offset = self.state.offset(bounds, content_bounds);
+ let scrollbar = self.scrollbar(bounds, content_bounds);
+
+ let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(cursor_position.x, cursor_position.y + offset as f32)
+ } else {
+ Point::new(cursor_position.x, -1.0)
};
- self::Renderer::draw(
- renderer,
- &self.state,
- bounds,
- content_layout.bounds(),
- is_mouse_over,
- is_mouse_over_scrollbar,
- scrollbar,
- offset,
- &self.style,
- content,
- )
+ if let Some(scrollbar) = scrollbar {
+ renderer.with_layer(bounds, |renderer| {
+ renderer.with_translation(
+ Vector::new(0.0, -(offset as f32)),
+ |renderer| {
+ self.content.draw(
+ renderer,
+ style,
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ );
+ },
+ );
+ });
+
+ let style = if self.state.is_scroller_grabbed() {
+ self.style_sheet.dragging()
+ } else if is_mouse_over_scrollbar {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ let is_scrollbar_visible =
+ style.background.is_some() || style.border_width > 0.0;
+
+ renderer.with_layer(
+ Rectangle {
+ width: bounds.width + 2.0,
+ height: bounds.height + 2.0,
+ ..bounds
+ },
+ |renderer| {
+ if is_scrollbar_visible {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: scrollbar.bounds,
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background.unwrap_or(Background::Color(
+ Color::TRANSPARENT,
+ )),
+ );
+ }
+
+ if is_mouse_over
+ || self.state.is_scroller_grabbed()
+ || is_scrollbar_visible
+ {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: scrollbar.scroller.bounds,
+ border_radius: style.scroller.border_radius,
+ border_width: style.scroller.border_width,
+ border_color: style.scroller.border_color,
+ },
+ style.scroller.color,
+ );
+ }
+ },
+ );
+ } else {
+ self.content.draw(
+ renderer,
+ style,
+ content_layout,
+ cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
+ );
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -577,19 +712,19 @@ impl State {
/// The scrollbar of a [`Scrollable`].
#[derive(Debug)]
-pub struct Scrollbar {
+struct Scrollbar {
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
/// [`Scroller`].
- pub outer_bounds: Rectangle,
+ outer_bounds: Rectangle,
/// The bounds of the [`Scrollbar`].
- pub bounds: Rectangle,
+ bounds: Rectangle,
/// The margin within the [`Scrollbar`].
- pub margin: u16,
+ margin: u16,
/// The bounds of the [`Scroller`].
- pub scroller: Scroller,
+ scroller: Scroller,
}
impl Scrollbar {
@@ -624,62 +759,15 @@ impl Scrollbar {
/// The handle of a [`Scrollbar`].
#[derive(Debug, Clone, Copy)]
-pub struct Scroller {
+struct Scroller {
/// The bounds of the [`Scroller`].
- pub bounds: Rectangle,
-}
-
-/// The renderer of a [`Scrollable`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Scrollable`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: column::Renderer + Sized {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Returns the [`Scrollbar`] given the bounds and content bounds of a
- /// [`Scrollable`].
- fn scrollbar(
- &self,
- bounds: Rectangle,
- content_bounds: Rectangle,
- offset: u32,
- scrollbar_width: u16,
- scrollbar_margin: u16,
- scroller_width: u16,
- ) -> Option<Scrollbar>;
-
- /// Draws the [`Scrollable`].
- ///
- /// It receives:
- /// - the [`State`] of the [`Scrollable`]
- /// - the bounds of the [`Scrollable`] widget
- /// - the bounds of the [`Scrollable`] content
- /// - whether the mouse is over the [`Scrollable`] or not
- /// - whether the mouse is over the [`Scrollbar`] or not
- /// - a optional [`Scrollbar`] to be rendered
- /// - the scrolling offset
- /// - the drawn content
- fn draw(
- &mut self,
- scrollable: &State,
- bounds: Rectangle,
- content_bounds: Rectangle,
- is_mouse_over: bool,
- is_mouse_over_scrollbar: bool,
- scrollbar: Option<Scrollbar>,
- offset: u32,
- style: &Self::Style,
- content: Self::Output,
- ) -> Self::Output;
+ bounds: Rectangle,
}
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 2a74d5a3..49bafab4 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -4,12 +4,17 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
+use crate::renderer;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Background, Clipboard, Color, Element, Hasher, Layout, Length, Point,
+ Rectangle, Size, Widget,
};
-use std::{hash::Hash, ops::RangeInclusive};
+use std::hash::Hash;
+use std::ops::RangeInclusive;
+
+pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
@@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// # Example
/// ```
-/// # use iced_native::{slider, renderer::Null};
+/// # use iced_native::widget::slider::{self, Slider};
/// #
-/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
@@ -37,7 +41,7 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
+pub struct Slider<'a, T, Message> {
state: &'a mut State,
range: RangeInclusive<T>,
step: T,
@@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
on_release: Option<Message>,
width: Length,
height: u16,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
+impl<'a, T, Message> Slider<'a, T, Message>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
- Renderer: self::Renderer,
{
+ /// The default height of a [`Slider`].
+ pub const DEFAULT_HEIGHT: u16 = 22;
+
/// Creates a new [`Slider`].
///
/// It expects:
@@ -93,8 +99,8 @@ where
on_change: Box::new(on_change),
on_release: None,
width: Length::Fill,
- height: Renderer::DEFAULT_HEIGHT,
- style: Renderer::Style::default(),
+ height: Self::DEFAULT_HEIGHT,
+ style_sheet: Default::default(),
}
}
@@ -122,8 +128,11 @@ where
}
/// Sets the style of the [`Slider`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -148,11 +157,11 @@ impl State {
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
- for Slider<'a, T, Message, Renderer>
+ for Slider<'a, T, Message>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -246,22 +255,113 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- let start = *self.range.start();
- let end = *self.range.end();
-
- renderer.draw(
- layout.bounds(),
- cursor_position,
- start.into() as f32..=end.into() as f32,
- self.value.into() as f32,
- self.state.is_dragging,
- &self.style,
- )
+ ) {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if self.state.is_dragging {
+ self.style_sheet.dragging()
+ } else if is_mouse_over {
+ self.style_sheet.hovered()
+ } else {
+ self.style_sheet.active()
+ };
+
+ let rail_y = bounds.y + (bounds.height / 2.0).round();
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y,
+ width: bounds.width,
+ height: 2.0,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail_colors.0,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y + 2.0,
+ width: bounds.width,
+ height: 2.0,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Background::Color(style.rail_colors.1),
+ );
+
+ let (handle_width, handle_height, handle_border_radius) = match style
+ .handle
+ .shape
+ {
+ HandleShape::Circle { radius } => {
+ (radius * 2.0, radius * 2.0, radius)
+ }
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), f32::from(bounds.height), border_radius),
+ };
+
+ let value = self.value.into() as f32;
+ let (range_start, range_end) = {
+ let (start, end) = self.range.clone().into_inner();
+
+ (start.into() as f32, end.into() as f32)
+ };
+
+ let handle_offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.width - handle_width) * (value - range_start)
+ / (range_end - range_start)
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round(),
+ y: rail_y - handle_height / 2.0,
+ width: handle_width,
+ height: handle_height,
+ },
+ border_radius: handle_border_radius,
+ border_width: style.handle.border_width,
+ border_color: style.handle.border_color,
+ },
+ style.handle.color,
+ );
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ if self.state.is_dragging {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::default()
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -272,48 +372,14 @@ where
}
}
-/// The renderer of a [`Slider`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Slider`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default height of a [`Slider`].
- const DEFAULT_HEIGHT: u16;
-
- /// Draws a [`Slider`].
- ///
- /// It receives:
- /// * the current cursor position
- /// * the bounds of the [`Slider`]
- /// * the local state of the [`Slider`]
- /// * the range of values of the [`Slider`]
- /// * the current value of the [`Slider`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- cursor_position: Point,
- range: RangeInclusive<f32>,
- value: f32,
- is_dragging: bool,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
+impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + crate::Renderer,
{
- fn from(
- slider: Slider<'a, T, Message, Renderer>,
- ) -> Element<'a, Message, Renderer> {
+ fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
}
diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs
index 6b34ece8..3373f3b7 100644
--- a/native/src/widget/space.rs
+++ b/native/src/widget/space.rs
@@ -1,9 +1,9 @@
//! Distribute content vertically.
-use std::hash::Hash;
+use crate::layout;
+use crate::renderer;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
-use crate::{
- layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
-};
+use std::hash::Hash;
/// An amount of empty space.
///
@@ -39,7 +39,7 @@ impl Space {
impl<Message, Renderer> Widget<Message, Renderer> for Space
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -61,13 +61,12 @@ where
fn draw(
&self,
- renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
- layout: Layout<'_>,
+ _renderer: &mut Renderer,
+ _style: &renderer::Style,
+ _layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(layout.bounds())
+ ) {
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -78,17 +77,9 @@ where
}
}
-/// The renderer of an amount of [`Space`].
-pub trait Renderer: crate::Renderer {
- /// Draws an amount of empty [`Space`].
- ///
- /// You should most likely return an empty primitive here.
- fn draw(&mut self, bounds: Rectangle) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: crate::Renderer,
Message: 'a,
{
fn from(space: Space) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 9cd61918..f212dfcb 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,12 +1,11 @@
//! Display vector graphics in your application.
use crate::layout;
+use crate::renderer;
+use crate::svg::{self, Handle};
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
-use std::{
- hash::{Hash, Hasher as _},
- path::PathBuf,
- sync::Arc,
-};
+use std::hash::Hash;
+use std::path::PathBuf;
/// A vector graphics image.
///
@@ -52,7 +51,7 @@ impl Svg {
impl<Message, Renderer> Widget<Message, Renderer> for Svg
where
- Renderer: self::Renderer,
+ Renderer: svg::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -90,12 +89,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(self.handle.clone(), layout)
+ ) {
+ renderer.draw(self.handle.clone(), layout.bounds())
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -107,94 +106,9 @@ where
}
}
-/// An [`Svg`] handle.
-#[derive(Debug, Clone)]
-pub struct Handle {
- id: u64,
- data: Arc<Data>,
-}
-
-impl Handle {
- /// Creates an SVG [`Handle`] pointing to the vector image of the given
- /// path.
- pub fn from_path(path: impl Into<PathBuf>) -> Handle {
- Self::from_data(Data::Path(path.into()))
- }
-
- /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
- /// or gzip compressed data.
- ///
- /// This is useful if you already have your SVG data in-memory, maybe
- /// because you downloaded or generated it procedurally.
- pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
- Self::from_data(Data::Bytes(bytes.into()))
- }
-
- fn from_data(data: Data) -> Handle {
- let mut hasher = Hasher::default();
- data.hash(&mut hasher);
-
- Handle {
- id: hasher.finish(),
- data: Arc::new(data),
- }
- }
-
- /// Returns the unique identifier of the [`Handle`].
- pub fn id(&self) -> u64 {
- self.id
- }
-
- /// Returns a reference to the SVG [`Data`].
- pub fn data(&self) -> &Data {
- &self.data
- }
-}
-
-impl Hash for Handle {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
-
-/// The data of an [`Svg`].
-#[derive(Clone, Hash)]
-pub enum Data {
- /// File data
- Path(PathBuf),
-
- /// In-memory data
- ///
- /// Can contain an SVG string or a gzip compressed data.
- Bytes(Vec<u8>),
-}
-
-impl std::fmt::Debug for Data {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Data::Path(path) => write!(f, "Path({:?})", path),
- Data::Bytes(_) => write!(f, "Bytes(...)"),
- }
- }
-}
-
-/// The renderer of an [`Svg`].
-///
-/// Your [renderer] will need to implement this trait before being able to use
-/// an [`Svg`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: crate::Renderer {
- /// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
- fn dimensions(&self, handle: &Handle) -> (u32, u32);
-
- /// Draws an [`Svg`].
- fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: svg::Renderer,
{
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
Element::new(icon)
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 168d49c2..4dbc4a65 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -1,12 +1,12 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
+use crate::renderer;
+use crate::text;
use crate::{
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
-pub use iced_core::text::Hit;
-
use std::hash::Hash;
/// A paragraph of text.
@@ -14,7 +14,7 @@ use std::hash::Hash;
/// # Example
///
/// ```
-/// # type Text = iced_native::Text<iced_native::renderer::Null>;
+/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .color([0.0, 0.0, 1.0])
@@ -23,7 +23,7 @@ use std::hash::Hash;
///
/// ![Text drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[derive(Debug)]
-pub struct Text<Renderer: self::Renderer> {
+pub struct Text<Renderer: text::Renderer> {
content: String,
size: Option<u16>,
color: Option<Color>,
@@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {
vertical_alignment: alignment::Vertical,
}
-impl<Renderer: self::Renderer> Text<Renderer> {
+impl<Renderer: text::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
@@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -134,21 +134,22 @@ where
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- renderer.draw(
- defaults,
- layout.bounds(),
+ ) {
+ draw(
+ renderer,
+ style,
+ layout,
&self.content,
- self.size.unwrap_or(renderer.default_size()),
self.font,
+ self.size,
self.color,
self.horizontal_alignment,
self.vertical_alignment,
- )
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -162,79 +163,65 @@ where
}
}
-/// The renderer of a [`Text`] fragment.
+/// Draws text using the same logic as the [`Text`] widget.
///
-/// Your [renderer] will need to implement this trait before being
-/// able to use [`Text`] in your user interface.
+/// Specifically:
///
-/// [renderer]: crate::Renderer
-pub trait Renderer: crate::Renderer {
- /// The font type used for [`Text`].
- type Font: Default + Copy;
-
- /// Returns the default size of [`Text`].
- fn default_size(&self) -> u16;
-
- /// Measures the [`Text`] in the given bounds and returns the minimum
- /// boundaries that can fit the contents.
- fn measure(
- &self,
- content: &str,
- size: u16,
- font: Self::Font,
- bounds: Size,
- ) -> (f32, f32);
-
- /// Tests whether the provided point is within the boundaries of [`Text`]
- /// laid out with the given parameters, returning information about
- /// the nearest character.
- ///
- /// If `nearest_only` is true, the hit test does not consider whether the
- /// the point is interior to any glyph bounds, returning only the character
- /// with the nearest centeroid.
- fn hit_test(
- &self,
- contents: &str,
- size: f32,
- font: Self::Font,
- bounds: Size,
- point: Point,
- nearest_only: bool,
- ) -> Option<Hit>;
-
- /// Draws a [`Text`] fragment.
- ///
- /// It receives:
- /// * the bounds of the [`Text`]
- /// * the contents of the [`Text`]
- /// * the size of the [`Text`]
- /// * the color of the [`Text`]
- /// * the [`HorizontalAlignment`] of the [`Text`]
- /// * the [`VerticalAlignment`] of the [`Text`]
- fn draw(
- &mut self,
- defaults: &Self::Defaults,
- bounds: Rectangle,
- content: &str,
- size: u16,
- font: Self::Font,
- color: Option<Color>,
- horizontal_alignment: alignment::Horizontal,
- vertical_alignment: alignment::Vertical,
- ) -> Self::Output;
+/// * If no `size` is provided, the default text size of the `Renderer` will be
+/// used.
+/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
+/// used.
+/// * The alignment attributes do not affect the position of the bounds of the
+/// [`Layout`].
+pub fn draw<Renderer>(
+ renderer: &mut Renderer,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ content: &str,
+ font: Renderer::Font,
+ size: Option<u16>,
+ color: Option<Color>,
+ horizontal_alignment: alignment::Horizontal,
+ vertical_alignment: alignment::Vertical,
+) where
+ Renderer: text::Renderer,
+{
+ let bounds = layout.bounds();
+
+ let x = match horizontal_alignment {
+ alignment::Horizontal::Left => bounds.x,
+ alignment::Horizontal::Center => bounds.center_x(),
+ alignment::Horizontal::Right => bounds.x + bounds.width,
+ };
+
+ let y = match vertical_alignment {
+ alignment::Vertical::Top => bounds.y,
+ alignment::Vertical::Center => bounds.center_y(),
+ alignment::Vertical::Bottom => bounds.y + bounds.height,
+ };
+
+ renderer.fill_text(crate::text::Text {
+ content,
+ size: f32::from(size.unwrap_or(renderer.default_size())),
+ bounds: Rectangle { x, y, ..bounds },
+ color: color.unwrap_or(style.text_color),
+ font,
+ horizontal_alignment,
+ vertical_alignment,
+ });
}
impl<'a, Message, Renderer> From<Text<Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer + 'a,
+ Renderer: text::Renderer + 'a,
{
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(text)
}
}
-impl<Renderer: self::Renderer> Clone for Text<Renderer> {
+impl<Renderer: text::Renderer> Clone for Text<Renderer> {
fn clone(&self) -> Self {
Self {
content: self.content.clone(),
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index d4d197d3..40c6c573 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -11,26 +11,31 @@ pub use value::Value;
use editor::Editor;
+use crate::alignment;
use crate::event::{self, Event};
use crate::keyboard;
use crate::layout;
use crate::mouse::{self, click};
-use crate::text;
+use crate::renderer;
+use crate::text::{self, Text};
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
- Size, Widget,
+ Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Size, Vector, Widget,
};
use std::u32;
+pub use iced_style::text_input::{Style, StyleSheet};
+
/// A field that can be filled with text.
///
/// # Example
/// ```
-/// # use iced_native::{text_input, renderer::Null};
+/// # use iced_native::renderer::Null;
+/// # use iced_native::widget::text_input;
/// #
-/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;
+/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),
@@ -49,7 +54,7 @@ use std::u32;
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct TextInput<'a, Message, Renderer: self::Renderer> {
+pub struct TextInput<'a, Message, Renderer: text::Renderer> {
state: &'a mut State,
placeholder: String,
value: Value,
@@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
/// Creates a new [`TextInput`].
///
@@ -97,7 +102,7 @@ where
size: None,
on_change: Box::new(on_change),
on_submit: None,
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -147,8 +152,11 @@ where
}
/// Sets the style of the [`TextInput`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -160,7 +168,7 @@ where
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
@@ -170,37 +178,165 @@ where
layout: Layout<'_>,
cursor_position: Point,
value: Option<&Value>,
- ) -> Renderer::Output {
+ ) {
let value = value.unwrap_or(&self.value);
+ let secure_value = self.is_secure.then(|| value.secure());
+ let value = secure_value.as_ref().unwrap_or(&value);
+
let bounds = layout.bounds();
let text_bounds = layout.children().next().unwrap().bounds();
- if self.is_secure {
- self::Renderer::draw(
- renderer,
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &value.secure(),
- &self.state,
- &self.style,
- )
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let style = if self.state.is_focused() {
+ self.style_sheet.focused()
+ } else if is_mouse_over {
+ self.style_sheet.hovered()
} else {
- self::Renderer::draw(
- renderer,
+ self.style_sheet.active()
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- value,
- &self.state,
- &self.style,
- )
+ border_radius: style.border_radius,
+ border_width: style.border_width,
+ border_color: style.border_color,
+ },
+ style.background,
+ );
+
+ let text = value.to_string();
+ let size = self.size.unwrap_or(renderer.default_size());
+
+ let (cursor, offset) = if self.state.is_focused() {
+ match self.state.cursor.state(&value) {
+ cursor::State::Index(position) => {
+ let (text_value_width, offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ position,
+ self.font,
+ );
+
+ (
+ Some((
+ renderer::Quad {
+ bounds: Rectangle {
+ x: text_bounds.x + text_value_width,
+ y: text_bounds.y,
+ width: 1.0,
+ height: text_bounds.height,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ self.style_sheet.value_color(),
+ )),
+ offset,
+ )
+ }
+ cursor::State::Selection { start, end } => {
+ let left = start.min(end);
+ let right = end.max(start);
+
+ let (left_position, left_offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ left,
+ self.font,
+ );
+
+ let (right_position, right_offset) =
+ measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ &value,
+ size,
+ right,
+ self.font,
+ );
+
+ let width = right_position - left_position;
+
+ (
+ Some((
+ renderer::Quad {
+ bounds: Rectangle {
+ x: text_bounds.x + left_position,
+ y: text_bounds.y,
+ width,
+ height: text_bounds.height,
+ },
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ self.style_sheet.selection_color(),
+ )),
+ if end == right {
+ right_offset
+ } else {
+ left_offset
+ },
+ )
+ }
+ }
+ } else {
+ (None, 0.0)
+ };
+
+ let text_width = renderer.measure_width(
+ if text.is_empty() {
+ &self.placeholder
+ } else {
+ &text
+ },
+ size,
+ self.font,
+ );
+
+ let render = |renderer: &mut Renderer| {
+ if let Some((cursor, color)) = cursor {
+ renderer.fill_quad(cursor, color);
+ }
+
+ renderer.fill_text(Text {
+ content: if text.is_empty() {
+ &self.placeholder
+ } else {
+ &text
+ },
+ color: if text.is_empty() {
+ self.style_sheet.placeholder_color()
+ } else {
+ self.style_sheet.value_color()
+ },
+ font: self.font,
+ bounds: Rectangle {
+ y: text_bounds.center_y(),
+ width: f32::INFINITY,
+ ..text_bounds
+ },
+ size: f32::from(size),
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Center,
+ });
+ };
+
+ if text_width > text_bounds.width {
+ renderer.with_layer(text_bounds, |renderer| {
+ renderer.with_translation(Vector::new(-offset, 0.0), render)
+ });
+ } else {
+ render(renderer);
}
}
}
@@ -209,7 +345,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
Message: Clone,
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -275,7 +411,8 @@ where
self.value.clone()
};
- renderer.find_cursor_position(
+ find_cursor_position(
+ renderer,
text_layout.bounds(),
self.font,
self.size,
@@ -294,16 +431,16 @@ where
if self.is_secure {
self.state.cursor.select_all(&self.value);
} else {
- let position = renderer
- .find_cursor_position(
- text_layout.bounds(),
- self.font,
- self.size,
- &self.value,
- &self.state,
- target,
- )
- .unwrap_or(0);
+ let position = find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ self.font,
+ self.size,
+ &self.value,
+ &self.state,
+ target,
+ )
+ .unwrap_or(0);
self.state.cursor.select_range(
self.value.previous_start_of_word(position),
@@ -341,16 +478,16 @@ where
self.value.clone()
};
- let position = renderer
- .find_cursor_position(
- text_layout.bounds(),
- self.font,
- self.size,
- &value,
- &self.state,
- target,
- )
- .unwrap_or(0);
+ let position = find_cursor_position(
+ renderer,
+ text_layout.bounds(),
+ self.font,
+ self.size,
+ &value,
+ &self.state,
+ target,
+ )
+ .unwrap_or(0);
self.state.cursor.select_range(
self.state.cursor.start(&value),
@@ -621,14 +758,27 @@ where
event::Status::Ignored
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Text
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- _defaults: &Renderer::Defaults,
+ _style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
+ ) {
self.draw(renderer, layout, cursor_position, None)
}
@@ -644,87 +794,11 @@ where
}
}
-/// The renderer of a [`TextInput`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`TextInput`] in your user interface.
-///
-/// [renderer]: crate::renderer
-pub trait Renderer: text::Renderer + Sized {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// Returns the width of the value of the [`TextInput`].
- fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
-
- /// Returns the current horizontal offset of the value of the
- /// [`TextInput`].
- ///
- /// This is the amount of horizontal scrolling applied when the [`Value`]
- /// does not fit the [`TextInput`].
- fn offset(
- &self,
- text_bounds: Rectangle,
- font: Self::Font,
- size: u16,
- value: &Value,
- state: &State,
- ) -> f32;
-
- /// Draws a [`TextInput`].
- ///
- /// It receives:
- /// - the bounds of the [`TextInput`]
- /// - the bounds of the text (i.e. the current value)
- /// - the cursor position
- /// - the placeholder to show when the value is empty
- /// - the current [`Value`]
- /// - the current [`State`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- text_bounds: Rectangle,
- cursor_position: Point,
- font: Self::Font,
- size: u16,
- placeholder: &str,
- value: &Value,
- state: &State,
- style: &Self::Style,
- ) -> Self::Output;
-
- /// Computes the position of the text cursor at the given X coordinate of
- /// a [`TextInput`].
- fn find_cursor_position(
- &self,
- text_bounds: Rectangle,
- font: Self::Font,
- size: Option<u16>,
- value: &Value,
- state: &State,
- x: f32,
- ) -> Option<usize> {
- let size = size.unwrap_or(self.default_size());
-
- let offset = self.offset(text_bounds, font, size, &value, &state);
-
- self.hit_test(
- &value.to_string(),
- size.into(),
- font,
- Size::INFINITY,
- Point::new(x + offset, text_bounds.height / 2.0),
- true,
- )
- .map(text::Hit::cursor)
- }
-}
-
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + text::Renderer,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
@@ -815,3 +889,88 @@ mod platform {
}
}
}
+
+fn offset<Renderer>(
+ renderer: &Renderer,
+ text_bounds: Rectangle,
+ font: Renderer::Font,
+ size: u16,
+ value: &Value,
+ state: &State,
+) -> f32
+where
+ Renderer: text::Renderer,
+{
+ if state.is_focused() {
+ let cursor = state.cursor();
+
+ let focus_position = match cursor.state(value) {
+ cursor::State::Index(i) => i,
+ cursor::State::Selection { end, .. } => end,
+ };
+
+ let (_, offset) = measure_cursor_and_scroll_offset(
+ renderer,
+ text_bounds,
+ value,
+ size,
+ focus_position,
+ font,
+ );
+
+ offset
+ } else {
+ 0.0
+ }
+}
+
+fn measure_cursor_and_scroll_offset<Renderer>(
+ renderer: &Renderer,
+ text_bounds: Rectangle,
+ value: &Value,
+ size: u16,
+ cursor_index: usize,
+ font: Renderer::Font,
+) -> (f32, f32)
+where
+ Renderer: text::Renderer,
+{
+ let text_before_cursor = value.until(cursor_index).to_string();
+
+ let text_value_width =
+ renderer.measure_width(&text_before_cursor, size, font);
+
+ let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
+
+ (text_value_width, offset)
+}
+
+/// Computes the position of the text cursor at the given X coordinate of
+/// a [`TextInput`].
+fn find_cursor_position<Renderer>(
+ renderer: &Renderer,
+ text_bounds: Rectangle,
+ font: Renderer::Font,
+ size: Option<u16>,
+ value: &Value,
+ state: &State,
+ x: f32,
+) -> Option<usize>
+where
+ Renderer: text::Renderer,
+{
+ let size = size.unwrap_or(renderer.default_size());
+
+ let offset = offset(renderer, text_bounds, font, size, &value, &state);
+
+ renderer
+ .hit_test(
+ &value.to_string(),
+ size.into(),
+ font,
+ Size::INFINITY,
+ Point::new(x + offset, text_bounds.height / 2.0),
+ true,
+ )
+ .map(text::Hit::cursor)
+}
diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs
index 0b50a382..bac530e1 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/native/src/widget/text_input/editor.rs
@@ -1,4 +1,4 @@
-use crate::text_input::{Cursor, Value};
+use crate::widget::text_input::{Cursor, Value};
pub struct Editor<'a> {
value: &'a mut Value,
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
index c624be4c..2dcc3ffe 100644
--- a/native/src/widget/toggler.rs
+++ b/native/src/widget/toggler.rs
@@ -5,19 +5,22 @@ use crate::alignment;
use crate::event;
use crate::layout;
use crate::mouse;
-use crate::row;
+use crate::renderer;
use crate::text;
+use crate::widget::{Row, Text};
use crate::{
Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Row, Text, Widget,
+ Rectangle, Widget,
};
+pub use iced_style::toggler::{Style, StyleSheet};
+
/// A toggler widget
///
/// # Example
///
/// ```
-/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
+/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// TogglerToggled(bool),
@@ -28,7 +31,7 @@ use crate::{
/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));
/// ```
#[allow(missing_debug_implementations)]
-pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
+pub struct Toggler<'a, Message, Renderer: text::Renderer> {
is_active: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: Option<String>,
@@ -38,12 +41,13 @@ pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
text_alignment: alignment::Horizontal,
spacing: u16,
font: Renderer::Font,
- style: Renderer::Style,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message, Renderer: self::Renderer + text::Renderer>
- Toggler<Message, Renderer>
-{
+impl<'a, Message, Renderer: text::Renderer> Toggler<'a, Message, Renderer> {
+ /// The default size of a [`Toggler`].
+ pub const DEFAULT_SIZE: u16 = 20;
+
/// Creates a new [`Toggler`].
///
/// It expects:
@@ -65,12 +69,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
on_toggle: Box::new(f),
label: label.into(),
width: Length::Fill,
- size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ size: Self::DEFAULT_SIZE,
text_size: None,
text_alignment: alignment::Horizontal::Left,
spacing: 0,
font: Renderer::Font::default(),
- style: Renderer::Style::default(),
+ style_sheet: Default::default(),
}
}
@@ -111,15 +115,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
}
/// Sets the style of the [`Toggler`].
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Toggler<'a, Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -183,50 +191,108 @@ where
}
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ if layout.bounds().contains(cursor_position) {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
- ) -> Renderer::Output {
- let bounds = layout.bounds();
- let mut children = layout.children();
+ ) {
+ /// Makes sure that the border radius of the toggler looks good at every size.
+ const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
- let label = match &self.label {
- Some(label) => {
- let label_layout = children.next().unwrap();
-
- Some(text::Renderer::draw(
- renderer,
- defaults,
- label_layout.bounds(),
- &label,
- self.text_size.unwrap_or(renderer.default_size()),
- self.font,
- None,
- self.text_alignment,
- alignment::Vertical::Center,
- ))
- }
+ /// The space ratio between the background Quad and the Toggler bounds, and
+ /// between the background Quad and foreground Quad.
+ const SPACE_RATIO: f32 = 0.05;
- None => None,
- };
+ let mut children = layout.children();
+
+ if let Some(label) = &self.label {
+ let label_layout = children.next().unwrap();
+
+ crate::widget::text::draw(
+ renderer,
+ style,
+ label_layout,
+ &label,
+ self.font,
+ self.text_size,
+ None,
+ self.text_alignment,
+ alignment::Vertical::Center,
+ );
+ }
let toggler_layout = children.next().unwrap();
- let toggler_bounds = toggler_layout.bounds();
+ let bounds = toggler_layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
- self::Renderer::draw(
- renderer,
- toggler_bounds,
- self.is_active,
- is_mouse_over,
- label,
- &self.style,
- )
+ let style = if is_mouse_over {
+ self.style_sheet.hovered(self.is_active)
+ } else {
+ self.style_sheet.active(self.is_active)
+ };
+
+ let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
+ let space = SPACE_RATIO * bounds.height as f32;
+
+ let toggler_background_bounds = Rectangle {
+ x: bounds.x + space,
+ y: bounds.y + space,
+ width: bounds.width - (2.0 * space),
+ height: bounds.height - (2.0 * space),
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: toggler_background_bounds,
+ border_radius,
+ border_width: 1.0,
+ border_color: style
+ .background_border
+ .unwrap_or(style.background),
+ },
+ style.background,
+ );
+
+ let toggler_foreground_bounds = Rectangle {
+ x: bounds.x
+ + if self.is_active {
+ bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
+ } else {
+ 2.0 * space
+ },
+ y: bounds.y + (2.0 * space),
+ width: bounds.height - (4.0 * space),
+ height: bounds.height - (4.0 * space),
+ };
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: toggler_foreground_bounds,
+ border_radius,
+ border_width: 1.0,
+ border_color: style
+ .foreground_border
+ .unwrap_or(style.foreground),
+ },
+ style.foreground,
+ );
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -237,45 +303,14 @@ where
}
}
-/// The renderer of a [`Toggler`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Toggler`] in your user interface.
-///
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer {
- /// The style supported by this renderer.
- type Style: Default;
-
- /// The default size of a [`Toggler`].
- const DEFAULT_SIZE: u16;
-
- /// Draws a [`Toggler`].
- ///
- /// It receives:
- /// * the bounds of the [`Toggler`]
- /// * whether the [`Toggler`] is activated or not
- /// * whether the mouse is over the [`Toggler`] or not
- /// * the drawn label of the [`Toggler`]
- /// * the style of the [`Toggler`]
- fn draw(
- &mut self,
- bounds: Rectangle,
- is_active: bool,
- is_mouse_over: bool,
- label: Option<Self::Output>,
- style: &Self::Style,
- ) -> Self::Output;
-}
-
-impl<'a, Message, Renderer> From<Toggler<Message, Renderer>>
+impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Renderer: 'a + text::Renderer,
Message: 'a,
{
fn from(
- toggler: Toggler<Message, Renderer>,
+ toggler: Toggler<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(toggler)
}
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
index 276afd41..c35005e0 100644
--- a/native/src/widget/tooltip.rs
+++ b/native/src/widget/tooltip.rs
@@ -3,28 +3,36 @@ use std::hash::Hash;
use iced_core::Rectangle;
+use crate::event;
+use crate::layout;
+use crate::mouse;
+use crate::renderer;
+use crate::text;
use crate::widget::container;
-use crate::widget::text::{self, Text};
+use crate::widget::text::Text;
use crate::{
- event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Widget,
+ Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Size,
+ Vector, Widget,
};
/// An element to display a widget over another.
#[allow(missing_debug_implementations)]
-pub struct Tooltip<'a, Message, Renderer: self::Renderer> {
+pub struct Tooltip<'a, Message, Renderer: text::Renderer> {
content: Element<'a, Message, Renderer>,
tooltip: Text<Renderer>,
position: Position,
- style: <Renderer as container::Renderer>::Style,
+ style_sheet: Box<dyn container::StyleSheet + 'a>,
gap: u16,
padding: u16,
}
impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
+ /// The default padding of a [`Tooltip`] drawn by this renderer.
+ const DEFAULT_PADDING: u16 = 5;
+
/// Creates an empty [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
@@ -37,9 +45,9 @@ where
content: content.into(),
tooltip: Text::new(tooltip.to_string()),
position,
- style: Default::default(),
+ style_sheet: Default::default(),
gap: 0,
- padding: Renderer::DEFAULT_PADDING,
+ padding: Self::DEFAULT_PADDING,
}
}
@@ -72,9 +80,9 @@ where
/// Sets the style of the [`Tooltip`].
pub fn style(
mut self,
- style: impl Into<<Renderer as container::Renderer>::Style>,
+ style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self {
- self.style = style.into();
+ self.style_sheet = style_sheet.into();
self
}
}
@@ -97,7 +105,7 @@ pub enum Position {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Tooltip<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.content.width()
@@ -134,27 +142,126 @@ where
)
}
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> mouse::Interaction {
+ self.content
+ .mouse_interaction(layout, cursor_position, viewport)
+ }
+
fn draw(
&self,
renderer: &mut Renderer,
- defaults: &Renderer::Defaults,
+ inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
- ) -> Renderer::Output {
- self::Renderer::draw(
+ ) {
+ self.content.draw(
renderer,
- defaults,
- cursor_position,
+ inherited_style,
layout,
+ cursor_position,
viewport,
- &self.content,
- &self.tooltip,
- self.position,
- &self.style,
- self.gap,
- self.padding,
- )
+ );
+
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ let gap = f32::from(self.gap);
+ let style = self.style_sheet.style();
+
+ let defaults = renderer::Style {
+ text_color: style
+ .text_color
+ .unwrap_or(inherited_style.text_color),
+ };
+
+ let text_layout = Widget::<(), Renderer>::layout(
+ &self.tooltip,
+ renderer,
+ &layout::Limits::new(Size::ZERO, viewport.size())
+ .pad(Padding::new(self.padding)),
+ );
+
+ let padding = f32::from(self.padding);
+ let text_bounds = text_layout.bounds();
+ let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
+ let y_center =
+ bounds.y + (bounds.height - text_bounds.height) / 2.0;
+
+ let mut tooltip_bounds = {
+ let offset = match self.position {
+ Position::Top => Vector::new(
+ x_center,
+ bounds.y - text_bounds.height - gap - padding,
+ ),
+ Position::Bottom => Vector::new(
+ x_center,
+ bounds.y + bounds.height + gap + padding,
+ ),
+ Position::Left => Vector::new(
+ bounds.x - text_bounds.width - gap - padding,
+ y_center,
+ ),
+ Position::Right => Vector::new(
+ bounds.x + bounds.width + gap + padding,
+ y_center,
+ ),
+ Position::FollowCursor => Vector::new(
+ cursor_position.x,
+ cursor_position.y - text_bounds.height,
+ ),
+ };
+
+ Rectangle {
+ x: offset.x - padding,
+ y: offset.y - padding,
+ width: text_bounds.width + padding * 2.0,
+ height: text_bounds.height + padding * 2.0,
+ }
+ };
+
+ if tooltip_bounds.x < viewport.x {
+ tooltip_bounds.x = viewport.x;
+ } else if viewport.x + viewport.width
+ < tooltip_bounds.x + tooltip_bounds.width
+ {
+ tooltip_bounds.x =
+ viewport.x + viewport.width - tooltip_bounds.width;
+ }
+
+ if tooltip_bounds.y < viewport.y {
+ tooltip_bounds.y = viewport.y;
+ } else if viewport.y + viewport.height
+ < tooltip_bounds.y + tooltip_bounds.height
+ {
+ tooltip_bounds.y =
+ viewport.y + viewport.height - tooltip_bounds.height;
+ }
+
+ renderer.with_layer(*viewport, |renderer| {
+ container::draw_background(renderer, &style, tooltip_bounds);
+
+ Widget::<(), Renderer>::draw(
+ &self.tooltip,
+ renderer,
+ &defaults,
+ Layout::with_offset(
+ Vector::new(
+ tooltip_bounds.x + padding,
+ tooltip_bounds.y + padding,
+ ),
+ &text_layout,
+ ),
+ cursor_position,
+ viewport,
+ );
+ });
+ }
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -165,41 +272,10 @@ where
}
}
-/// The renderer of a [`Tooltip`].
-///
-/// Your [renderer] will need to implement this trait before being
-/// able to use a [`Tooltip`] in your user interface.
-///
-/// [`Tooltip`]: struct.Tooltip.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer:
- crate::Renderer + text::Renderer + container::Renderer
-{
- /// The default padding of a [`Tooltip`] drawn by this renderer.
- const DEFAULT_PADDING: u16;
-
- /// Draws a [`Tooltip`].
- ///
- /// [`Tooltip`]: struct.Tooltip.html
- fn draw<Message>(
- &mut self,
- defaults: &Self::Defaults,
- cursor_position: Point,
- content_layout: Layout<'_>,
- viewport: &Rectangle,
- content: &Element<'_, Message, Self>,
- tooltip: &Text<Self>,
- position: Position,
- style: &<Self as container::Renderer>::Style,
- gap: u16,
- padding: u16,
- ) -> Self::Output;
-}
-
impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + text::Renderer,
Message: 'a,
{
fn from(
diff --git a/src/widget.rs b/src/widget.rs
index db052106..0f0b0325 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -38,14 +38,16 @@ mod platform {
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
pub mod image {
//! Display images in your user interface.
- pub use crate::runtime::image::viewer;
- pub use crate::runtime::image::{Handle, Image, Viewer};
+ pub use crate::runtime::image::Handle;
+ pub use crate::runtime::widget::image::viewer;
+ pub use crate::runtime::widget::image::{Image, Viewer};
}
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
pub mod svg {
//! Display vector graphics in your user interface.
- pub use crate::runtime::svg::{Handle, Svg};
+ pub use crate::runtime::svg::Handle;
+ pub use crate::runtime::widget::svg::Svg;
}
#[doc(no_inline)]
diff --git a/style/src/button.rs b/style/src/button.rs
index 2281e32f..de2de4f4 100644
--- a/style/src/button.rs
+++ b/style/src/button.rs
@@ -80,17 +80,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs
index 566136bb..f8bc6241 100644
--- a/style/src/checkbox.rs
+++ b/style/src/checkbox.rs
@@ -39,17 +39,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/container.rs b/style/src/container.rs
index 1ce6a7ca..2f411611 100644
--- a/style/src/container.rs
+++ b/style/src/container.rs
@@ -43,17 +43,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs
index e39ee797..a12ac3f5 100644
--- a/style/src/pane_grid.rs
+++ b/style/src/pane_grid.rs
@@ -35,17 +35,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs
index d1801e5f..ad96b201 100644
--- a/style/src/pick_list.rs
+++ b/style/src/pick_list.rs
@@ -56,15 +56,15 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: 'a + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs
index d0878c84..a0195c7a 100644
--- a/style/src/progress_bar.rs
+++ b/style/src/progress_bar.rs
@@ -26,15 +26,15 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: 'a + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
diff --git a/style/src/radio.rs b/style/src/radio.rs
index c41b70c0..6fb1cb6c 100644
--- a/style/src/radio.rs
+++ b/style/src/radio.rs
@@ -37,17 +37,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/rule.rs b/style/src/rule.rs
index 221f48fb..12a40f7d 100644
--- a/style/src/rule.rs
+++ b/style/src/rule.rs
@@ -1,5 +1,4 @@
//! Display a horizontal or vertical rule for dividing content.
-
use iced_core::Color;
/// The fill mode of a rule.
@@ -104,15 +103,15 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: 'a + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs
index 65da9803..748ba888 100644
--- a/style/src/scrollable.rs
+++ b/style/src/scrollable.rs
@@ -60,17 +60,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/slider.rs b/style/src/slider.rs
index 9148fcbe..1bb28b09 100644
--- a/style/src/slider.rs
+++ b/style/src/slider.rs
@@ -79,17 +79,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/text_input.rs b/style/src/text_input.rs
index 19acea65..3d5817cc 100644
--- a/style/src/text_input.rs
+++ b/style/src/text_input.rs
@@ -73,17 +73,17 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: StyleSheet + 'a,
{
- fn from(style: T) -> Self {
- Box::new(style)
+ fn from(style_sheet: T) -> Self {
+ Box::new(style_sheet)
}
}
diff --git a/style/src/toggler.rs b/style/src/toggler.rs
index 5a155123..c06a8cd1 100644
--- a/style/src/toggler.rs
+++ b/style/src/toggler.rs
@@ -41,15 +41,15 @@ impl StyleSheet for Default {
}
}
-impl std::default::Default for Box<dyn StyleSheet> {
+impl<'a> std::default::Default for Box<dyn StyleSheet + 'a> {
fn default() -> Self {
Box::new(Default)
}
}
-impl<T> From<T> for Box<dyn StyleSheet>
+impl<'a, T> From<T> for Box<dyn StyleSheet + 'a>
where
- T: 'static + StyleSheet,
+ T: 'a + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs
index cd450b55..88137607 100644
--- a/web/src/widget/button.rs
+++ b/web/src/widget/button.rs
@@ -51,7 +51,7 @@ pub struct Button<'a, Message> {
#[allow(dead_code)]
min_height: u32,
padding: Padding,
- style: Box<dyn StyleSheet>,
+ style: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message> Button<'a, Message> {
@@ -104,7 +104,7 @@ impl<'a, Message> Button<'a, Message> {
}
/// Sets the style of the [`Button`].
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
+ pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
self.style = style.into();
self
}
diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs
index 43110aa7..844bf862 100644
--- a/web/src/widget/checkbox.rs
+++ b/web/src/widget/checkbox.rs
@@ -24,17 +24,17 @@ use std::rc::Rc;
///
/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Checkbox<Message> {
+pub struct Checkbox<'a, Message> {
is_checked: bool,
on_toggle: Rc<dyn Fn(bool) -> Message>,
label: String,
id: Option<String>,
width: Length,
#[allow(dead_code)]
- style: Box<dyn StyleSheet>,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message> Checkbox<Message> {
+impl<'a, Message> Checkbox<'a, Message> {
/// Creates a new [`Checkbox`].
///
/// It expects:
@@ -53,7 +53,7 @@ impl<Message> Checkbox<Message> {
label: label.into(),
id: None,
width: Length::Shrink,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -64,8 +64,11 @@ impl<Message> Checkbox<Message> {
}
/// Sets the style of the [`Checkbox`].
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -76,7 +79,7 @@ impl<Message> Checkbox<Message> {
}
}
-impl<Message> Widget<Message> for Checkbox<Message>
+impl<'a, Message> Widget<Message> for Checkbox<'a, Message>
where
Message: 'static,
{
@@ -137,11 +140,11 @@ where
}
}
-impl<'a, Message> From<Checkbox<Message>> for Element<'a, Message>
+impl<'a, Message> From<Checkbox<'a, Message>> for Element<'a, Message>
where
Message: 'static,
{
- fn from(checkbox: Checkbox<Message>) -> Element<'a, Message> {
+ fn from(checkbox: Checkbox<'a, Message>) -> Element<'a, Message> {
Element::new(checkbox)
}
}
diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs
index 24aa7cef..8e345b9a 100644
--- a/web/src/widget/container.rs
+++ b/web/src/widget/container.rs
@@ -19,7 +19,7 @@ pub struct Container<'a, Message> {
max_height: u32,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
- style_sheet: Box<dyn StyleSheet>,
+ style_sheet: Box<dyn StyleSheet + 'a>,
content: Element<'a, Message>,
}
@@ -89,7 +89,7 @@ impl<'a, Message> Container<'a, Message> {
}
/// Sets the style of the [`Container`].
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
+ pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
self.style_sheet = style.into();
self
}
diff --git a/web/src/widget/progress_bar.rs b/web/src/widget/progress_bar.rs
index 7d77616e..01f412f8 100644
--- a/web/src/widget/progress_bar.rs
+++ b/web/src/widget/progress_bar.rs
@@ -18,15 +18,15 @@ use std::ops::RangeInclusive;
///
/// ![Progress bar](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
-pub struct ProgressBar {
+pub struct ProgressBar<'a> {
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
- style: Box<dyn StyleSheet>,
+ style: Box<dyn StyleSheet + 'a>,
}
-impl ProgressBar {
+impl<'a> ProgressBar<'a> {
/// Creates a new [`ProgressBar`].
///
/// It expects:
@@ -61,7 +61,7 @@ impl ProgressBar {
}
}
-impl<Message> Widget<Message> for ProgressBar {
+impl<'a, Message> Widget<Message> for ProgressBar<'a> {
fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
@@ -106,11 +106,11 @@ impl<Message> Widget<Message> for ProgressBar {
}
}
-impl<'a, Message> From<ProgressBar> for Element<'a, Message>
+impl<'a, Message> From<ProgressBar<'a>> for Element<'a, Message>
where
Message: 'static,
{
- fn from(container: ProgressBar) -> Element<'a, Message> {
+ fn from(container: ProgressBar<'a>) -> Element<'a, Message> {
Element::new(container)
}
}
diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs
index fbc88d29..03b2922b 100644
--- a/web/src/widget/radio.rs
+++ b/web/src/widget/radio.rs
@@ -31,17 +31,17 @@ use dodrio::bumpalo;
///
/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Radio<Message> {
+pub struct Radio<'a, Message> {
is_selected: bool,
on_click: Message,
label: String,
id: Option<String>,
name: Option<String>,
#[allow(dead_code)]
- style: Box<dyn StyleSheet>,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
-impl<Message> Radio<Message> {
+impl<'a, Message> Radio<'a, Message> {
/// Creates a new [`Radio`] button.
///
/// It expects:
@@ -66,13 +66,16 @@ impl<Message> Radio<Message> {
label: label.into(),
id: None,
name: None,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
/// Sets the style of the [`Radio`] button.
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
@@ -89,7 +92,7 @@ impl<Message> Radio<Message> {
}
}
-impl<Message> Widget<Message> for Radio<Message>
+impl<'a, Message> Widget<Message> for Radio<'a, Message>
where
Message: 'static + Clone,
{
@@ -142,11 +145,11 @@ where
}
}
-impl<'a, Message> From<Radio<Message>> for Element<'a, Message>
+impl<'a, Message> From<Radio<'a, Message>> for Element<'a, Message>
where
Message: 'static + Clone,
{
- fn from(radio: Radio<Message>) -> Element<'a, Message> {
+ fn from(radio: Radio<'a, Message>) -> Element<'a, Message> {
Element::new(radio)
}
}
diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs
index 847bf5a0..22cb61be 100644
--- a/web/src/widget/scrollable.rs
+++ b/web/src/widget/scrollable.rs
@@ -14,7 +14,7 @@ pub struct Scrollable<'a, Message> {
max_height: u32,
content: Column<'a, Message>,
#[allow(dead_code)]
- style: Box<dyn StyleSheet>,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message> Scrollable<'a, Message> {
@@ -27,7 +27,7 @@ impl<'a, Message> Scrollable<'a, Message> {
height: Length::Shrink,
max_height: u32::MAX,
content: Column::new(),
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -78,8 +78,11 @@ impl<'a, Message> Scrollable<'a, Message> {
}
/// Sets the style of the [`Scrollable`] .
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs
index f457aa4c..8cbf5bd0 100644
--- a/web/src/widget/slider.rs
+++ b/web/src/widget/slider.rs
@@ -41,7 +41,7 @@ pub struct Slider<'a, T, Message> {
#[allow(dead_code)]
width: Length,
#[allow(dead_code)]
- style: Box<dyn StyleSheet>,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, T, Message> Slider<'a, T, Message>
@@ -85,7 +85,7 @@ where
step: T::from(1),
on_change: Rc::new(Box::new(on_change)),
width: Length::Fill,
- style: Default::default(),
+ style_sheet: Default::default(),
}
}
@@ -96,8 +96,11 @@ where
}
/// Sets the style of the [`Slider`].
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
- self.style = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs
index e4877f2a..c5874485 100644
--- a/web/src/widget/text_input.rs
+++ b/web/src/widget/text_input.rs
@@ -39,7 +39,7 @@ pub struct TextInput<'a, Message> {
size: Option<u16>,
on_change: Rc<Box<dyn Fn(String) -> Message>>,
on_submit: Option<Message>,
- style_sheet: Box<dyn StyleSheet>,
+ style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message> TextInput<'a, Message> {
@@ -112,8 +112,11 @@ impl<'a, Message> TextInput<'a, Message> {
}
/// Sets the style of the [`TextInput`].
- pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
- self.style_sheet = style.into();
+ pub fn style(
+ mut self,
+ style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
+ ) -> Self {
+ self.style_sheet = style_sheet.into();
self
}
}
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 73fa5c5f..bb84cc8f 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -8,7 +8,6 @@ use iced_graphics::font;
use iced_graphics::layer::Layer;
use iced_graphics::{Primitive, Viewport};
use iced_native::alignment;
-use iced_native::mouse;
use iced_native::{Font, Size};
#[cfg(any(feature = "image_rs", feature = "svg"))]
@@ -28,6 +27,7 @@ pub struct Backend {
image_pipeline: image::Pipeline,
default_text_size: u16,
+ primitive: Primitive,
}
impl Backend {
@@ -60,6 +60,7 @@ impl Backend {
image_pipeline,
default_text_size: settings.default_text_size,
+ primitive: Primitive::None,
}
}
@@ -67,23 +68,23 @@ impl Backend {
///
/// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information.
- pub fn draw<T: AsRef<str>>(
+ pub fn present<T: AsRef<str>>(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
frame: &wgpu::TextureView,
+ primitives: &[Primitive],
viewport: &Viewport,
- (primitive, mouse_interaction): &(Primitive, mouse::Interaction),
overlay_text: &[T],
- ) -> mouse::Interaction {
+ ) {
log::debug!("Drawing");
let target_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let transformation = viewport.projection();
- let mut layers = Layer::generate(primitive, viewport);
+ let mut layers = Layer::generate(primitives, viewport);
layers.push(Layer::overlay(overlay_text, viewport));
for layer in layers {
@@ -102,8 +103,6 @@ impl Backend {
#[cfg(any(feature = "image_rs", feature = "svg"))]
self.image_pipeline.trim_cache();
-
- *mouse_interaction
}
fn flush(
@@ -120,6 +119,10 @@ impl Backend {
) {
let bounds = (layer.bounds * scale_factor).snap();
+ if bounds.width < 1 || bounds.height < 1 {
+ return;
+ }
+
if !layer.quads.is_empty() {
self.quad_pipeline.draw(
device,
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index e868a655..ed99b126 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -36,9 +36,7 @@ mod backend;
mod quad;
mod text;
-pub use iced_graphics::{
- Antialiasing, Color, Defaults, Error, Primitive, Viewport,
-};
+pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
pub use wgpu;
pub use backend::Backend;
diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs
index a575d036..99ae0ac2 100644
--- a/wgpu/src/widget.rs
+++ b/wgpu/src/widget.rs
@@ -66,13 +66,14 @@ pub mod qr_code;
#[doc(no_inline)]
pub use qr_code::QRCode;
-pub use iced_native::Space;
+pub use iced_native::widget::Space;
/// A container that distributes its contents vertically.
-pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>;
+pub type Column<'a, Message> =
+ iced_native::widget::Column<'a, Message, Renderer>;
/// A container that distributes its contents horizontally.
-pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>;
+pub type Row<'a, Message> = iced_native::widget::Row<'a, Message, Renderer>;
/// A paragraph of text.
-pub type Text = iced_native::Text<Renderer>;
+pub type Text = iced_native::widget::Text<Renderer>;
diff --git a/wgpu/src/widget/button.rs b/wgpu/src/widget/button.rs
index fc729cd5..f11ff25e 100644
--- a/wgpu/src/widget/button.rs
+++ b/wgpu/src/widget/button.rs
@@ -4,9 +4,10 @@
use crate::Renderer;
pub use iced_graphics::button::{Style, StyleSheet};
-pub use iced_native::button::State;
+pub use iced_native::widget::button::State;
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
-pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>;
+pub type Button<'a, Message> =
+ iced_native::widget::Button<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/checkbox.rs b/wgpu/src/widget/checkbox.rs
index d27d77cc..76d572d9 100644
--- a/wgpu/src/widget/checkbox.rs
+++ b/wgpu/src/widget/checkbox.rs
@@ -6,4 +6,5 @@ pub use iced_graphics::checkbox::{Style, StyleSheet};
/// A box that can be checked.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
-pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>;
+pub type Checkbox<'a, Message> =
+ iced_native::widget::Checkbox<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/container.rs b/wgpu/src/widget/container.rs
index bc26cef2..c16db50d 100644
--- a/wgpu/src/widget/container.rs
+++ b/wgpu/src/widget/container.rs
@@ -7,4 +7,5 @@ pub use iced_graphics::container::{Style, StyleSheet};
///
/// This is an alias of an `iced_native` container with a default
/// `Renderer`.
-pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>;
+pub type Container<'a, Message> =
+ iced_native::widget::Container<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs
index fc36862c..3c47acf0 100644
--- a/wgpu/src/widget/pane_grid.rs
+++ b/wgpu/src/widget/pane_grid.rs
@@ -20,12 +20,13 @@ pub use iced_graphics::pane_grid::{
/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
///
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
-pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
+pub type PaneGrid<'a, Message> =
+ iced_native::widget::PaneGrid<'a, Message, Renderer>;
/// The content of a [`Pane`].
pub type Content<'a, Message> =
- iced_native::pane_grid::Content<'a, Message, Renderer>;
+ iced_native::widget::pane_grid::Content<'a, Message, Renderer>;
/// The title bar of a [`Pane`].
pub type TitleBar<'a, Message> =
- iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
+ iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/pick_list.rs b/wgpu/src/widget/pick_list.rs
index fccc68c9..4d93be68 100644
--- a/wgpu/src/widget/pick_list.rs
+++ b/wgpu/src/widget/pick_list.rs
@@ -1,9 +1,9 @@
//! Display a dropdown list of selectable values.
-pub use iced_native::pick_list::State;
+pub use iced_native::widget::pick_list::State;
pub use iced_graphics::overlay::menu::Style as Menu;
pub use iced_graphics::pick_list::{Style, StyleSheet};
/// A widget allowing the selection of a single value from a list of options.
pub type PickList<'a, T, Message> =
- iced_native::PickList<'a, T, Message, crate::Renderer>;
+ iced_native::widget::PickList<'a, T, Message, crate::Renderer>;
diff --git a/wgpu/src/widget/progress_bar.rs b/wgpu/src/widget/progress_bar.rs
index 45a25d00..88391ccb 100644
--- a/wgpu/src/widget/progress_bar.rs
+++ b/wgpu/src/widget/progress_bar.rs
@@ -2,12 +2,4 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-use crate::Renderer;
-
-pub use iced_graphics::progress_bar::{Style, StyleSheet};
-
-/// A bar that displays progress.
-///
-/// This is an alias of an `iced_native` progress bar with an
-/// `iced_wgpu::Renderer`.
-pub type ProgressBar = iced_native::ProgressBar<Renderer>;
+pub use iced_graphics::progress_bar::*;
diff --git a/wgpu/src/widget/radio.rs b/wgpu/src/widget/radio.rs
index 0b843d1f..9ef1d7a5 100644
--- a/wgpu/src/widget/radio.rs
+++ b/wgpu/src/widget/radio.rs
@@ -7,4 +7,4 @@ pub use iced_graphics::radio::{Style, StyleSheet};
///
/// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`.
-pub type Radio<Message> = iced_native::Radio<Message, Renderer>;
+pub type Radio<'a, Message> = iced_native::widget::Radio<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/rule.rs b/wgpu/src/widget/rule.rs
index 3f7bc67a..40281773 100644
--- a/wgpu/src/widget/rule.rs
+++ b/wgpu/src/widget/rule.rs
@@ -1,10 +1,3 @@
//! Display a horizontal or vertical rule for dividing content.
-use crate::Renderer;
-
-pub use iced_graphics::rule::{FillMode, Style, StyleSheet};
-
-/// Display a horizontal or vertical rule for dividing content.
-///
-/// This is an alias of an `iced_native` rule with an `iced_wgpu::Renderer`.
-pub type Rule = iced_native::Rule<Renderer>;
+pub use iced_graphics::rule::*;
diff --git a/wgpu/src/widget/scrollable.rs b/wgpu/src/widget/scrollable.rs
index fabb4318..d5635ec5 100644
--- a/wgpu/src/widget/scrollable.rs
+++ b/wgpu/src/widget/scrollable.rs
@@ -2,7 +2,7 @@
use crate::Renderer;
pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet};
-pub use iced_native::scrollable::State;
+pub use iced_native::widget::scrollable::State;
/// A widget that can vertically display an infinite amount of content
/// with a scrollbar.
@@ -10,4 +10,4 @@ pub use iced_native::scrollable::State;
/// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`.
pub type Scrollable<'a, Message> =
- iced_native::Scrollable<'a, Message, Renderer>;
+ iced_native::widget::Scrollable<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/slider.rs b/wgpu/src/widget/slider.rs
index 9a269858..2fb3d5d9 100644
--- a/wgpu/src/widget/slider.rs
+++ b/wgpu/src/widget/slider.rs
@@ -1,13 +1,5 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-use crate::Renderer;
-
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
-pub use iced_native::slider::State;
-
-/// An horizontal bar and a handle that selects a single value from a range of
-/// values.
-///
-/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
-pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
+pub use iced_native::widget::slider::{Slider, State};
diff --git a/wgpu/src/widget/text_input.rs b/wgpu/src/widget/text_input.rs
index db18b1cc..5560e3e0 100644
--- a/wgpu/src/widget/text_input.rs
+++ b/wgpu/src/widget/text_input.rs
@@ -4,9 +4,10 @@
use crate::Renderer;
pub use iced_graphics::text_input::{Style, StyleSheet};
-pub use iced_native::text_input::State;
+pub use iced_native::widget::text_input::State;
/// A field that can be filled with text.
///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
-pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>;
+pub type TextInput<'a, Message> =
+ iced_native::widget::TextInput<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/toggler.rs b/wgpu/src/widget/toggler.rs
index dfcf759b..7ef5e22e 100644
--- a/wgpu/src/widget/toggler.rs
+++ b/wgpu/src/widget/toggler.rs
@@ -6,4 +6,5 @@ pub use iced_graphics::toggler::{Style, StyleSheet};
/// A toggler that can be toggled
///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
-pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
+pub type Toggler<'a, Message> =
+ iced_native::widget::Toggler<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs
index 89ab3a15..c6af3903 100644
--- a/wgpu/src/widget/tooltip.rs
+++ b/wgpu/src/widget/tooltip.rs
@@ -1,6 +1,6 @@
//! Display a widget over another.
/// A widget allowing the selection of a single value from a list of options.
pub type Tooltip<'a, Message> =
- iced_native::Tooltip<'a, Message, crate::Renderer>;
+ iced_native::widget::Tooltip<'a, Message, crate::Renderer>;
-pub use iced_native::tooltip::Position;
+pub use iced_native::widget::tooltip::Position;
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index bd8e1f89..3b264475 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -1,7 +1,7 @@
use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
use futures::task::SpawnExt;
-use iced_native::{futures, mouse};
+use iced_native::futures;
use raw_window_handle::HasRawWindowHandle;
/// A window graphics backend for iced powered by `wgpu`.
@@ -133,15 +133,14 @@ impl iced_graphics::window::Compositor for Compositor {
);
}
- fn draw<T: AsRef<str>>(
+ fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
- output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
- ) -> Result<mouse::Interaction, iced_graphics::window::SurfaceError> {
+ ) -> Result<(), iced_graphics::window::SurfaceError> {
match surface.get_current_texture() {
Ok(frame) => {
let mut encoder = self.device.create_command_encoder(
@@ -180,15 +179,17 @@ impl iced_graphics::window::Compositor for Compositor {
depth_stencil_attachment: None,
});
- let mouse_interaction = renderer.backend_mut().draw(
- &mut self.device,
- &mut self.staging_belt,
- &mut encoder,
- view,
- viewport,
- output,
- overlay,
- );
+ renderer.with_primitives(|backend, primitives| {
+ backend.present(
+ &mut self.device,
+ &mut self.staging_belt,
+ &mut encoder,
+ view,
+ primitives,
+ viewport,
+ overlay,
+ );
+ });
// Submit work
self.staging_belt.finish();
@@ -203,7 +204,7 @@ impl iced_graphics::window::Compositor for Compositor {
self.local_pool.run_until_stalled();
- Ok(mouse_interaction)
+ Ok(())
}
Err(error) => match error {
wgpu::SurfaceError::Timeout => {
diff --git a/winit/src/application.rs b/winit/src/application.rs
index ab9a70a0..9d787933 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -253,10 +253,7 @@ async fn run_instance<A, E, C>(
&mut debug,
));
- let mut primitive =
- user_interface.draw(&mut renderer, state.cursor_position());
let mut mouse_interaction = mouse::Interaction::default();
-
let mut events = Vec::new();
let mut messages = Vec::new();
@@ -319,10 +316,18 @@ async fn run_instance<A, E, C>(
}
debug.draw_started();
- primitive =
+ let new_mouse_interaction =
user_interface.draw(&mut renderer, state.cursor_position());
debug.draw_finished();
+ if new_mouse_interaction != mouse_interaction {
+ window.set_cursor_icon(conversion::mouse_interaction(
+ new_mouse_interaction,
+ ));
+
+ mouse_interaction = new_mouse_interaction;
+ }
+
window.request_redraw();
}
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
@@ -359,8 +364,16 @@ async fn run_instance<A, E, C>(
debug.layout_finished();
debug.draw_started();
- primitive = user_interface
+ let new_mouse_interaction = user_interface
.draw(&mut renderer, state.cursor_position());
+
+ if new_mouse_interaction != mouse_interaction {
+ window.set_cursor_icon(conversion::mouse_interaction(
+ new_mouse_interaction,
+ ));
+
+ mouse_interaction = new_mouse_interaction;
+ }
debug.draw_finished();
compositor.configure_surface(
@@ -372,27 +385,16 @@ async fn run_instance<A, E, C>(
viewport_version = current_viewport_version;
}
- match compositor.draw(
+ match compositor.present(
&mut renderer,
&mut surface,
state.viewport(),
state.background_color(),
- &primitive,
&debug.overlay(),
) {
- Ok(new_mouse_interaction) => {
+ Ok(()) => {
debug.render_finished();
- if new_mouse_interaction != mouse_interaction {
- window.set_cursor_icon(
- conversion::mouse_interaction(
- new_mouse_interaction,
- ),
- );
-
- mouse_interaction = new_mouse_interaction;
- }
-
// TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this.
}