Merge pull request #1110 from iced-rs/remove-renderer-traits

Reduce the surface of the `Renderer` APIs
This commit is contained in:
Héctor Ramón 2021-11-07 15:15:33 +07:00 committed by GitHub
commit eafad00af2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
142 changed files with 3344 additions and 4588 deletions

View file

@ -17,7 +17,6 @@
pub mod alignment; pub mod alignment;
pub mod keyboard; pub mod keyboard;
pub mod mouse; pub mod mouse;
pub mod text;
mod background; mod background;
mod color; mod color;

View file

@ -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
}
}
}
}
}

View file

@ -8,4 +8,3 @@ publish = false
[dependencies] [dependencies]
iced = { path = "../.." } iced = { path = "../.." }
iced_native = { path = "../../native" } iced_native = { path = "../../native" }
iced_graphics = { path = "../../graphics" }

View file

@ -9,10 +9,10 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic, // Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be // if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers. // 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::{ use iced_native::{
layout, mouse, Background, Color, Element, Hasher, Layout, Length, Color, Element, Hasher, Length, Point, Rectangle, Size, Widget,
Point, Rectangle, Size, Widget,
}; };
pub struct Circle { 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 where
B: Backend, Renderer: renderer::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
Length::Shrink Length::Shrink
@ -39,7 +39,7 @@ mod circle {
fn layout( fn layout(
&self, &self,
_renderer: &Renderer<B>, _renderer: &Renderer,
_limits: &layout::Limits, _limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0)) layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
@ -53,30 +53,29 @@ mod circle {
fn draw( fn draw(
&self, &self,
_renderer: &mut Renderer<B>, renderer: &mut Renderer,
_defaults: &Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) { ) {
( renderer.fill_quad(
Primitive::Quad { renderer::Quad {
bounds: layout.bounds(), bounds: layout.bounds(),
background: Background::Color(Color::BLACK),
border_radius: self.radius, border_radius: self.radius,
border_width: 0.0, border_width: 0.0,
border_color: Color::TRANSPARENT, 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 where
B: Backend, Renderer: renderer::Renderer,
{ {
fn into(self) -> Element<'a, Message, Renderer<B>> { fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self) Element::new(self)
} }
} }

View file

@ -10,12 +10,11 @@ mod rainbow {
// Of course, you can choose to make the implementation renderer-agnostic, // Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be // if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers. // implemented by `iced_wgpu` and other renderers.
use iced_graphics::{ use iced_graphics::renderer::{self, Renderer};
triangle::{Mesh2D, Vertex2D}, use iced_graphics::{Backend, Primitive};
Backend, Defaults, Primitive, Renderer,
};
use iced_native::{ use iced_native::{
layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size, layout, Element, Hasher, Layout, Length, Point, Rectangle, Size,
Vector, Widget, Vector, Widget,
}; };
@ -53,12 +52,15 @@ mod rainbow {
fn draw( fn draw(
&self, &self,
_renderer: &mut Renderer<B>, renderer: &mut Renderer<B>,
_defaults: &Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) { ) {
use iced_graphics::triangle::{Mesh2D, Vertex2D};
use iced_native::Renderer as _;
let b = layout.bounds(); let b = layout.bounds();
// R O Y G B I V // R O Y G B I V
@ -88,10 +90,7 @@ mod rainbow {
let posn_bl = [0.0, b.height]; let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0]; let posn_l = [0.0, b.height / 2.0];
( let mesh = Primitive::Mesh2D {
Primitive::Translate {
translation: Vector::new(b.x, b.y),
content: Box::new(Primitive::Mesh2D {
size: b.size(), size: b.size(),
buffers: Mesh2D { buffers: Mesh2D {
vertices: vec![ vertices: vec![
@ -143,10 +142,11 @@ mod rainbow {
0, 8, 1, // L 0, 8, 1, // L
], ],
}, },
}), };
},
mouse::Interaction::default(), renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
) renderer.draw_primitive(mesh);
});
} }
} }

View file

@ -1,9 +1,7 @@
use iced_glow::Renderer; use iced_glow::Renderer;
use iced_glutin::slider; use iced_glutin::widget::slider::{self, Slider};
use iced_glutin::{ use iced_glutin::widget::{Column, Row, Text};
Alignment, Color, Column, Command, Element, Length, Program, Row, Slider, use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
Text,
};
pub struct Controls { pub struct Controls {
background_color: Color, background_color: Color,

View file

@ -68,7 +68,6 @@ pub fn main() {
let mut state = program::State::new( let mut state = program::State::new(
controls, controls,
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
); );
@ -160,16 +159,19 @@ pub fn main() {
} }
// And then iced on top // And then iced on top
let mouse_interaction = renderer.backend_mut().draw( renderer.with_primitives(|backend, primitive| {
backend.present(
&gl, &gl,
primitive,
&viewport, &viewport,
state.primitive(),
&debug.overlay(), &debug.overlay(),
); );
});
// Update the mouse cursor // Update the mouse cursor
windowed_context.window().set_cursor_icon( windowed_context.window().set_cursor_icon(
iced_winit::conversion::mouse_interaction( iced_winit::conversion::mouse_interaction(
mouse_interaction, state.mouse_interaction(),
), ),
); );

View file

@ -1,8 +1,7 @@
use iced_wgpu::Renderer; use iced_wgpu::Renderer;
use iced_winit::{ use iced_winit::widget::slider::{self, Slider};
slider, Alignment, Color, Column, Command, Element, Length, Program, Row, use iced_winit::widget::{Column, Row, Text};
Slider, Text, use iced_winit::{Alignment, Color, Command, Element, Length, Program};
};
pub struct Controls { pub struct Controls {
background_color: Color, background_color: Color,

View file

@ -94,7 +94,6 @@ pub fn main() {
let mut state = program::State::new( let mut state = program::State::new(
controls, controls,
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
); );
@ -196,15 +195,17 @@ pub fn main() {
} }
// And then iced on top // And then iced on top
let mouse_interaction = renderer.backend_mut().draw( renderer.with_primitives(|backend, primitive| {
backend.present(
&mut device, &mut device,
&mut staging_belt, &mut staging_belt,
&mut encoder, &mut encoder,
&view, &view,
primitive,
&viewport, &viewport,
state.primitive(),
&debug.overlay(), &debug.overlay(),
); );
});
// Then we submit the work // Then we submit the work
staging_belt.finish(); staging_belt.finish();
@ -214,7 +215,7 @@ pub fn main() {
// Update the mouse cursor // Update the mouse cursor
window.set_cursor_icon( window.set_cursor_icon(
iced_winit::conversion::mouse_interaction( iced_winit::conversion::mouse_interaction(
mouse_interaction, state.mouse_interaction(),
), ),
); );

View file

@ -177,7 +177,11 @@ impl Application for Example {
let title_bar = pane_grid::TitleBar::new(title) let title_bar = pane_grid::TitleBar::new(title)
.controls(pane.controls.view(id, total_panes, pane.is_pinned)) .controls(pane.controls.view(id, total_panes, pane.is_pinned))
.padding(10) .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( pane_grid::Content::new(pane.content.view(
id, id,
@ -185,7 +189,11 @@ impl Application for Example {
pane.is_pinned, pane.is_pinned,
)) ))
.title_bar(title_bar) .title_bar(title_bar)
.style(style::Pane { is_focused }) .style(if is_focused {
style::Pane::Focused
} else {
style::Pane::Active
})
}) })
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
@ -387,14 +395,16 @@ mod style {
0xC4 as f32 / 255.0, 0xC4 as f32 / 255.0,
); );
pub struct TitleBar { pub enum TitleBar {
pub is_focused: bool, Active,
Focused,
} }
impl container::StyleSheet for TitleBar { impl container::StyleSheet for TitleBar {
fn style(&self) -> container::Style { fn style(&self) -> container::Style {
let pane = Pane { let pane = match self {
is_focused: self.is_focused, Self::Active => Pane::Active,
Self::Focused => Pane::Focused,
} }
.style(); .style();
@ -406,8 +416,9 @@ mod style {
} }
} }
pub struct Pane { pub enum Pane {
pub is_focused: bool, Active,
Focused,
} }
impl container::StyleSheet for Pane { impl container::StyleSheet for Pane {
@ -415,10 +426,9 @@ mod style {
container::Style { container::Style {
background: Some(Background::Color(SURFACE)), background: Some(Background::Color(SURFACE)),
border_width: 2.0, border_width: 2.0,
border_color: if self.is_focused { border_color: match self {
Color::BLACK Self::Active => Color::from_rgb(0.7, 0.7, 0.7),
} else { Self::Focused => Color::BLACK,
Color::from_rgb(0.7, 0.7, 0.7)
}, },
..Default::default() ..Default::default()
} }

View file

@ -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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), Theme::Light => Default::default(),

View file

@ -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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => light::Button.into(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), 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 { fn from(theme: Theme) -> Self {
match theme { match theme {
Theme::Light => Default::default(), Theme::Light => Default::default(),

View file

@ -363,8 +363,10 @@ impl Controls {
let filter_button = |state, label, filter, current_filter| { let filter_button = |state, label, filter, current_filter| {
let label = Text::new(label).size(16); let label = Text::new(label).size(16);
let button = let button =
Button::new(state, label).style(style::Button::Filter { Button::new(state, label).style(if filter == current_filter {
selected: filter == current_filter, style::Button::FilterSelected
} else {
style::Button::FilterActive
}); });
button.on_press(Message::FilterChanged(filter)).padding(8) button.on_press(Message::FilterChanged(filter)).padding(8)
@ -602,7 +604,8 @@ mod style {
use iced::{button, Background, Color, Vector}; use iced::{button, Background, Color, Vector};
pub enum Button { pub enum Button {
Filter { selected: bool }, FilterActive,
FilterSelected,
Icon, Icon,
Destructive, Destructive,
} }
@ -610,20 +613,15 @@ mod style {
impl button::StyleSheet for Button { impl button::StyleSheet for Button {
fn active(&self) -> button::Style { fn active(&self) -> button::Style {
match self { match self {
Button::Filter { selected } => { Button::FilterActive => button::Style::default(),
if *selected { Button::FilterSelected => button::Style {
button::Style { background: Some(Background::Color(Color::from_rgb(
background: Some(Background::Color( 0.2, 0.2, 0.7,
Color::from_rgb(0.2, 0.2, 0.7), ))),
)),
border_radius: 10.0, border_radius: 10.0,
text_color: Color::WHITE, text_color: Color::WHITE,
..button::Style::default() ..button::Style::default()
} },
} else {
button::Style::default()
}
}
Button::Icon => button::Style { Button::Icon => button::Style {
text_color: Color::from_rgb(0.5, 0.5, 0.5), text_color: Color::from_rgb(0.5, 0.5, 0.5),
..button::Style::default() ..button::Style::default()
@ -646,9 +644,7 @@ mod style {
button::Style { button::Style {
text_color: match self { text_color: match self {
Button::Icon => Color::from_rgb(0.2, 0.2, 0.7), Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
Button::Filter { selected } if !selected => { Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7),
Color::from_rgb(0.2, 0.2, 0.7)
}
_ => active.text_color, _ => active.text_color,
}, },
shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),

View file

@ -5,10 +5,8 @@ use crate::{Settings, Transformation, Viewport};
use iced_graphics::backend; use iced_graphics::backend;
use iced_graphics::font; use iced_graphics::font;
use iced_graphics::Layer; use iced_graphics::{Layer, Primitive};
use iced_graphics::Primitive;
use iced_native::alignment; use iced_native::alignment;
use iced_native::mouse;
use iced_native::{Font, Size}; use iced_native::{Font, Size};
/// A [`glow`] graphics backend for [`iced`]. /// 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. /// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information. /// This is useful for rendering debug information.
pub fn draw<T: AsRef<str>>( pub fn present<T: AsRef<str>>(
&mut self, &mut self,
gl: &glow::Context, gl: &glow::Context,
primitives: &[Primitive],
viewport: &Viewport, viewport: &Viewport,
(primitive, mouse_interaction): &(Primitive, mouse::Interaction),
overlay_text: &[T], overlay_text: &[T],
) -> mouse::Interaction { ) {
let viewport_size = viewport.physical_size(); let viewport_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor() as f32;
let projection = viewport.projection(); 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)); layers.push(Layer::overlay(overlay_text, viewport));
for layer in layers { for layer in layers {
@ -70,8 +68,6 @@ impl Backend {
viewport_size.height, viewport_size.height,
); );
} }
*mouse_interaction
} }
fn flush( fn flush(
@ -83,6 +79,11 @@ impl Backend {
target_height: u32, target_height: u32,
) { ) {
let mut bounds = (layer.bounds * scale_factor).snap(); let mut bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
return;
}
bounds.height = bounds.height.min(target_height); bounds.height = bounds.height.min(target_height);
if !layer.quads.is_empty() { if !layer.quads.is_empty() {

View file

@ -4,14 +4,14 @@
//! //!
//! [`glow`]: https://github.com/grovesNL/glow //! [`glow`]: https://github.com/grovesNL/glow
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
//#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![deny(unused_results)] #![deny(unused_results)]
#![forbid(rust_2018_idioms)] #![forbid(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
mod backend; mod backend;
pub mod program; mod program;
mod quad; mod quad;
mod text; mod text;
mod triangle; mod triangle;

View file

@ -66,13 +66,14 @@ pub mod qr_code;
#[doc(no_inline)] #[doc(no_inline)]
pub use qr_code::QRCode; 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. /// 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. /// 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. /// A paragraph of text.
pub type Text = iced_native::Text<Renderer>; pub type Text = iced_native::widget::Text<Renderer>;

View file

@ -4,9 +4,10 @@
use crate::Renderer; use crate::Renderer;
pub use iced_graphics::button::{Style, StyleSheet}; 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. /// A widget that produces a message when clicked.
/// ///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. /// 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>;

View file

@ -6,4 +6,5 @@ pub use iced_graphics::checkbox::{Style, StyleSheet};
/// A box that can be checked. /// A box that can be checked.
/// ///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. /// 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>;

View file

@ -7,4 +7,5 @@ pub use iced_graphics::container::{Style, StyleSheet};
/// ///
/// This is an alias of an `iced_native` container with a default /// This is an alias of an `iced_native` container with a default
/// `Renderer`. /// `Renderer`.
pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>; pub type Container<'a, Message> =
iced_native::widget::Container<'a, Message, Renderer>;

View file

@ -20,12 +20,13 @@ pub use iced_graphics::pane_grid::{
/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) /// [![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`. /// 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`]. /// The content of a [`Pane`].
pub type Content<'a, Message> = 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`]. /// The title bar of a [`Pane`].
pub type TitleBar<'a, Message> = pub type TitleBar<'a, Message> =
iced_native::pane_grid::TitleBar<'a, Message, Renderer>; iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>;

View file

@ -1,9 +1,9 @@
//! Display a dropdown list of selectable values. //! 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::overlay::menu::Style as Menu;
pub use iced_graphics::pick_list::{Style, StyleSheet}; pub use iced_graphics::pick_list::{Style, StyleSheet};
/// A widget allowing the selection of a single value from a list of options. /// A widget allowing the selection of a single value from a list of options.
pub type PickList<'a, T, Message> = pub type PickList<'a, T, Message> =
iced_native::PickList<'a, T, Message, crate::Renderer>; iced_native::widget::PickList<'a, T, Message, crate::Renderer>;

View file

@ -2,12 +2,5 @@
//! //!
//! A [`ProgressBar`] has a range of possible values and a current value, //! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style. //! as well as a length, height and style.
use crate::Renderer;
pub use iced_graphics::progress_bar::{Style, StyleSheet}; pub use iced_graphics::progress_bar::*;
/// 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>;

View file

@ -7,4 +7,4 @@ pub use iced_graphics::radio::{Style, StyleSheet};
/// ///
/// This is an alias of an `iced_native` radio button with an /// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`. /// `iced_wgpu::Renderer`.
pub type Radio<Message> = iced_native::Radio<Message, Renderer>; pub type Radio<'a, Message> = iced_native::widget::Radio<'a, Message, Renderer>;

View file

@ -1,10 +1,3 @@
//! Display a horizontal or vertical rule for dividing content. //! Display a horizontal or vertical rule for dividing content.
use crate::Renderer; pub use iced_graphics::rule::*;
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>;

View file

@ -2,7 +2,7 @@
use crate::Renderer; use crate::Renderer;
pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet}; 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 /// A widget that can vertically display an infinite amount of content
/// with a scrollbar. /// 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 /// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`. /// `Renderer`.
pub type Scrollable<'a, Message> = pub type Scrollable<'a, Message> =
iced_native::Scrollable<'a, Message, Renderer>; iced_native::widget::Scrollable<'a, Message, Renderer>;

View file

@ -1,13 +1,5 @@
//! Display an interactive selector of a single value from a range of values. //! Display an interactive selector of a single value from a range of values.
//! //!
//! A [`Slider`] has some local [`State`]. //! A [`Slider`] has some local [`State`].
use crate::Renderer;
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet}; pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
pub use iced_native::slider::State; pub use iced_native::widget::slider::{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>;

View file

@ -4,9 +4,10 @@
use crate::Renderer; use crate::Renderer;
pub use iced_graphics::text_input::{Style, StyleSheet}; 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. /// A field that can be filled with text.
/// ///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. /// 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>;

View file

@ -6,4 +6,5 @@ pub use iced_graphics::toggler::{Style, StyleSheet};
/// A toggler that can be toggled. /// A toggler that can be toggled.
/// ///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. /// 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>;

View file

@ -1,6 +1,6 @@
//! Display a widget over another. //! Display a widget over another.
/// A widget allowing the selection of a single value from a list of options. /// A widget allowing the selection of a single value from a list of options.
pub type Tooltip<'a, Message> = 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;

View file

@ -3,7 +3,6 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
use core::ffi::c_void; use core::ffi::c_void;
use glow::HasContext; use glow::HasContext;
use iced_graphics::{Antialiasing, Size}; use iced_graphics::{Antialiasing, Size};
use iced_native::mouse;
/// A window graphics backend for iced powered by `glow`. /// A window graphics backend for iced powered by `glow`.
#[allow(missing_debug_implementations)] #[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, &mut self,
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
viewport: &Viewport, viewport: &Viewport,
color: Color, color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> mouse::Interaction { ) {
let gl = &self.gl; let gl = &self.gl;
let [r, g, b, a] = color.into_linear(); 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); 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);
});
} }
} }

View file

@ -1,5 +1,6 @@
//! Create interactive, native cross-platform applications. //! 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; pub use iced_winit::Application;
@ -179,10 +180,7 @@ async fn run_instance<A, E, C>(
&mut debug, &mut debug,
)); ));
let mut primitive =
user_interface.draw(&mut renderer, state.cursor_position());
let mut mouse_interaction = mouse::Interaction::default(); let mut mouse_interaction = mouse::Interaction::default();
let mut events = Vec::new(); let mut events = Vec::new();
let mut messages = Vec::new(); let mut messages = Vec::new();
@ -246,10 +244,18 @@ async fn run_instance<A, E, C>(
} }
debug.draw_started(); debug.draw_started();
primitive = let new_mouse_interaction =
user_interface.draw(&mut renderer, state.cursor_position()); user_interface.draw(&mut renderer, state.cursor_position());
debug.draw_finished(); 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(); context.window().request_redraw();
} }
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
@ -291,10 +297,20 @@ async fn run_instance<A, E, C>(
debug.layout_finished(); debug.layout_finished();
debug.draw_started(); debug.draw_started();
primitive = user_interface let new_mouse_interaction = user_interface
.draw(&mut renderer, state.cursor_position()); .draw(&mut renderer, state.cursor_position());
debug.draw_finished(); 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( context.resize(glutin::dpi::PhysicalSize::new(
physical_size.width, physical_size.width,
physical_size.height, physical_size.height,
@ -305,11 +321,10 @@ async fn run_instance<A, E, C>(
viewport_version = current_viewport_version; viewport_version = current_viewport_version;
} }
let new_mouse_interaction = compositor.draw( compositor.present(
&mut renderer, &mut renderer,
state.viewport(), state.viewport(),
state.background_color(), state.background_color(),
&primitive,
&debug.overlay(), &debug.overlay(),
); );
@ -317,14 +332,6 @@ async fn run_instance<A, E, C>(
debug.render_finished(); 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! // TODO: Handle animations!
// Maybe we can use `ControlFlow::WaitUntil` for this. // Maybe we can use `ControlFlow::WaitUntil` for this.
} }

View file

@ -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,
}
}
}

View file

@ -74,7 +74,7 @@ impl<'a> Layer<'a> {
/// Distributes the given [`Primitive`] and generates a list of layers based /// Distributes the given [`Primitive`] and generates a list of layers based
/// on its contents. /// on its contents.
pub fn generate( pub fn generate(
primitive: &'a Primitive, primitives: &'a [Primitive],
viewport: &Viewport, viewport: &Viewport,
) -> Vec<Self> { ) -> Vec<Self> {
let first_layer = let first_layer =
@ -82,12 +82,14 @@ impl<'a> Layer<'a> {
let mut layers = vec![first_layer]; let mut layers = vec![first_layer];
for primitive in primitives {
Self::process_primitive( Self::process_primitive(
&mut layers, &mut layers,
Vector::new(0.0, 0.0), Vector::new(0.0, 0.0),
primitive, primitive,
0, 0,
); );
}
layers layers
} }
@ -173,11 +175,7 @@ impl<'a> Layer<'a> {
}); });
} }
} }
Primitive::Clip { Primitive::Clip { bounds, content } => {
bounds,
offset,
content,
} => {
let layer = &mut layers[current_layer]; let layer = &mut layers[current_layer];
let translated_bounds = *bounds + translation; let translated_bounds = *bounds + translation;
@ -190,8 +188,7 @@ impl<'a> Layer<'a> {
Self::process_primitive( Self::process_primitive(
layers, layers,
translation translation,
- Vector::new(offset.x as f32, offset.y as f32),
content, content,
layers.len() - 1, layers.len() - 1,
); );

View file

@ -13,15 +13,14 @@
mod antialiasing; mod antialiasing;
mod error; mod error;
mod primitive; mod primitive;
mod renderer;
mod transformation; mod transformation;
mod viewport; mod viewport;
pub mod backend; pub mod backend;
pub mod defaults;
pub mod font; pub mod font;
pub mod layer; pub mod layer;
pub mod overlay; pub mod overlay;
pub mod renderer;
pub mod triangle; pub mod triangle;
pub mod widget; pub mod widget;
pub mod window; pub mod window;
@ -31,7 +30,6 @@ pub use widget::*;
pub use antialiasing::Antialiasing; pub use antialiasing::Antialiasing;
pub use backend::Backend; pub use backend::Backend;
pub use defaults::Defaults;
pub use error::Error; pub use error::Error;
pub use layer::Layer; pub use layer::Layer;
pub use primitive::Primitive; pub use primitive::Primitive;

View file

@ -1,116 +1,3 @@
//! Build and show dropdown menus. //! 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; 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()
},
)
}
}

View file

@ -1,6 +1,6 @@
use iced_native::{ use iced_native::image;
image, svg, Background, Color, Font, Rectangle, Size, Vector, use iced_native::svg;
}; use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
use crate::alignment; use crate::alignment;
use crate::triangle; use crate::triangle;
@ -66,8 +66,6 @@ pub enum Primitive {
Clip { Clip {
/// The bounds of the clip /// The bounds of the clip
bounds: Rectangle, bounds: Rectangle,
/// The offset transformation of the clip
offset: Vector<u32>,
/// The content of the clip /// The content of the clip
content: Box<Primitive>, content: Box<Primitive>,
}, },

View file

@ -1,30 +1,43 @@
use crate::{Backend, Defaults, Primitive}; //! Create a renderer from a [`Backend`].
use iced_native::layout::{self, Layout}; use crate::backend::{self, Backend};
use iced_native::mouse; use crate::{Primitive, Vector};
use iced_native::{ use iced_native::layout;
Background, Color, Element, Point, Rectangle, Vector, Widget, 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. /// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)] #[derive(Debug)]
pub struct Renderer<B: Backend> { pub struct Renderer<B: Backend> {
backend: B, backend: B,
primitives: Vec<Primitive>,
} }
impl<B: Backend> Renderer<B> { impl<B: Backend> Renderer<B> {
/// Creates a new [`Renderer`] from the given [`Backend`]. /// Creates a new [`Renderer`] from the given [`Backend`].
pub fn new(backend: B) -> Self { 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 { pub fn backend(&self) -> &B {
&self.backend &self.backend
} }
/// Returns a mutable reference to the [`Backend`] of the [`Renderer`]. /// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
pub fn backend_mut(&mut self) -> &mut B { pub fn draw_primitive(&mut self, primitive: Primitive) {
&mut self.backend 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 where
B: Backend, B: Backend,
{ {
type Output = (Primitive, mouse::Interaction);
type Defaults = Defaults;
fn layout<'a, Message>( fn layout<'a, Message>(
&mut self, &mut self,
element: &Element<'a, Message, Self>, element: &Element<'a, Message, Self>,
@ -47,75 +57,114 @@ where
layout 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, &mut self,
(base_primitive, base_cursor): (Primitive, mouse::Interaction), translation: Vector,
(overlay_primitives, overlay_cursor): (Primitive, mouse::Interaction), f: impl FnOnce(&mut Self),
overlay_bounds: Rectangle, ) {
) -> (Primitive, mouse::Interaction) { let current_primitives = std::mem::take(&mut self.primitives);
(
Primitive::Group { f(self);
primitives: vec![
base_primitive, let layer_primitives =
Primitive::Clip { std::mem::replace(&mut self.primitives, current_primitives);
bounds: Rectangle {
width: overlay_bounds.width + 0.5, self.primitives.push(Primitive::Translate {
height: overlay_bounds.height + 0.5, translation,
..overlay_bounds content: Box::new(Primitive::Group {
}, primitives: layer_primitives,
offset: Vector::new(0, 0), }),
content: Box::new(overlay_primitives), });
}, }
],
}, fn fill_quad(
if base_cursor > overlay_cursor { &mut self,
base_cursor quad: renderer::Quad,
} else { background: impl Into<Background>,
overlay_cursor ) {
}, 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> text::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
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,
) )
} }
}
impl<B> layout::Debugger for Renderer<B> fn fill_text(&mut self, text: Text<'_, Self::Font>) {
where self.primitives.push(Primitive::Text {
B: Backend, content: text.content.to_string(),
{ bounds: text.bounds,
fn explain<Message>( size: text.size,
&mut self, color: text.color,
defaults: &Defaults, font: text.font,
widget: &dyn Widget<Message, Self>, horizontal_alignment: text.horizontal_alignment,
layout: Layout<'_>, vertical_alignment: text.vertical_alignment,
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)
}
}
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: color,
}); });
for child in layout.children() {
explain_layout(child, color, primitives);
} }
} }

View file

@ -1,111 +1,12 @@
//! Allow your users to perform actions by pressing a button. //! Allow your users to perform actions by pressing a button.
//! //!
//! A [`Button`] has some local [`State`]. //! A [`Button`] has some local [`State`].
use crate::defaults::{self, Defaults}; use crate::Renderer;
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::{
Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
};
pub use iced_native::button::State; pub use iced_native::widget::button::{State, Style, StyleSheet};
pub use iced_style::button::{Style, StyleSheet};
/// A widget that produces a message when clicked. /// A widget that produces a message when clicked.
/// ///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. /// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
pub type Button<'a, Message, Backend> = pub type Button<'a, Message, Backend> =
iced_native::Button<'a, Message, Renderer<Backend>>; iced_native::widget::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()
},
)
}
}

View file

@ -3,7 +3,9 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a //! 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, //! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more! //! and more!
use crate::{Backend, Defaults, Primitive, Renderer}; use crate::renderer::{self, Renderer};
use crate::{Backend, Primitive};
use iced_native::layout; use iced_native::layout;
use iced_native::mouse; use iced_native::mouse;
use iced_native::{ use iced_native::{
@ -186,32 +188,42 @@ where
event::Status::Ignored event::Status::Ignored
} }
fn draw( fn mouse_interaction(
&self, &self,
_renderer: &mut Renderer<B>,
_defaults: &Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) { ) -> 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>,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
use iced_native::Renderer as _;
let bounds = layout.bounds(); let bounds = layout.bounds();
let translation = Vector::new(bounds.x, bounds.y); let translation = Vector::new(bounds.x, bounds.y);
let cursor = Cursor::from_window_position(cursor_position); let cursor = Cursor::from_window_position(cursor_position);
( renderer.with_translation(translation, |renderer| {
Primitive::Translate { renderer.draw_primitive(Primitive::Group {
translation,
content: Box::new(Primitive::Group {
primitives: self primitives: self
.program .program
.draw(bounds, cursor) .draw(bounds, cursor)
.into_iter() .into_iter()
.map(Geometry::into_primitive) .map(Geometry::into_primitive)
.collect(), .collect(),
}), });
}, });
self.program.mouse_interaction(bounds, cursor),
)
} }
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {

View file

@ -1,77 +1,10 @@
//! Show toggle controls using checkboxes. //! Show toggle controls using checkboxes.
use crate::alignment; use crate::Renderer;
use crate::backend::{self, Backend};
use crate::{Primitive, Rectangle, Renderer};
use iced_native::checkbox;
use iced_native::mouse;
pub use iced_style::checkbox::{Style, StyleSheet}; pub use iced_style::checkbox::{Style, StyleSheet};
/// A box that can be checked. /// A box that can be checked.
/// ///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. /// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
pub type Checkbox<Message, Backend> = pub type Checkbox<'a, Message, Backend> =
iced_native::Checkbox<Message, Renderer<Backend>>; iced_native::widget::Checkbox<'a, 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()
},
)
}
}

View file

@ -1,49 +1,5 @@
use crate::{Backend, Primitive, Renderer}; use crate::Renderer;
use iced_native::column;
use iced_native::mouse;
use iced_native::{Element, Layout, Point, Rectangle};
/// A container that distributes its contents vertically. /// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> = pub type Column<'a, Message, Backend> =
iced_native::Column<'a, Message, Renderer<Backend>>; iced_native::widget::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,
)
}
}

View file

@ -1,8 +1,5 @@
//! Decorate content and apply alignment. //! Decorate content and apply alignment.
use crate::container; use crate::Renderer;
use crate::defaults::{self, Defaults};
use crate::{Backend, Primitive, Renderer};
use iced_native::{Background, Color, Element, Layout, Point, Rectangle};
pub use iced_style::container::{Style, StyleSheet}; 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 /// This is an alias of an `iced_native` container with a default
/// `Renderer`. /// `Renderer`.
pub type Container<'a, Message, Backend> = pub type Container<'a, Message, Backend> =
iced_native::Container<'a, Message, Renderer<Backend>>; iced_native::widget::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
}
}

View file

@ -2,13 +2,12 @@
pub mod viewer; pub mod viewer;
use crate::backend::{self, Backend}; use crate::backend::{self, Backend};
use crate::{Primitive, Rectangle, Renderer};
use crate::{Primitive, Renderer};
use iced_native::image; 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> impl<B> image::Renderer for Renderer<B>
where where
@ -18,17 +17,7 @@ where
self.backend().dimensions(handle) self.backend().dimensions(handle)
} }
fn draw( fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
&mut self, self.draw_primitive(Primitive::Image { handle, bounds })
handle: image::Handle,
layout: Layout<'_>,
) -> Self::Output {
(
Primitive::Image {
handle,
bounds: layout.bounds(),
},
mouse::Interaction::default(),
)
} }
} }

View file

@ -1,55 +1,2 @@
//! Zoom and pan on an image. //! Zoom and pan on an image.
use crate::backend::{self, Backend}; pub use iced_native::widget::image::Viewer;
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
}
},
)
}
}

View file

@ -7,14 +7,9 @@
//! drag and drop, and hotkey support. //! drag and drop, and hotkey support.
//! //!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::defaults; use crate::Renderer;
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};
pub use iced_native::pane_grid::{ pub use iced_native::widget::pane_grid::{
Axis, Configuration, Content, Direction, DragEvent, Node, Pane, Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
ResizeEvent, Split, State, TitleBar, 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`. /// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
pub type PaneGrid<'a, Message, Backend> = pub type PaneGrid<'a, Message, Backend> =
iced_native::PaneGrid<'a, Message, Renderer<Backend>>; iced_native::widget::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,
)
}
}
}

View file

@ -1,103 +1,9 @@
//! Display a dropdown list of selectable values. //! Display a dropdown list of selectable values.
use crate::alignment; use crate::Renderer;
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::{mouse, Font, Padding, Point, Rectangle}; pub use iced_native::widget::pick_list::State;
use iced_style::menu;
pub use iced_native::pick_list::State;
pub use iced_style::pick_list::{Style, StyleSheet}; pub use iced_style::pick_list::{Style, StyleSheet};
/// A widget allowing the selection of a single value from a list of options. /// A widget allowing the selection of a single value from a list of options.
pub type PickList<'a, T, Message, Backend> = pub type PickList<'a, T, Message, Backend> =
iced_native::PickList<'a, T, Message, Renderer<Backend>>; iced_native::widget::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()
},
)
}
}

View file

@ -2,73 +2,4 @@
//! //!
//! A [`ProgressBar`] has a range of possible values and a current value, //! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style. //! as well as a length, height and style.
use crate::{Backend, Primitive, Renderer}; pub use iced_native::widget::progress_bar::*;
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(),
)
}
}

View file

@ -1,10 +1,12 @@
//! Encode and display information in a QR code. //! Encode and display information in a QR code.
use crate::canvas; 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::{ use iced_native::{
layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Size, Widget, Widget,
}; };
use thiserror::Error; use thiserror::Error;
@ -80,12 +82,14 @@ where
fn draw( fn draw(
&self, &self,
_renderer: &mut Renderer<B>, renderer: &mut Renderer<B>,
_defaults: &Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) { ) {
use iced_native::Renderer as _;
let bounds = layout.bounds(); let bounds = layout.bounds();
let side_length = self.state.width + 2 * QUIET_ZONE; let side_length = self.state.width + 2 * QUIET_ZONE;
@ -122,13 +126,11 @@ where
}); });
}); });
( let translation = Vector::new(bounds.x, bounds.y);
Primitive::Translate {
translation: Vector::new(bounds.x, bounds.y), renderer.with_translation(translation, |renderer| {
content: Box::new(geometry.into_primitive()), renderer.draw_primitive(geometry.into_primitive());
}, });
mouse::Interaction::default(),
)
} }
} }

View file

@ -1,8 +1,5 @@
//! Create choices using radio buttons. //! Create choices using radio buttons.
use crate::{Backend, Primitive, Renderer}; use crate::Renderer;
use iced_native::mouse;
use iced_native::radio;
use iced_native::{Background, Color, Rectangle};
pub use iced_style::radio::{Style, StyleSheet}; 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 /// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`. /// `iced_wgpu::Renderer`.
pub type Radio<Message, Backend> = pub type Radio<'a, Message, Backend> =
iced_native::Radio<Message, Renderer<Backend>>; iced_native::widget::Radio<'a, 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()
},
)
}
}

View file

@ -1,49 +1,5 @@
use crate::{Backend, Primitive, Renderer}; use crate::Renderer;
use iced_native::mouse;
use iced_native::row;
use iced_native::{Element, Layout, Point, Rectangle};
/// A container that distributes its contents horizontally. /// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> = pub type Row<'a, Message, Backend> =
iced_native::Row<'a, Message, Renderer<Backend>>; iced_native::widget::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,
)
}
}

View file

@ -1,73 +1,3 @@
//! Display a horizontal or vertical rule for dividing content. //! Display a horizontal or vertical rule for dividing content.
use crate::{Backend, Primitive, Renderer}; pub use iced_native::widget::rule::*;
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())
}
}

View file

@ -1,10 +1,7 @@
//! Navigate an endless amount of content with a scrollbar. //! Navigate an endless amount of content with a scrollbar.
use crate::{Backend, Primitive, Renderer}; use crate::Renderer;
use iced_native::mouse;
use iced_native::scrollable;
use iced_native::{Background, Color, Rectangle, Vector};
pub use iced_native::scrollable::State; pub use iced_native::widget::scrollable::State;
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
/// A widget that can vertically display an infinite amount of content /// 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 /// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`. /// `Renderer`.
pub type Scrollable<'a, Message, Backend> = pub type Scrollable<'a, Message, Backend> =
iced_native::Scrollable<'a, Message, Renderer<Backend>>; iced_native::widget::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
},
)
}
}

View file

@ -1,123 +1,5 @@
//! Display an interactive selector of a single value from a range of values. //! Display an interactive selector of a single value from a range of values.
//! //!
//! A [`Slider`] has some local [`State`]. //! A [`Slider`] has some local [`State`].
use crate::{Backend, Primitive, Renderer}; pub use iced_native::widget::slider::{Slider, State};
use iced_native::mouse;
use iced_native::slider;
use iced_native::{Background, Color, Point, Rectangle};
pub use iced_native::slider::State;
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; 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()
},
)
}
}

View file

@ -1,15 +1 @@
use crate::{Backend, Primitive, Renderer}; pub use iced_native::widget::Space;
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())
}
}

View file

@ -1,9 +1,10 @@
//! Display vector graphics in your application. //! Display vector graphics in your application.
use crate::backend::{self, Backend}; use crate::backend::{self, Backend};
use crate::{Primitive, Renderer}; use crate::{Primitive, Rectangle, Renderer};
use iced_native::{mouse, svg, Layout}; 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> impl<B> svg::Renderer for Renderer<B>
where where
@ -13,17 +14,7 @@ where
self.backend().viewport_dimensions(handle) self.backend().viewport_dimensions(handle)
} }
fn draw( fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
&mut self, self.draw_primitive(Primitive::Svg { handle, bounds })
handle: svg::Handle,
layout: Layout<'_>,
) -> Self::Output {
(
Primitive::Svg {
handle,
bounds: layout.bounds(),
},
mouse::Interaction::default(),
)
} }
} }

View file

@ -1,92 +1,7 @@
//! Write some text for your users to read. //! Write some text for your users to read.
use crate::backend::{self, Backend}; use crate::Renderer;
use crate::{Primitive, Renderer};
use iced_native::alignment;
use iced_native::mouse;
use iced_native::text;
use iced_native::{Color, Font, Point, Rectangle, Size};
/// A paragraph of text. /// A paragraph of text.
/// ///
/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`. /// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`.
pub type Text<Backend> = iced_native::Text<Renderer<Backend>>; pub type Text<Backend> = iced_native::widget::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(),
)
}
}

View file

@ -1,266 +1,13 @@
//! Display fields that can be filled with text. //! Display fields that can be filled with text.
//! //!
//! A [`TextInput`] has some local [`State`]. //! A [`TextInput`] has some local [`State`].
use crate::alignment; use crate::Renderer;
use crate::backend::{self, Backend};
use crate::{
Background, Color, Font, Point, Primitive, Rectangle, Renderer, Size,
Vector,
};
use iced_native::mouse; pub use iced_native::widget::text_input::State;
use iced_native::text_input::{self, cursor};
use std::f32;
pub use iced_native::text_input::State;
pub use iced_style::text_input::{Style, StyleSheet}; pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text. /// A field that can be filled with text.
/// ///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. /// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
pub type TextInput<'a, Message, Backend> = pub type TextInput<'a, Message, Backend> =
iced_native::TextInput<'a, Message, Renderer<Backend>>; iced_native::widget::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)
}

View file

@ -1,99 +1,10 @@
//! Show toggle controls using togglers. //! Show toggle controls using togglers.
use crate::backend::{self, Backend}; use crate::Renderer;
use crate::{Primitive, Renderer};
use iced_native::mouse;
use iced_native::toggler;
use iced_native::Rectangle;
pub use iced_style::toggler::{Style, StyleSheet}; 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. /// A toggler that can be toggled.
/// ///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`. /// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
pub type Toggler<Message, Backend> = pub type Toggler<'a, Message, Backend> =
iced_native::Toggler<Message, Renderer<Backend>>; iced_native::widget::Toggler<'a, 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()
},
)
}
}

View file

@ -1,168 +1,11 @@
//! Decorate content and apply alignment. //! Decorate content and apply alignment.
use crate::backend::{self, Backend}; use crate::Renderer;
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};
/// An element decorating some content. /// An element decorating some content.
/// ///
/// This is an alias of an `iced_native` tooltip with a default /// This is an alias of an `iced_native` tooltip with a default
/// `Renderer`. /// `Renderer`.
pub type Tooltip<'a, Message, Backend> = pub type Tooltip<'a, Message, Backend> =
iced_native::Tooltip<'a, Message, Renderer<Backend>>; iced_native::widget::Tooltip<'a, Message, Renderer<Backend>>;
pub use iced_native::tooltip::Position; pub use iced_native::widget::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,
);
(
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)
}
}
}

View file

@ -1,7 +1,5 @@
use crate::{Color, Error, Viewport}; use crate::{Color, Error, Viewport};
use iced_native::mouse;
use raw_window_handle::HasRawWindowHandle; use raw_window_handle::HasRawWindowHandle;
use thiserror::Error; use thiserror::Error;
@ -30,9 +28,8 @@ pub trait Compositor: Sized {
window: &W, window: &W,
) -> Self::Surface; ) -> Self::Surface;
/// Crates a new [`SwapChain`] for the given [`Surface`]. /// Configures a new [`Surface`] with the given dimensions.
/// ///
/// [`SwapChain`]: Self::SwapChain
/// [`Surface`]: Self::Surface /// [`Surface`]: Self::Surface
fn configure_surface( fn configure_surface(
&mut self, &mut self,
@ -41,18 +38,17 @@ pub trait Compositor: Sized {
height: u32, 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 /// [`SwapChain`]: Self::SwapChain
fn draw<T: AsRef<str>>( fn present<T: AsRef<str>>(
&mut self, &mut self,
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
surface: &mut Self::Surface, surface: &mut Self::Surface,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> Result<mouse::Interaction, SurfaceError>; ) -> Result<(), SurfaceError>;
} }
/// Result of an unsuccessful call to [`Compositor::draw`]. /// 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" "A timeout was encountered while trying to acquire the next frame"
)] )]
Timeout, 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( #[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, Outdated,
/// The swap chain has been lost and needs to be recreated. /// 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, Lost,
/// There is no more memory left to allocate a new frame. /// There is no more memory left to allocate a new frame.
#[error("There is no more memory left to allocate a new frame")] #[error("There is no more memory left to allocate a new frame")]

View file

@ -1,5 +1,4 @@
use crate::{Color, Error, Size, Viewport}; use crate::{Color, Error, Size, Viewport};
use iced_native::mouse;
use core::ffi::c_void; use core::ffi::c_void;
@ -49,15 +48,15 @@ pub trait GLCompositor: Sized {
/// Resizes the viewport of the [`GLCompositor`]. /// Resizes the viewport of the [`GLCompositor`].
fn resize_viewport(&mut self, physical_size: Size<u32>); 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 /// [`Renderer`]: crate::Renderer
fn draw<T: AsRef<str>>( fn present<T: AsRef<str>>(
&mut self, &mut self,
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> mouse::Interaction; );
} }

View file

@ -23,3 +23,7 @@ path = "../core"
version = "0.3" version = "0.3"
path = "../futures" path = "../futures"
features = ["thread-pool"] features = ["thread-pool"]
[dependencies.iced_style]
version = "0.3"
path = "../style"

View file

@ -1,6 +1,8 @@
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::{ use crate::{
Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget, Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
}; };
@ -77,7 +79,7 @@ where
/// ///
/// ``` /// ```
/// # mod counter { /// # 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)] /// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {} /// # pub enum Message {}
@ -104,7 +106,8 @@ where
/// # pub enum Message { /// # pub enum Message {
/// # Counter(usize, counter::Message) /// # Counter(usize, counter::Message)
/// # } /// # }
/// use iced_native::{Element, Row}; /// use iced_native::Element;
/// use iced_native::widget::Row;
/// use iced_wgpu::Renderer; /// use iced_wgpu::Renderer;
/// ///
/// impl ManyCounters { /// impl ManyCounters {
@ -189,7 +192,7 @@ where
) -> Element<'a, Message, Renderer> ) -> Element<'a, Message, Renderer>
where where
Message: 'static, Message: 'static,
Renderer: 'a + layout::Debugger, Renderer: 'a,
{ {
Element { Element {
widget: Box::new(Explain::new(self, color.into())), widget: Box::new(Explain::new(self, color.into())),
@ -241,13 +244,24 @@ where
pub fn draw( pub fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) {
self.widget self.widget
.draw(renderer, defaults, layout, cursor_position, viewport) .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,
) -> mouse::Interaction {
self.widget
.mouse_interaction(layout, cursor_position, viewport)
} }
/// Computes the _layout_ hash of the [`Element`]. /// Computes the _layout_ hash of the [`Element`].
@ -336,13 +350,23 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) {
self.widget self.widget
.draw(renderer, defaults, layout, cursor_position, viewport) .draw(renderer, style, layout, cursor_position, viewport)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> mouse::Interaction {
self.widget
.mouse_interaction(layout, cursor_position, viewport)
} }
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {
@ -378,7 +402,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer> for Explain<'a, Message, Renderer>
where where
Renderer: crate::Renderer + layout::Debugger, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.element.widget.width() self.element.widget.width()
@ -418,19 +442,51 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) {
renderer.explain( fn explain_layout<Renderer: crate::Renderer>(
defaults, renderer: &mut Renderer,
self.element.widget.as_ref(), 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, layout,
cursor_position, cursor_position,
viewport, 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) { fn hash_layout(&self, state: &mut Hasher) {

124
native/src/image.rs Normal file
View file

@ -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);
}

View file

@ -1,11 +1,9 @@
//! Position your widgets properly. //! Position your widgets properly.
mod debugger;
mod limits; mod limits;
mod node; mod node;
pub mod flex; pub mod flex;
pub use debugger::Debugger;
pub use limits::Limits; pub use limits::Limits;
pub use node::Node; pub use node::Node;

View file

@ -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;
}

View file

@ -36,6 +36,7 @@
pub mod clipboard; pub mod clipboard;
pub mod command; pub mod command;
pub mod event; pub mod event;
pub mod image;
pub mod keyboard; pub mod keyboard;
pub mod layout; pub mod layout;
pub mod mouse; pub mod mouse;
@ -43,6 +44,8 @@ pub mod overlay;
pub mod program; pub mod program;
pub mod renderer; pub mod renderer;
pub mod subscription; pub mod subscription;
pub mod svg;
pub mod text;
pub mod touch; pub mod touch;
pub mod widget; pub mod widget;
pub mod window; pub mod window;
@ -84,4 +87,4 @@ pub use renderer::Renderer;
pub use runtime::Runtime; pub use runtime::Runtime;
pub use subscription::Subscription; pub use subscription::Subscription;
pub use user_interface::{Cache, UserInterface}; pub use user_interface::{Cache, UserInterface};
pub use widget::*; pub use widget::Widget;

View file

@ -8,7 +8,9 @@ pub use menu::Menu;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; 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. /// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer> pub trait Overlay<Message, Renderer>
@ -32,10 +34,10 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output; );
/// Computes the _layout_ hash of the [`Overlay`]. /// Computes the _layout_ hash of the [`Overlay`].
/// ///
@ -73,4 +75,16 @@ where
) -> event::Status { ) -> event::Status {
event::Status::Ignored 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
}
} }

View file

@ -2,7 +2,9 @@ pub use crate::Overlay;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; 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`]. /// A generic [`Overlay`].
#[allow(missing_debug_implementations)] #[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`]. /// Draws the [`Element`] and its children using the given [`Layout`].
pub fn draw( pub fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output { ) {
self.overlay self.overlay.draw(renderer, style, layout, cursor_position)
.draw(renderer, defaults, layout, cursor_position)
} }
/// Computes the _layout_ hash of the [`Element`]. /// Computes the _layout_ hash of the [`Element`].
@ -139,15 +151,24 @@ where
event_status 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( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output { ) {
self.content self.content.draw(renderer, style, layout, cursor_position)
.draw(renderer, defaults, layout, cursor_position)
} }
fn hash_layout(&self, state: &mut Hasher, position: Point) { fn hash_layout(&self, state: &mut Hasher, position: Point) {

View file

@ -1,20 +1,24 @@
//! Build and show dropdown menus. //! Build and show dropdown menus.
use crate::container; use crate::alignment;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::scrollable; use crate::renderer;
use crate::text; use crate::text::{self, Text};
use crate::touch; use crate::touch;
use crate::widget::scrollable::{self, Scrollable};
use crate::widget::Container;
use crate::{ use crate::{
Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point, Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Scrollable, Size, Vector, Widget, Rectangle, Size, Vector, Widget,
}; };
pub use iced_style::menu::Style;
/// A list of selectable options. /// A list of selectable options.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Menu<'a, T, Renderer: self::Renderer> { pub struct Menu<'a, T, Renderer: text::Renderer> {
state: &'a mut State, state: &'a mut State,
options: &'a [T], options: &'a [T],
hovered_option: &'a mut Option<usize>, hovered_option: &'a mut Option<usize>,
@ -23,13 +27,13 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
padding: Padding, padding: Padding,
text_size: Option<u16>, text_size: Option<u16>,
font: Renderer::Font, font: Renderer::Font,
style: <Renderer as self::Renderer>::Style, style: Style,
} }
impl<'a, T, Renderer> Menu<'a, T, Renderer> impl<'a, T, Renderer> Menu<'a, T, Renderer>
where where
T: ToString + Clone, T: ToString + Clone,
Renderer: self::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
/// Creates a new [`Menu`] with the given [`State`], a list of options, and /// Creates a new [`Menu`] with the given [`State`], a list of options, and
/// the message to produced when an option is selected. /// the message to produced when an option is selected.
@ -77,10 +81,7 @@ where
} }
/// Sets the style of the [`Menu`]. /// Sets the style of the [`Menu`].
pub fn style( pub fn style(mut self, style: impl Into<Style>) -> Self {
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
) -> Self {
self.style = style.into(); self.style = style.into();
self 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>, container: Container<'a, Message, Renderer>,
width: u16, width: u16,
target_height: f32, 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 where
Message: 'a, Message: 'a,
Renderer: 'a, Renderer: 'a,
@ -168,7 +169,7 @@ where
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer> impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer> for Overlay<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: text::Renderer,
{ {
fn layout( fn layout(
&self, &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( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output { ) {
let primitives = self.container.draw( let bounds = layout.bounds();
renderer,
defaults, renderer.fill_quad(
layout, renderer::Quad {
cursor_position, bounds,
&layout.bounds(), border_color: self.style.border_color,
border_width: self.style.border_width,
border_radius: 0.0,
},
self.style.background,
); );
renderer.decorate( self.container
layout.bounds(), .draw(renderer, style, layout, cursor_position, &bounds);
cursor_position,
&self.style,
primitives,
)
} }
} }
struct List<'a, T, Renderer: self::Renderer> { struct List<'a, T, Renderer: text::Renderer> {
options: &'a [T], options: &'a [T],
hovered_option: &'a mut Option<usize>, hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>, last_selection: &'a mut Option<T>,
padding: Padding, padding: Padding,
text_size: Option<u16>, text_size: Option<u16>,
font: Renderer::Font, 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> for List<'a, T, Renderer>
where where
T: Clone + ToString, T: Clone + ToString,
Renderer: self::Renderer, Renderer: text::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
Length::Fill Length::Fill
@ -376,65 +387,84 @@ where
event::Status::Ignored 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);
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, _cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) {
self::Renderer::draw( let bounds = layout.bounds();
renderer,
layout.bounds(), let text_size = self.text_size.unwrap_or(renderer.default_size());
cursor_position, let option_height = (text_size + self.padding.vertical()) as usize;
viewport,
self.options, let offset = viewport.y - bounds.y;
*self.hovered_option, let start = (offset / option_height as f32) as usize;
self.padding, let end =
self.text_size.unwrap_or(renderer.default_size()), ((offset + viewport.height) / option_height as f32).ceil() as usize;
self.font,
&self.style, 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,
);
} }
}
/// The renderer of a [`Menu`]. renderer.fill_text(Text {
/// content: &option.to_string(),
/// Your [renderer] will need to implement this trait before being bounds: Rectangle {
/// able to use a [`Menu`] in your user interface. x: bounds.x + self.padding.left as f32,
/// y: bounds.center_y(),
/// [renderer]: crate::renderer width: f32::INFINITY,
pub trait Renderer: ..bounds
scrollable::Renderer + container::Renderer + text::Renderer },
{ size: f32::from(text_size),
/// The [`Menu`] style supported by this renderer. font: self.font,
type Style: Default + Clone; color: if is_selected {
self.style.selected_text_color
/// Decorates a the list of options of a [`Menu`]. } else {
/// self.style.text_color
/// This method can be used to draw a background for the [`Menu`]. },
fn decorate( horizontal_alignment: alignment::Horizontal::Left,
&mut self, vertical_alignment: alignment::Vertical::Center,
bounds: Rectangle, });
cursor_position: Point, }
style: &<Self as Renderer>::Style, }
primitive: Self::Output,
) -> Self::Output;
/// 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;
} }
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>> 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 where
T: ToString + Clone, T: ToString + Clone,
Message: 'a, Message: 'a,
Renderer: 'a + self::Renderer, Renderer: 'a + text::Renderer,
{ {
fn into(self) -> Element<'a, Message, Renderer> { fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self) Element::new(self)

View file

@ -1,5 +1,6 @@
use crate::mouse;
use crate::{ use crate::{
Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size, Cache, Clipboard, Command, Debug, Event, Point, Program, Size,
UserInterface, UserInterface,
}; };
@ -12,9 +13,9 @@ where
{ {
program: P, program: P,
cache: Option<Cache>, cache: Option<Cache>,
primitive: <P::Renderer as Renderer>::Output,
queued_events: Vec<Event>, queued_events: Vec<Event>,
queued_messages: Vec<P::Message>, queued_messages: Vec<P::Message>,
mouse_interaction: mouse::Interaction,
} }
impl<P> State<P> impl<P> State<P>
@ -26,11 +27,10 @@ where
pub fn new( pub fn new(
mut program: P, mut program: P,
bounds: Size, bounds: Size,
cursor_position: Point,
renderer: &mut P::Renderer, renderer: &mut P::Renderer,
debug: &mut Debug, debug: &mut Debug,
) -> Self { ) -> Self {
let mut user_interface = build_user_interface( let user_interface = build_user_interface(
&mut program, &mut program,
Cache::default(), Cache::default(),
renderer, renderer,
@ -38,18 +38,14 @@ where
debug, debug,
); );
debug.draw_started();
let primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished();
let cache = Some(user_interface.into_cache()); let cache = Some(user_interface.into_cache());
State { State {
program, program,
cache, cache,
primitive,
queued_events: Vec::new(), queued_events: Vec::new(),
queued_messages: Vec::new(), queued_messages: Vec::new(),
mouse_interaction: mouse::Interaction::Idle,
} }
} }
@ -58,11 +54,6 @@ where
&self.program &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`]. /// Queues an event in the [`State`] for processing during an [`update`].
/// ///
/// [`update`]: Self::update /// [`update`]: Self::update
@ -82,6 +73,11 @@ where
self.queued_events.is_empty() && self.queued_messages.is_empty() 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 /// Processes all the queued events and messages, rebuilding and redrawing
/// the widgets of the linked [`Program`] if necessary. /// the widgets of the linked [`Program`] if necessary.
/// ///
@ -120,7 +116,8 @@ where
if messages.is_empty() { if messages.is_empty() {
debug.draw_started(); debug.draw_started();
self.primitive = user_interface.draw(renderer, cursor_position); self.mouse_interaction =
user_interface.draw(renderer, cursor_position);
debug.draw_finished(); debug.draw_finished();
self.cache = Some(user_interface.into_cache()); self.cache = Some(user_interface.into_cache());
@ -151,7 +148,8 @@ where
); );
debug.draw_started(); debug.draw_started();
self.primitive = user_interface.draw(renderer, cursor_position); self.mouse_interaction =
user_interface.draw(renderer, cursor_position);
debug.draw_finished(); debug.draw_finished();
self.cache = Some(user_interface.into_cache()); self.cache = Some(user_interface.into_cache());

View file

@ -19,28 +19,17 @@
//! [`text::Renderer`]: crate::widget::text::Renderer //! [`text::Renderer`]: crate::widget::text::Renderer
//! [`Checkbox`]: crate::widget::Checkbox //! [`Checkbox`]: crate::widget::Checkbox
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer //! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
mod null; mod null;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub use null::Null; 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 /// A component that can take the state of a user interface and produce an
/// output for its users. /// output for its users.
pub trait Renderer: Sized { 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. /// Lays out the elements of a user interface.
/// ///
/// You should override this if you need to perform any operations before or /// 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) element.layout(self, limits)
} }
/// Overlays the `overlay` output with the given bounds on top of the `base` /// Draws the primitives recorded in the given closure in a new layer.
/// output. ///
fn overlay( /// 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, &mut self,
base: Self::Output, translation: Vector,
overlay: Self::Output, f: impl FnOnce(&mut Self),
overlay_bounds: Rectangle, );
) -> Self::Output;
/// 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,
}
}
} }

View file

@ -1,20 +1,6 @@
use crate::alignment; use crate::renderer::{self, Renderer};
use crate::button; use crate::text::{self, Text};
use crate::checkbox; use crate::{Background, Font, Point, Rectangle, Size, Vector};
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,
};
/// A renderer that does nothing. /// A renderer that does nothing.
/// ///
@ -30,33 +16,21 @@ impl Null {
} }
impl Renderer for Null { impl Renderer for Null {
type Output = (); fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
type Defaults = ();
fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) { fn with_translation(
}
}
impl column::Renderer for Null {
fn draw<Message>(
&mut self, &mut self,
_defaults: &Self::Defaults, _translation: Vector,
_content: &[Element<'_, Message, Self>], _f: impl FnOnce(&mut Self),
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) { ) {
} }
}
impl row::Renderer for Null { fn clear(&mut self) {}
fn draw<Message>(
fn fill_quad(
&mut self, &mut self,
_defaults: &Self::Defaults, _quad: renderer::Quad,
_content: &[Element<'_, Message, Self>], _background: impl Into<Background>,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) { ) {
} }
} }
@ -64,6 +38,10 @@ impl row::Renderer for Null {
impl text::Renderer for Null { impl text::Renderer for Null {
type Font = Font; 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 { fn default_size(&self) -> u16 {
20 20
} }
@ -90,240 +68,5 @@ impl text::Renderer for Null {
None None
} }
fn draw( fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
&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,
) {
}
} }

88
native/src/svg.rs Normal file
View file

@ -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);
}

114
native/src/text.rs Normal file
View file

@ -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>);
}

View file

@ -1,6 +1,8 @@
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size}; use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher; use std::hash::Hasher;
@ -47,7 +49,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer; /// # pub use iced_native::renderer::Null as Renderer;
/// # } /// # }
/// # /// #
/// # use iced_native::Column; /// # use iced_native::widget::Column;
/// # /// #
/// # pub struct Counter; /// # pub struct Counter;
/// # /// #
@ -141,7 +143,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer; /// # pub use iced_native::renderer::Null as Renderer;
/// # } /// # }
/// # /// #
/// # use iced_native::Column; /// # use iced_native::widget::Column;
/// # /// #
/// # pub struct Counter; /// # pub struct Counter;
/// # /// #
@ -277,7 +279,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer; /// # pub use iced_native::renderer::Null as Renderer;
/// # } /// # }
/// # /// #
/// # use iced_native::Column; /// # use iced_native::widget::Column;
/// # /// #
/// # pub struct Counter; /// # pub struct Counter;
/// # /// #
@ -333,10 +335,13 @@ where
&mut self, &mut self,
renderer: &mut Renderer, renderer: &mut Renderer,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output { ) -> mouse::Interaction {
// TODO: Move to shell level (?)
renderer.clear();
let viewport = Rectangle::with_size(self.bounds); 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)) self.root.overlay(Layout::new(&self.base.layout))
{ {
let layer = Self::overlay_layer( let layer = Self::overlay_layer(
@ -346,51 +351,81 @@ where
renderer, 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); self.overlay = Some(layer);
Some((overlay_primitives, overlay_bounds))
} else {
None
}; };
if let Some((overlay_primitives, overlay_bounds)) = overlay { if let Some(layer) = &self.overlay {
let base_cursor = if overlay_bounds.contains(cursor_position) { let base_cursor = if layer.layout.bounds().contains(cursor_position)
{
Point::new(-1.0, -1.0) Point::new(-1.0, -1.0)
} else { } else {
cursor_position cursor_position
}; };
let base_primitives = self.root.widget.draw( self.root.widget.draw(
renderer, renderer,
&Renderer::Defaults::default(), &renderer::Style::default(),
Layout::new(&self.base.layout), Layout::new(&self.base.layout),
base_cursor, base_cursor,
&viewport, &viewport,
); );
renderer.overlay(
base_primitives,
overlay_primitives,
overlay_bounds,
)
} else { } else {
self.root.widget.draw( self.root.widget.draw(
renderer, renderer,
&Renderer::Defaults::default(), &renderer::Style::default(),
Layout::new(&self.base.layout), Layout::new(&self.base.layout),
cursor_position, cursor_position,
&viewport, &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 /// Relayouts and returns a new [`UserInterface`] using the provided

View file

@ -10,14 +10,6 @@
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or //! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
//! source of inspiration. //! 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 //! [renderer]: crate::renderer
pub mod button; pub mod button;
pub mod checkbox; pub mod checkbox;
@ -80,7 +72,9 @@ pub use tooltip::Tooltip;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle}; use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction. /// A component that displays information and allows interaction.
@ -131,11 +125,11 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output; );
/// Computes the _layout_ hash of the [`Widget`]. /// Computes the _layout_ hash of the [`Widget`].
/// ///
@ -174,6 +168,18 @@ where
event::Status::Ignored 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. /// Returns the overlay of the [`Widget`], if there is any.
fn overlay( fn overlay(
&mut self, &mut self,

View file

@ -5,20 +5,24 @@ use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::touch; use crate::touch;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
Widget, Point, Rectangle, Vector, Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
pub use iced_style::button::{Style, StyleSheet};
/// A generic widget that produces a message when pressed. /// A generic widget that produces a message when pressed.
/// ///
/// ``` /// ```
/// # use iced_native::{button, Text}; /// # use iced_native::widget::{button, Text};
/// # /// #
/// # type Button<'a, Message> = /// # 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)] /// #[derive(Clone)]
/// enum Message { /// enum Message {
@ -34,10 +38,10 @@ use std::hash::Hash;
/// be disabled: /// be disabled:
/// ///
/// ``` /// ```
/// # use iced_native::{button, Text}; /// # use iced_native::widget::{button, Text};
/// # /// #
/// # type Button<'a, Message> = /// # 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)] /// #[derive(Clone)]
/// enum Message { /// enum Message {
@ -53,7 +57,7 @@ use std::hash::Hash;
/// } /// }
/// ``` /// ```
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer: self::Renderer> { pub struct Button<'a, Message, Renderer> {
state: &'a mut State, state: &'a mut State,
content: Element<'a, Message, Renderer>, content: Element<'a, Message, Renderer>,
on_press: Option<Message>, on_press: Option<Message>,
@ -62,13 +66,13 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
min_width: u32, min_width: u32,
min_height: u32, min_height: u32,
padding: Padding, padding: Padding,
style: Renderer::Style, style_sheet: Box<dyn StyleSheet + 'a>,
} }
impl<'a, Message, Renderer> Button<'a, Message, Renderer> impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where where
Message: Clone, Message: Clone,
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
/// Creates a new [`Button`] with some local [`State`] and the given /// Creates a new [`Button`] with some local [`State`] and the given
/// content. /// content.
@ -84,8 +88,8 @@ where
height: Length::Shrink, height: Length::Shrink,
min_width: 0, min_width: 0,
min_height: 0, min_height: 0,
padding: Renderer::DEFAULT_PADDING, padding: Padding::new(5),
style: Renderer::Style::default(), style_sheet: Default::default(),
} }
} }
@ -127,8 +131,11 @@ where
} }
/// Sets the style of the [`Button`]. /// Sets the style of the [`Button`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
} }
@ -150,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer> for Button<'a, Message, Renderer>
where where
Message: Clone, Message: Clone,
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -241,24 +248,88 @@ where
event::Status::Ignored event::Status::Ignored
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
renderer.draw( let is_mouse_over = layout.bounds().contains(cursor_position);
defaults, let is_disabled = self.on_press.is_none();
layout.bounds(),
if is_mouse_over && !is_disabled {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
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 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, cursor_position,
self.on_press.is_none(), &bounds,
self.state.is_pressed, );
&self.style,
&self.content,
layout.children().next().unwrap(),
)
} }
fn hash_layout(&self, state: &mut Hasher) { 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>> impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Message: 'a + Clone, Message: 'a + Clone,
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
{ {
fn from( fn from(
button: Button<'a, Message, Renderer>, button: Button<'a, Message, Renderer>,

View file

@ -1,24 +1,27 @@
//! Show toggle controls using checkboxes. //! Show toggle controls using checkboxes.
use std::hash::Hash; use std::hash::Hash;
use crate::alignment::{self, Alignment}; use crate::alignment;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::row; use crate::renderer;
use crate::text; use crate::text;
use crate::touch; use crate::touch;
use crate::widget::{self, Row, Text};
use crate::{ use crate::{
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row, Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
Text, Widget, Rectangle, Widget,
}; };
pub use iced_style::checkbox::{Style, StyleSheet};
/// A box that can be checked. /// A box that can be checked.
/// ///
/// # Example /// # 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 { /// pub enum Message {
/// CheckboxToggled(bool), /// 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) /// ![Checkbox drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> { pub struct Checkbox<'a, Message, Renderer: text::Renderer> {
is_checked: bool, is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>, on_toggle: Box<dyn Fn(bool) -> Message>,
label: String, label: String,
@ -41,12 +44,16 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
text_size: Option<u16>, text_size: Option<u16>,
font: Renderer::Font, font: Renderer::Font,
text_color: Option<Color>, text_color: Option<Color>,
style: Renderer::Style, style_sheet: Box<dyn StyleSheet + 'a>,
} }
impl<Message, Renderer: self::Renderer + text::Renderer> impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> {
Checkbox<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`]. /// Creates a new [`Checkbox`].
/// ///
/// It expects: /// It expects:
@ -64,12 +71,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
on_toggle: Box::new(f), on_toggle: Box::new(f),
label: label.into(), label: label.into(),
width: Length::Shrink, width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE, size: Self::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, spacing: Self::DEFAULT_SPACING,
text_size: None, text_size: None,
font: Renderer::Font::default(), font: Renderer::Font::default(),
text_color: None, 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`]. /// Sets the style of the [`Checkbox`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
} }
impl<Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Checkbox<Message, Renderer> for Checkbox<'a, Message, Renderer>
where where
Renderer: self::Renderer + text::Renderer + row::Renderer, Renderer: text::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -180,43 +190,84 @@ where
event::Status::Ignored event::Status::Ignored
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let mut children = layout.children(); let mut children = layout.children();
let checkbox_layout = children.next().unwrap(); {
let label_layout = children.next().unwrap(); let layout = children.next().unwrap();
let checkbox_bounds = checkbox_layout.bounds(); let bounds = layout.bounds();
let label = text::Renderer::draw( 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,
);
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, renderer,
defaults, style,
label_layout.bounds(), label_layout,
&self.label, &self.label,
self.text_size.unwrap_or(renderer.default_size()),
self.font, self.font,
self.text_size,
self.text_color, self.text_color,
alignment::Horizontal::Left, alignment::Horizontal::Left,
alignment::Vertical::Center, alignment::Vertical::Center,
); );
}
let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer,
checkbox_bounds,
self.is_checked,
is_mouse_over,
label,
&self.style,
)
} }
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {
@ -227,47 +278,14 @@ where
} }
} }
/// The renderer of a [`Checkbox`]. impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
///
/// 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>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer + text::Renderer + row::Renderer, Renderer: 'a + text::Renderer,
Message: 'a, Message: 'a,
{ {
fn from( fn from(
checkbox: Checkbox<Message, Renderer>, checkbox: Checkbox<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer> {
Element::new(checkbox) Element::new(checkbox)
} }

View file

@ -3,7 +3,9 @@ use std::hash::Hash;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::{ use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point, Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget, Rectangle, Widget,
@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer> for Column<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -162,21 +164,37 @@ where
.fold(event::Status::Ignored, event::Status::merge) .fold(event::Status::Ignored, event::Status::merge)
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
renderer.draw( self.children
defaults, .iter()
&self.children, .zip(layout.children())
.map(|(child, layout)| {
child.widget.mouse_interaction(
layout, layout,
cursor_position, cursor_position,
viewport, viewport,
) )
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
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) { 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>> impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from( fn from(

View file

@ -4,19 +4,23 @@ use std::hash::Hash;
use crate::alignment::{self, Alignment}; use crate::alignment::{self, Alignment};
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
Widget, Point, Rectangle, Widget,
}; };
use std::u32; use std::u32;
pub use iced_style::container::{Style, StyleSheet};
/// An element decorating some content. /// An element decorating some content.
/// ///
/// It is normally used for alignment purposes. /// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Container<'a, Message, Renderer: self::Renderer> { pub struct Container<'a, Message, Renderer> {
padding: Padding, padding: Padding,
width: Length, width: Length,
height: Length, height: Length,
@ -24,13 +28,13 @@ pub struct Container<'a, Message, Renderer: self::Renderer> {
max_height: u32, max_height: u32,
horizontal_alignment: alignment::Horizontal, horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical, vertical_alignment: alignment::Vertical,
style: Renderer::Style, style_sheet: Box<dyn StyleSheet + 'a>,
content: Element<'a, Message, Renderer>, content: Element<'a, Message, Renderer>,
} }
impl<'a, Message, Renderer> Container<'a, Message, Renderer> impl<'a, Message, Renderer> Container<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
/// Creates an empty [`Container`]. /// Creates an empty [`Container`].
pub fn new<T>(content: T) -> Self pub fn new<T>(content: T) -> Self
@ -45,7 +49,7 @@ where
max_height: u32::MAX, max_height: u32::MAX,
horizontal_alignment: alignment::Horizontal::Left, horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top, vertical_alignment: alignment::Vertical::Top,
style: Renderer::Style::default(), style_sheet: Default::default(),
content: content.into(), content: content.into(),
} }
} }
@ -105,8 +109,11 @@ where
} }
/// Sets the style of the [`Container`]. /// Sets the style of the [`Container`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
} }
@ -114,7 +121,7 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer> for Container<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -172,25 +179,44 @@ where
) )
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
renderer.draw( self.content.widget.mouse_interaction(
defaults, layout.children().next().unwrap(),
layout.bounds(),
cursor_position, cursor_position,
viewport, viewport,
&self.style,
&self.content,
layout.children().next().unwrap(),
) )
} }
fn draw(
&self,
renderer: &mut Renderer,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
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,
);
}
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {
struct Marker; struct Marker;
std::any::TypeId::of::<Marker>().hash(state); std::any::TypeId::of::<Marker>().hash(state);
@ -212,33 +238,33 @@ where
} }
} }
/// The renderer of a [`Container`]. /// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
/// pub fn draw_background<Renderer>(
/// Your [renderer] will need to implement this trait before being renderer: &mut Renderer,
/// able to use a [`Container`] in your user interface. style: &Style,
///
/// [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, bounds: Rectangle,
cursor_position: Point, ) where
viewport: &Rectangle, Renderer: crate::Renderer,
style: &Self::Style, {
content: &Element<'_, Message, Self>, if style.background.is_some() || style.border_width > 0.0 {
content_layout: Layout<'_>, renderer.fill_quad(
) -> Self::Output; 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>> impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from( fn from(

View file

@ -2,21 +2,19 @@
pub mod viewer; pub mod viewer;
pub use viewer::Viewer; pub use viewer::Viewer;
use crate::image::{self, Handle};
use crate::layout; use crate::layout;
use crate::renderer;
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{ use std::hash::Hash;
hash::{Hash, Hasher as _},
path::PathBuf,
sync::Arc,
};
/// A frame that displays an image while keeping aspect ratio. /// A frame that displays an image while keeping aspect ratio.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// # use iced_native::Image; /// # use iced_native::widget::Image;
/// # /// #
/// let image = Image::new("resources/ferris.png"); /// let image = Image::new("resources/ferris.png");
/// ``` /// ```
@ -54,7 +52,7 @@ impl Image {
impl<Message, Renderer> Widget<Message, Renderer> for Image impl<Message, Renderer> Widget<Message, Renderer> for Image
where where
Renderer: self::Renderer, Renderer: image::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -92,12 +90,12 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) {
renderer.draw(self.handle.clone(), layout) renderer.draw(self.handle.clone(), layout.bounds());
} }
fn hash_layout(&self, state: &mut Hasher) { 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> impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: image::Renderer,
{ {
fn from(image: Image) -> Element<'a, Message, Renderer> { fn from(image: Image) -> Element<'a, Message, Renderer> {
Element::new(image) Element::new(image)

View file

@ -3,6 +3,7 @@ use crate::event::{self, Event};
use crate::image; use crate::image;
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::renderer;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Widget, Widget,
@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {
/// will be respected. /// will be respected.
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
where where
Renderer: self::Renderer + image::Renderer, Renderer: image::Renderer,
{ {
let (width, height) = renderer.dimensions(&self.handle); 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> impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
where where
Renderer: self::Renderer + image::Renderer, Renderer: image::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -280,14 +281,32 @@ where
} }
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> 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,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let image_size = self.image_size(renderer, bounds.size()); let image_size = self.image_size(renderer, bounds.size());
@ -301,17 +320,19 @@ where
image_top_left - self.state.offset(bounds, image_size) image_top_left - self.state.offset(bounds, image_size)
}; };
let is_mouse_over = bounds.contains(cursor_position); renderer.with_layer(bounds, |renderer| {
renderer.with_translation(translation, |renderer| {
self::Renderer::draw( image::Renderer::draw(
renderer, renderer,
&self.state,
bounds,
image_size,
translation,
self.handle.clone(), self.handle.clone(),
is_mouse_over, Rectangle {
x: bounds.x,
y: bounds.y,
..Rectangle::with_size(image_size)
},
) )
});
});
} }
fn hash_layout(&self, state: &mut Hasher) { 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> impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer + image::Renderer, Renderer: 'a + image::Renderer,
Message: 'a, Message: 'a,
{ {
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> { fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {

View file

@ -27,18 +27,19 @@ pub use split::Split;
pub use state::State; pub use state::State;
pub use title_bar::TitleBar; pub use title_bar::TitleBar;
use crate::container;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::row; use crate::renderer;
use crate::touch; use crate::touch;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
Widget, Vector, Widget,
}; };
pub use iced_style::pane_grid::{Line, StyleSheet};
/// A collection of panes distributed using either vertical or horizontal splits /// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available. /// to completely fill the space available.
/// ///
@ -61,10 +62,10 @@ use crate::{
/// ## Example /// ## Example
/// ///
/// ``` /// ```
/// # use iced_native::{pane_grid, Text}; /// # use iced_native::widget::{pane_grid, Text};
/// # /// #
/// # type PaneGrid<'a, Message> = /// # 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 { /// enum PaneState {
/// SomePane, /// SomePane,
@ -89,7 +90,7 @@ use crate::{
/// .on_resize(10, Message::PaneResized); /// .on_resize(10, Message::PaneResized);
/// ``` /// ```
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct PaneGrid<'a, Message, Renderer: self::Renderer> { pub struct PaneGrid<'a, Message, Renderer> {
state: &'a mut state::Internal, state: &'a mut state::Internal,
elements: Vec<(Pane, Content<'a, Message, Renderer>)>, elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length, width: Length,
@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> 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> impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
/// Creates a [`PaneGrid`] with the given [`State`] and view function. /// Creates a [`PaneGrid`] with the given [`State`] and view function.
/// ///
@ -130,7 +131,7 @@ where
on_click: None, on_click: None,
on_drag: None, on_drag: None,
on_resize: None, on_resize: None,
style: Default::default(), style_sheet: Default::default(),
} }
} }
@ -190,18 +191,15 @@ where
} }
/// Sets the style of the [`PaneGrid`]. /// Sets the style of the [`PaneGrid`].
pub fn style( pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
mut self, self.style_sheet = style.into();
style: impl Into<<Renderer as self::Renderer>::Style>,
) -> Self {
self.style = style.into();
self self
} }
} }
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn click_pane( fn click_pane(
&mut self, &mut self,
@ -318,7 +316,7 @@ pub struct ResizeEvent {
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer> for PaneGrid<'a, Message, Renderer>
where where
Renderer: self::Renderer + container::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -473,14 +471,43 @@ where
.fold(event_status, event::Status::merge) .fold(event_status, event::Status::merge)
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) -> 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,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
let picked_pane = self.state.picked_pane();
let picked_split = self let picked_split = self
.state .state
.picked_split() .picked_split()
@ -529,17 +556,89 @@ where
None => None, None => None,
}); });
self::Renderer::draw( 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, renderer,
defaults, style,
&self.elements,
self.state.picked_pane(),
picked_split,
layout, layout,
&self.style, pane_cursor_position,
cursor_position,
viewport, 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) { 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>> impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer + row::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from( fn from(

View file

@ -1,4 +1,4 @@
use crate::pane_grid::Axis; use crate::widget::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`]. /// The arrangement of a [`PaneGrid`].
/// ///

View file

@ -1,30 +1,32 @@
use crate::container;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; 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}; use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
/// The content of a [`Pane`]. /// The content of a [`Pane`].
/// ///
/// [`Pane`]: crate::widget::pane_grid::Pane /// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)] #[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>>, title_bar: Option<TitleBar<'a, Message, Renderer>>,
body: Element<'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> impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where where
Renderer: pane_grid::Renderer, Renderer: crate::Renderer,
{ {
/// Creates a new [`Content`] with the provided body. /// Creates a new [`Content`] with the provided body.
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self { pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
Self { Self {
title_bar: None, title_bar: None,
body: body.into(), body: body.into(),
style: Default::default(), style_sheet: Default::default(),
} }
} }
@ -40,16 +42,16 @@ where
/// Sets the style of the [`Content`]. /// Sets the style of the [`Content`].
pub fn style( pub fn style(
mut self, mut self,
style: impl Into<<Renderer as container::Renderer>::Style>, style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self { ) -> Self {
self.style = style.into(); self.style_sheet = style_sheet.into();
self self
} }
} }
impl<'a, Message, Renderer> Content<'a, Message, Renderer> impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where where
Renderer: pane_grid::Renderer, Renderer: crate::Renderer,
{ {
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
/// ///
@ -57,35 +59,45 @@ where
pub fn draw( pub fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, 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 { if let Some(title_bar) = &self.title_bar {
let mut children = layout.children(); let mut children = layout.children();
let title_bar_layout = children.next().unwrap(); let title_bar_layout = children.next().unwrap();
let body_layout = children.next().unwrap(); let body_layout = children.next().unwrap();
renderer.draw_pane( let show_controls = bounds.contains(cursor_position);
defaults,
layout.bounds(), title_bar.draw(
&self.style, renderer,
Some((title_bar, title_bar_layout)), style,
(&self.body, body_layout), title_bar_layout,
cursor_position, cursor_position,
viewport, viewport,
) show_controls,
);
self.body.draw(
renderer,
style,
body_layout,
cursor_position,
viewport,
);
} else { } else {
renderer.draw_pane( self.body
defaults, .draw(renderer, style, layout, cursor_position, viewport);
layout.bounds(),
&self.style,
None,
(&self.body, layout),
cursor_position,
viewport,
)
} }
} }
@ -186,6 +198,40 @@ where
event_status.merge(body_status) 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) { pub(crate) fn hash_layout(&self, state: &mut Hasher) {
if let Some(title_bar) = &self.title_bar { if let Some(title_bar) = &self.title_bar {
title_bar.hash_layout(state); title_bar.hash_layout(state);
@ -215,7 +261,7 @@ where
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer> impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
where where
T: Into<Element<'a, Message, Renderer>>, T: Into<Element<'a, Message, Renderer>>,
Renderer: pane_grid::Renderer + container::Renderer, Renderer: crate::Renderer,
{ {
fn from(element: T) -> Self { fn from(element: T) -> Self {
Self::new(element) Self::new(element)

View file

@ -1,7 +1,5 @@
use crate::{ use crate::widget::pane_grid::{Axis, Pane, Split};
pane_grid::{Axis, Pane, Split}, use crate::{Rectangle, Size};
Rectangle, Size,
};
use std::collections::BTreeMap; use std::collections::BTreeMap;

View file

@ -1,7 +1,7 @@
use crate::{ use crate::widget::pane_grid::{
pane_grid::{Axis, Configuration, Direction, Node, Pane, Split}, Axis, Configuration, Direction, Node, Pane, Split,
Hasher, Point, Rectangle, Size,
}; };
use crate::{Hasher, Point, Rectangle, Size};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};

View file

@ -1,8 +1,9 @@
use crate::container;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::pane_grid; use crate::renderer;
use crate::widget::container;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size, Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
}; };
@ -11,17 +12,17 @@ use crate::{
/// ///
/// [`Pane`]: crate::widget::pane_grid::Pane /// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { pub struct TitleBar<'a, Message, Renderer> {
content: Element<'a, Message, Renderer>, content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>, controls: Option<Element<'a, Message, Renderer>>,
padding: Padding, padding: Padding,
always_show_controls: bool, 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> impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where where
Renderer: pane_grid::Renderer, Renderer: crate::Renderer,
{ {
/// Creates a new [`TitleBar`] with the given content. /// Creates a new [`TitleBar`] with the given content.
pub fn new<E>(content: E) -> Self pub fn new<E>(content: E) -> Self
@ -33,7 +34,7 @@ where
controls: None, controls: None,
padding: Padding::ZERO, padding: Padding::ZERO,
always_show_controls: false, always_show_controls: false,
style: Default::default(), style_sheet: Default::default(),
} }
} }
@ -55,9 +56,9 @@ where
/// Sets the style of the [`TitleBar`]. /// Sets the style of the [`TitleBar`].
pub fn style( pub fn style(
mut self, mut self,
style: impl Into<<Renderer as container::Renderer>::Style>, style: impl Into<Box<dyn container::StyleSheet + 'a>>,
) -> Self { ) -> Self {
self.style = style.into(); self.style_sheet = style.into();
self self
} }
@ -77,7 +78,7 @@ where
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where where
Renderer: pane_grid::Renderer, Renderer: crate::Renderer,
{ {
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
/// ///
@ -85,39 +86,47 @@ where
pub fn draw( pub fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, inherited_style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
show_controls: bool, 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 mut children = layout.children();
let padded = children.next().unwrap(); let padded = children.next().unwrap();
let mut children = padded.children(); let mut children = padded.children();
let title_layout = children.next().unwrap(); 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(); let controls_layout = children.next().unwrap();
if show_controls || self.always_show_controls { if show_controls || self.always_show_controls {
Some((controls, controls_layout)) controls.draw(
} else { renderer,
None &inherited_style,
} controls_layout,
} else {
None
};
renderer.draw_title_bar(
defaults,
layout.bounds(),
&self.style,
(&self.content, title_layout),
controls,
cursor_position, cursor_position,
viewport, viewport,
) );
}
}
} }
/// Returns whether the mouse cursor is over the pick area of the /// Returns whether the mouse cursor is over the pick area of the
@ -244,6 +253,35 @@ where
control_status.merge(title_status) 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( pub(crate) fn overlay(
&mut self, &mut self,
layout: Layout<'_>, layout: Layout<'_>,

View file

@ -1,12 +1,13 @@
//! Display a dropdown list of selectable values. //! Display a dropdown list of selectable values.
use crate::alignment;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::keyboard; use crate::keyboard;
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::overlay::menu::{self, Menu}; use crate::overlay::menu::{self, Menu};
use crate::scrollable; use crate::renderer;
use crate::text; use crate::text::{self, Text};
use crate::touch; use crate::touch;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
@ -14,9 +15,11 @@ use crate::{
}; };
use std::borrow::Cow; use std::borrow::Cow;
pub use iced_style::pick_list::{Style, StyleSheet};
/// A widget for selecting a single value from a list of options. /// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct PickList<'a, T, Message, Renderer: self::Renderer> pub struct PickList<'a, T, Message, Renderer: text::Renderer>
where where
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,
{ {
@ -33,7 +36,7 @@ where
padding: Padding, padding: Padding,
text_size: Option<u16>, text_size: Option<u16>,
font: Renderer::Font, font: Renderer::Font,
style: <Renderer as self::Renderer>::Style, style_sheet: Box<dyn StyleSheet + 'a>,
} }
/// The local state of a [`PickList`]. /// 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> PickList<'a, T, Message, Renderer>
where where
T: ToString + Eq, T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>, [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, /// 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 /// the current selected value, and the message to produce when an option is
/// selected. /// selected.
@ -93,9 +99,9 @@ where
selected, selected,
width: Length::Shrink, width: Length::Shrink,
text_size: None, text_size: None,
padding: Renderer::DEFAULT_PADDING, padding: Self::DEFAULT_PADDING,
font: Default::default(), font: Default::default(),
style: Default::default(), style_sheet: Default::default(),
} }
} }
@ -132,9 +138,9 @@ where
/// Sets the style of the [`PickList`]. /// Sets the style of the [`PickList`].
pub fn style( pub fn style(
mut self, mut self,
style: impl Into<<Renderer as self::Renderer>::Style>, style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self { ) -> Self {
self.style = style.into(); self.style_sheet = style_sheet.into();
self self
} }
} }
@ -145,7 +151,7 @@ where
T: Clone + ToString + Eq, T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,
Message: 'static, Message: 'static,
Renderer: self::Renderer + scrollable::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -320,25 +326,90 @@ where
} }
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
self::Renderer::draw( let bounds = layout.bounds();
renderer, let is_mouse_over = bounds.contains(cursor_position);
layout.bounds(),
cursor_position, if is_mouse_over {
self.selected.as_ref().map(ToString::to_string), mouse::Interaction::Pointer
self.placeholder.as_ref().map(String::as_str), } else {
self.padding, mouse::Interaction::default()
}
}
fn draw(
&self,
renderer: &mut Renderer,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
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()), self.text_size.unwrap_or(renderer.default_size()),
self.font, ),
&self.style, 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( fn overlay(
@ -357,7 +428,7 @@ where
.width(bounds.width.round() as u16) .width(bounds.width.round() as u16)
.padding(self.padding) .padding(self.padding)
.font(self.font) .font(self.font)
.style(Renderer::menu_style(&self.style)); .style(self.style_sheet.menu());
if let Some(text_size) = self.text_size { if let Some(text_size) = self.text_size {
menu = menu.text_size(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>> impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
for PickList<'a, T, Message, Renderer> for PickList<'a, T, Message, Renderer>
where where
T: Clone + ToString + Eq, T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,
Renderer: self::Renderer + 'a, Renderer: text::Renderer + 'a,
Message: 'static, Message: 'static,
{ {
fn into(self) -> Element<'a, Message, Renderer> { fn into(self) -> Element<'a, Message, Renderer> {

View file

@ -1,17 +1,19 @@
//! Provide progress feedback to your users. //! Provide progress feedback to your users.
use crate::layout;
use crate::renderer;
use crate::{ 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}; use std::{hash::Hash, ops::RangeInclusive};
pub use iced_style::progress_bar::{Style, StyleSheet};
/// A bar that displays progress. /// A bar that displays progress.
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # use iced_native::renderer::Null; /// # use iced_native::widget::ProgressBar;
/// #
/// # pub type ProgressBar = iced_native::ProgressBar<Null>;
/// let value = 50.0; /// let value = 50.0;
/// ///
/// ProgressBar::new(0.0..=100.0, value); /// 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) /// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct ProgressBar<Renderer: self::Renderer> { pub struct ProgressBar<'a> {
range: RangeInclusive<f32>, range: RangeInclusive<f32>,
value: f32, value: f32,
width: Length, width: Length,
height: Option<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`]. /// Creates a new [`ProgressBar`].
/// ///
/// It expects: /// It expects:
@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
range, range,
width: Length::Fill, width: Length::Fill,
height: None, 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`]. /// Sets the style of the [`ProgressBar`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
} }
impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
} }
fn height(&self) -> Length { fn height(&self) -> Length {
self.height self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT))
} }
fn layout( fn layout(
@ -80,10 +87,9 @@ where
_renderer: &Renderer, _renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let limits = limits.width(self.width).height( let limits = limits
self.height .width(self.width)
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)), .height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));
);
let size = limits.resolve(Size::ZERO); let size = limits.resolve(Size::ZERO);
@ -93,17 +99,47 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) {
renderer.draw( let bounds = layout.bounds();
layout.bounds(), let (range_start, range_end) = self.range.clone().into_inner();
self.range.clone(),
self.value, let active_progress_width = if range_start >= range_end {
&self.style, 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) { fn hash_layout(&self, state: &mut Hasher) {
@ -115,45 +151,13 @@ where
} }
} }
/// The renderer of a [`ProgressBar`]. impl<'a, Message, Renderer> From<ProgressBar<'a>>
///
/// 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>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from( fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {
progress_bar: ProgressBar<Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(progress_bar) Element::new(progress_bar)
} }
} }

View file

@ -1,24 +1,27 @@
//! Create choices using radio buttons. //! Create choices using radio buttons.
use std::hash::Hash; use std::hash::Hash;
use crate::alignment::{self, Alignment}; use crate::alignment;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::row; use crate::renderer;
use crate::text; use crate::text;
use crate::touch; use crate::touch;
use crate::widget::{self, Row, Text};
use crate::{ use crate::{
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row, Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
Text, Widget, Rectangle, Widget,
}; };
pub use iced_style::radio::{Style, StyleSheet};
/// A circular button representing a choice. /// A circular button representing a choice.
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # type Radio<Message> = /// # type Radio<'a, Message> =
/// # iced_native::Radio<Message, iced_native::renderer::Null>; /// # iced_native::widget::Radio<'a, Message, iced_native::renderer::Null>;
/// # /// #
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice { /// 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) /// ![Radio buttons drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> { pub struct Radio<'a, Message, Renderer: text::Renderer> {
is_selected: bool, is_selected: bool,
on_click: Message, on_click: Message,
label: String, label: String,
@ -50,14 +53,19 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
text_size: Option<u16>, text_size: Option<u16>,
text_color: Option<Color>, text_color: Option<Color>,
font: Renderer::Font, font: Renderer::Font,
style: Renderer::Style, style_sheet: Box<dyn StyleSheet + 'a>,
} }
impl<Message, Renderer: self::Renderer + text::Renderer> impl<'a, Message, Renderer: text::Renderer> Radio<'a, Message, Renderer>
Radio<Message, Renderer>
where where
Message: Clone, 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. /// Creates a new [`Radio`] button.
/// ///
/// It expects: /// It expects:
@ -81,12 +89,12 @@ where
on_click: f(value), on_click: f(value),
label: label.into(), label: label.into(),
width: Length::Shrink, width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE, size: Self::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15 spacing: Self::DEFAULT_SPACING, //15
text_size: None, text_size: None,
text_color: None, text_color: None,
font: Default::default(), font: Default::default(),
style: Renderer::Style::default(), style_sheet: Default::default(),
} }
} }
@ -127,16 +135,20 @@ where
} }
/// Sets the style of the [`Radio`] button. /// Sets the style of the [`Radio`] button.
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
} }
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Radio<'a, Message, Renderer>
where where
Message: Clone, Message: Clone,
Renderer: self::Renderer + text::Renderer + row::Renderer, Renderer: text::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -192,43 +204,88 @@ where
event::Status::Ignored event::Status::Ignored
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let mut children = layout.children(); let mut children = layout.children();
let radio_layout = children.next().unwrap(); {
let label_layout = children.next().unwrap(); let layout = children.next().unwrap();
let radio_bounds = radio_layout.bounds(); let bounds = layout.bounds();
let label = text::Renderer::draw( 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,
);
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, renderer,
defaults, style,
label_layout.bounds(), label_layout,
&self.label, &self.label,
self.text_size.unwrap_or(renderer.default_size()),
self.font, self.font,
self.text_size,
self.text_color, self.text_color,
alignment::Horizontal::Left, alignment::Horizontal::Left,
alignment::Vertical::Center, alignment::Vertical::Center,
); );
}
let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer,
radio_bounds,
self.is_selected,
is_mouse_over,
label,
&self.style,
)
} }
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {
@ -239,46 +296,15 @@ where
} }
} }
/// The renderer of a [`Radio`] button. impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
///
/// 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>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Message: 'a + Clone, 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) Element::new(radio)
} }
} }

View file

@ -1,7 +1,9 @@
//! Distribute content horizontally. //! Distribute content horizontally.
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::{ use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point, Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget, Rectangle, Widget,
@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer> for Row<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -161,21 +163,37 @@ where
.fold(event::Status::Ignored, event::Status::merge) .fold(event::Status::Ignored, event::Status::merge)
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
renderer.draw( self.children
defaults, .iter()
&self.children, .zip(layout.children())
.map(|(child, layout)| {
child.widget.mouse_interaction(
layout, layout,
cursor_position, cursor_position,
viewport, viewport,
) )
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
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) { 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>> impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> { fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {

View file

@ -1,28 +1,31 @@
//! Display a horizontal or vertical rule for dividing content. //! 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 std::hash::Hash;
use crate::{ pub use iced_style::rule::{FillMode, Style, StyleSheet};
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
/// Display a horizontal or vertical rule for dividing content. /// Display a horizontal or vertical rule for dividing content.
#[derive(Debug, Copy, Clone)] #[allow(missing_debug_implementations)]
pub struct Rule<Renderer: self::Renderer> { pub struct Rule<'a> {
width: Length, width: Length,
height: Length, height: Length,
style: Renderer::Style,
is_horizontal: bool, 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. /// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
pub fn horizontal(spacing: u16) -> Self { pub fn horizontal(spacing: u16) -> Self {
Rule { Rule {
width: Length::Fill, width: Length::Fill,
height: Length::from(Length::Units(spacing)), height: Length::from(Length::Units(spacing)),
style: Renderer::Style::default(),
is_horizontal: true, is_horizontal: true,
style_sheet: Default::default(),
} }
} }
@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
Rule { Rule {
width: Length::from(Length::Units(spacing)), width: Length::from(Length::Units(spacing)),
height: Length::Fill, height: Length::Fill,
style: Renderer::Style::default(),
is_horizontal: false, is_horizontal: false,
style_sheet: Default::default(),
} }
} }
/// Sets the style of the [`Rule`]. /// Sets the style of the [`Rule`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
} }
impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -68,12 +74,53 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _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) { fn hash_layout(&self, state: &mut Hasher) {
@ -85,32 +132,12 @@ where
} }
} }
/// The renderer of a [`Rule`]. impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>
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>
where where
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> { fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {
Element::new(rule) Element::new(rule)
} }
} }

View file

@ -1,21 +1,24 @@
//! Navigate an endless amount of content with a scrollbar. //! Navigate an endless amount of content with a scrollbar.
use crate::column;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer;
use crate::touch; use crate::touch;
use crate::widget::Column;
use crate::{ use crate::{
Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding, Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
Point, Rectangle, Size, Vector, Widget, Padding, Point, Rectangle, Size, Vector, Widget,
}; };
use std::{f32, hash::Hash, u32}; 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 /// A widget that can vertically display an infinite amount of content with a
/// scrollbar. /// scrollbar.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Scrollable<'a, Message, Renderer: self::Renderer> { pub struct Scrollable<'a, Message, Renderer> {
state: &'a mut State, state: &'a mut State,
height: Length, height: Length,
max_height: u32, max_height: u32,
@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scroller_width: u16, scroller_width: u16,
content: Column<'a, Message, Renderer>, content: Column<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message>>, 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`]. /// Creates a new [`Scrollable`] with the given [`State`].
pub fn new(state: &'a mut State) -> Self { pub fn new(state: &'a mut State) -> Self {
Scrollable { Scrollable {
@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scroller_width: 10, scroller_width: 10,
content: Column::new(), content: Column::new(),
on_scroll: None, 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`] . /// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self 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> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer> for Scrollable<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content) Widget::<Message, Renderer>::width(&self.content)
@ -202,15 +259,7 @@ where
let content = layout.children().next().unwrap(); let content = layout.children().next().unwrap();
let content_bounds = content.bounds(); let content_bounds = content.bounds();
let offset = self.state.offset(bounds, content_bounds); let scrollbar = self.scrollbar(bounds, content_bounds);
let scrollbar = renderer.scrollbar(
bounds,
content_bounds,
offset,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
);
let is_mouse_over_scrollbar = scrollbar let is_mouse_over_scrollbar = scrollbar
.as_ref() .as_ref()
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
@ -374,26 +423,16 @@ where
event::Status::Ignored event::Status::Ignored
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
let bounds = layout.bounds(); let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap(); let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds(); let content_bounds = content_layout.bounds();
let offset = self.state.offset(bounds, content_bounds); let scrollbar = self.scrollbar(bounds, content_bounds);
let scrollbar = renderer.scrollbar(
bounds,
content_bounds,
offset,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
);
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar let is_mouse_over_scrollbar = scrollbar
@ -401,16 +440,18 @@ where
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
.unwrap_or(false); .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 { let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32) Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else { } else {
Point::new(cursor_position.x, -1.0) Point::new(cursor_position.x, -1.0)
}; };
self.content.draw( self.content.mouse_interaction(
renderer,
defaults,
content_layout, content_layout,
cursor_position, cursor_position,
&Rectangle { &Rectangle {
@ -418,20 +459,114 @@ where
..bounds ..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( 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, renderer,
&self.state, style,
bounds, content_layout,
content_layout.bounds(), cursor_position,
is_mouse_over, &Rectangle {
is_mouse_over_scrollbar, y: bounds.y + offset as f32,
scrollbar, ..bounds
offset, },
&self.style, );
content, },
) );
});
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) { fn hash_layout(&self, state: &mut Hasher) {
@ -577,19 +712,19 @@ impl State {
/// The scrollbar of a [`Scrollable`]. /// The scrollbar of a [`Scrollable`].
#[derive(Debug)] #[derive(Debug)]
pub struct Scrollbar { struct Scrollbar {
/// The outer bounds of the scrollable, including the [`Scrollbar`] and /// The outer bounds of the scrollable, including the [`Scrollbar`] and
/// [`Scroller`]. /// [`Scroller`].
pub outer_bounds: Rectangle, outer_bounds: Rectangle,
/// The bounds of the [`Scrollbar`]. /// The bounds of the [`Scrollbar`].
pub bounds: Rectangle, bounds: Rectangle,
/// The margin within the [`Scrollbar`]. /// The margin within the [`Scrollbar`].
pub margin: u16, margin: u16,
/// The bounds of the [`Scroller`]. /// The bounds of the [`Scroller`].
pub scroller: Scroller, scroller: Scroller,
} }
impl Scrollbar { impl Scrollbar {
@ -624,62 +759,15 @@ impl Scrollbar {
/// The handle of a [`Scrollbar`]. /// The handle of a [`Scrollbar`].
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Scroller { struct Scroller {
/// The bounds of the [`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, 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;
} }
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>> impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from( fn from(

View file

@ -4,12 +4,17 @@
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::renderer;
use crate::touch; use crate::touch;
use crate::{ 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 /// An horizontal bar and a handle that selects a single value from a range of
/// values. /// values.
@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};
/// ///
/// # Example /// # 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)] /// #[derive(Clone)]
/// pub enum Message { /// pub enum Message {
/// SliderChanged(f32), /// 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) /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Slider<'a, T, Message, Renderer: self::Renderer> { pub struct Slider<'a, T, Message> {
state: &'a mut State, state: &'a mut State,
range: RangeInclusive<T>, range: RangeInclusive<T>,
step: T, step: T,
@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
on_release: Option<Message>, on_release: Option<Message>,
width: Length, width: Length,
height: u16, 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 where
T: Copy + From<u8> + std::cmp::PartialOrd, T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone, Message: Clone,
Renderer: self::Renderer,
{ {
/// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: u16 = 22;
/// Creates a new [`Slider`]. /// Creates a new [`Slider`].
/// ///
/// It expects: /// It expects:
@ -93,8 +99,8 @@ where
on_change: Box::new(on_change), on_change: Box::new(on_change),
on_release: None, on_release: None,
width: Length::Fill, width: Length::Fill,
height: Renderer::DEFAULT_HEIGHT, height: Self::DEFAULT_HEIGHT,
style: Renderer::Style::default(), style_sheet: Default::default(),
} }
} }
@ -122,8 +128,11 @@ where
} }
/// Sets the style of the [`Slider`]. /// Sets the style of the [`Slider`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
@ -148,11 +157,11 @@ impl State {
} }
impl<'a, T, Message, Renderer> Widget<Message, Renderer> impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for Slider<'a, T, Message, Renderer> for Slider<'a, T, Message>
where where
T: Copy + Into<f64> + num_traits::FromPrimitive, T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone, Message: Clone,
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -246,22 +255,113 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) {
let start = *self.range.start(); let bounds = layout.bounds();
let end = *self.range.end(); let is_mouse_over = bounds.contains(cursor_position);
renderer.draw( let style = if self.state.is_dragging {
layout.bounds(), self.style_sheet.dragging()
cursor_position, } else if is_mouse_over {
start.into() as f32..=end.into() as f32, self.style_sheet.hovered()
self.value.into() as f32, } else {
self.state.is_dragging, self.style_sheet.active()
&self.style, };
)
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) { fn hash_layout(&self, state: &mut Hasher) {
@ -272,48 +372,14 @@ where
} }
} }
/// The renderer of a [`Slider`]. impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>
///
/// 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>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone, Message: 'a + Clone,
Renderer: 'a + self::Renderer, Renderer: 'a + crate::Renderer,
{ {
fn from( fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {
slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(slider) Element::new(slider)
} }
} }

View file

@ -1,9 +1,9 @@
//! Distribute content vertically. //! 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::{ use std::hash::Hash;
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
/// An amount of empty space. /// An amount of empty space.
/// ///
@ -39,7 +39,7 @@ impl Space {
impl<Message, Renderer> Widget<Message, Renderer> for Space impl<Message, Renderer> Widget<Message, Renderer> for Space
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -61,13 +61,12 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, _renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, _layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) {
renderer.draw(layout.bounds())
} }
fn hash_layout(&self, state: &mut Hasher) { 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> impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: crate::Renderer,
Message: 'a, Message: 'a,
{ {
fn from(space: Space) -> Element<'a, Message, Renderer> { fn from(space: Space) -> Element<'a, Message, Renderer> {

View file

@ -1,12 +1,11 @@
//! Display vector graphics in your application. //! Display vector graphics in your application.
use crate::layout; use crate::layout;
use crate::renderer;
use crate::svg::{self, Handle};
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{ use std::hash::Hash;
hash::{Hash, Hasher as _}, use std::path::PathBuf;
path::PathBuf,
sync::Arc,
};
/// A vector graphics image. /// A vector graphics image.
/// ///
@ -52,7 +51,7 @@ impl Svg {
impl<Message, Renderer> Widget<Message, Renderer> for Svg impl<Message, Renderer> Widget<Message, Renderer> for Svg
where where
Renderer: self::Renderer, Renderer: svg::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -90,12 +89,12 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_defaults: &Renderer::Defaults, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) {
renderer.draw(self.handle.clone(), layout) renderer.draw(self.handle.clone(), layout.bounds())
} }
fn hash_layout(&self, state: &mut Hasher) { 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> impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: svg::Renderer,
{ {
fn from(icon: Svg) -> Element<'a, Message, Renderer> { fn from(icon: Svg) -> Element<'a, Message, Renderer> {
Element::new(icon) Element::new(icon)

View file

@ -1,12 +1,12 @@
//! Write some text for your users to read. //! Write some text for your users to read.
use crate::alignment; use crate::alignment;
use crate::layout; use crate::layout;
use crate::renderer;
use crate::text;
use crate::{ use crate::{
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
}; };
pub use iced_core::text::Hit;
use std::hash::Hash; use std::hash::Hash;
/// A paragraph of text. /// A paragraph of text.
@ -14,7 +14,7 @@ use std::hash::Hash;
/// # Example /// # 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!") /// Text::new("I <3 iced!")
/// .color([0.0, 0.0, 1.0]) /// .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) /// ![Text drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[derive(Debug)] #[derive(Debug)]
pub struct Text<Renderer: self::Renderer> { pub struct Text<Renderer: text::Renderer> {
content: String, content: String,
size: Option<u16>, size: Option<u16>,
color: Option<Color>, color: Option<Color>,
@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {
vertical_alignment: alignment::Vertical, 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. /// Create a new fragment of [`Text`] with the given contents.
pub fn new<T: Into<String>>(label: T) -> Self { pub fn new<T: Into<String>>(label: T) -> Self {
Text { Text {
@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer> impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
where where
Renderer: self::Renderer, Renderer: text::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -134,21 +134,22 @@ where
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
defaults: &Renderer::Defaults, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor_position: Point, _cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) {
renderer.draw( draw(
defaults, renderer,
layout.bounds(), style,
layout,
&self.content, &self.content,
self.size.unwrap_or(renderer.default_size()),
self.font, self.font,
self.size,
self.color, self.color,
self.horizontal_alignment, self.horizontal_alignment,
self.vertical_alignment, self.vertical_alignment,
) );
} }
fn hash_layout(&self, state: &mut Hasher) { 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 /// Specifically:
/// able to use [`Text`] in your user interface.
/// ///
/// [renderer]: crate::Renderer /// * If no `size` is provided, the default text size of the `Renderer` will be
pub trait Renderer: crate::Renderer { /// used.
/// The font type used for [`Text`]. /// * If no `color` is provided, the [`renderer::Style::text_color`] will be
type Font: Default + Copy; /// used.
/// * The alignment attributes do not affect the position of the bounds of the
/// Returns the default size of [`Text`]. /// [`Layout`].
fn default_size(&self) -> u16; pub fn draw<Renderer>(
renderer: &mut Renderer,
/// Measures the [`Text`] in the given bounds and returns the minimum style: &renderer::Style,
/// boundaries that can fit the contents. layout: Layout<'_>,
fn measure(
&self,
content: &str, content: &str,
size: u16, font: Renderer::Font,
font: Self::Font, size: Option<u16>,
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>, color: Option<Color>,
horizontal_alignment: alignment::Horizontal, horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical, vertical_alignment: alignment::Vertical,
) -> Self::Output; ) 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>> impl<'a, Message, Renderer> From<Text<Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: self::Renderer + 'a, Renderer: text::Renderer + 'a,
{ {
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> { fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(text) Element::new(text)
} }
} }
impl<Renderer: self::Renderer> Clone for Text<Renderer> { impl<Renderer: text::Renderer> Clone for Text<Renderer> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
content: self.content.clone(), content: self.content.clone(),

View file

@ -11,26 +11,31 @@ pub use value::Value;
use editor::Editor; use editor::Editor;
use crate::alignment;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::keyboard; use crate::keyboard;
use crate::layout; use crate::layout;
use crate::mouse::{self, click}; use crate::mouse::{self, click};
use crate::text; use crate::renderer;
use crate::text::{self, Text};
use crate::touch; use crate::touch;
use crate::{ use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
Size, Widget, Rectangle, Size, Vector, Widget,
}; };
use std::u32; use std::u32;
pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text. /// A field that can be filled with text.
/// ///
/// # Example /// # 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)] /// #[derive(Debug, Clone)]
/// enum Message { /// enum Message {
/// TextInputChanged(String), /// 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) /// ![Text input drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct TextInput<'a, Message, Renderer: self::Renderer> { pub struct TextInput<'a, Message, Renderer: text::Renderer> {
state: &'a mut State, state: &'a mut State,
placeholder: String, placeholder: String,
value: Value, value: Value,
@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
size: Option<u16>, size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>, on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>, on_submit: Option<Message>,
style: Renderer::Style, style_sheet: Box<dyn StyleSheet + 'a>,
} }
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where where
Message: Clone, Message: Clone,
Renderer: self::Renderer, Renderer: text::Renderer,
{ {
/// Creates a new [`TextInput`]. /// Creates a new [`TextInput`].
/// ///
@ -97,7 +102,7 @@ where
size: None, size: None,
on_change: Box::new(on_change), on_change: Box::new(on_change),
on_submit: None, on_submit: None,
style: Renderer::Style::default(), style_sheet: Default::default(),
} }
} }
@ -147,8 +152,11 @@ where
} }
/// Sets the style of the [`TextInput`]. /// Sets the style of the [`TextInput`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { pub fn style(
self.style = style.into(); mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self self
} }
@ -160,7 +168,7 @@ where
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where where
Renderer: self::Renderer, Renderer: text::Renderer,
{ {
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided. /// [`Value`] if provided.
@ -170,38 +178,166 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
value: Option<&Value>, value: Option<&Value>,
) -> Renderer::Output { ) {
let value = value.unwrap_or(&self.value); 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 bounds = layout.bounds();
let text_bounds = layout.children().next().unwrap().bounds(); let text_bounds = layout.children().next().unwrap().bounds();
if self.is_secure { let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer, let style = if self.state.is_focused() {
bounds, self.style_sheet.focused()
text_bounds, } else if is_mouse_over {
cursor_position, self.style_sheet.hovered()
self.font,
self.size.unwrap_or(renderer.default_size()),
&self.placeholder,
&value.secure(),
&self.state,
&self.style,
)
} else { } else {
self::Renderer::draw( self.style_sheet.active()
renderer, };
renderer.fill_quad(
renderer::Quad {
bounds, bounds,
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, text_bounds,
cursor_position, &value,
size,
position,
self.font, self.font,
self.size.unwrap_or(renderer.default_size()), );
&self.placeholder,
value, (
&self.state, Some((
&self.style, 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> for TextInput<'a, Message, Renderer>
where where
Message: Clone, Message: Clone,
Renderer: self::Renderer, Renderer: text::Renderer,
{ {
fn width(&self) -> Length { fn width(&self) -> Length {
self.width self.width
@ -275,7 +411,8 @@ where
self.value.clone() self.value.clone()
}; };
renderer.find_cursor_position( find_cursor_position(
renderer,
text_layout.bounds(), text_layout.bounds(),
self.font, self.font,
self.size, self.size,
@ -294,8 +431,8 @@ where
if self.is_secure { if self.is_secure {
self.state.cursor.select_all(&self.value); self.state.cursor.select_all(&self.value);
} else { } else {
let position = renderer let position = find_cursor_position(
.find_cursor_position( renderer,
text_layout.bounds(), text_layout.bounds(),
self.font, self.font,
self.size, self.size,
@ -341,8 +478,8 @@ where
self.value.clone() self.value.clone()
}; };
let position = renderer let position = find_cursor_position(
.find_cursor_position( renderer,
text_layout.bounds(), text_layout.bounds(),
self.font, self.font,
self.size, self.size,
@ -621,14 +758,27 @@ where
event::Status::Ignored event::Status::Ignored
} }
fn draw( fn mouse_interaction(
&self, &self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> Renderer::Output { ) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Text
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
renderer: &mut Renderer,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
self.draw(renderer, layout, cursor_position, None) 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>> impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Message: 'a + Clone, Message: 'a + Clone,
Renderer: 'a + self::Renderer, Renderer: 'a + text::Renderer,
{ {
fn from( fn from(
text_input: TextInput<'a, Message, Renderer>, 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)
}

Some files were not shown because too many files have changed in this diff Show more