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

View file

@ -10,12 +10,11 @@ mod rainbow {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
use iced_graphics::{
triangle::{Mesh2D, Vertex2D},
Backend, Defaults, Primitive, Renderer,
};
use iced_graphics::renderer::{self, Renderer};
use iced_graphics::{Backend, Primitive};
use iced_native::{
layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size,
Vector, Widget,
};
@ -53,12 +52,15 @@ mod rainbow {
fn draw(
&self,
_renderer: &mut Renderer<B>,
_defaults: &Defaults,
renderer: &mut Renderer<B>,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
) {
use iced_graphics::triangle::{Mesh2D, Vertex2D};
use iced_native::Renderer as _;
let b = layout.bounds();
// R O Y G B I V
@ -88,65 +90,63 @@ mod rainbow {
let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0];
(
Primitive::Translate {
translation: Vector::new(b.x, b.y),
content: Box::new(Primitive::Mesh2D {
size: b.size(),
buffers: Mesh2D {
vertices: vec![
Vertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex2D {
position: posn_tl,
color: color_r,
},
Vertex2D {
position: posn_t,
color: color_o,
},
Vertex2D {
position: posn_tr,
color: color_y,
},
Vertex2D {
position: posn_r,
color: color_g,
},
Vertex2D {
position: posn_br,
color: color_gb,
},
Vertex2D {
position: posn_b,
color: color_b,
},
Vertex2D {
position: posn_bl,
color: color_i,
},
Vertex2D {
position: posn_l,
color: color_v,
},
],
indices: vec![
0, 1, 2, // TL
0, 2, 3, // T
0, 3, 4, // TR
0, 4, 5, // R
0, 5, 6, // BR
0, 6, 7, // B
0, 7, 8, // BL
0, 8, 1, // L
],
let mesh = Primitive::Mesh2D {
size: b.size(),
buffers: Mesh2D {
vertices: vec![
Vertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
},
}),
Vertex2D {
position: posn_tl,
color: color_r,
},
Vertex2D {
position: posn_t,
color: color_o,
},
Vertex2D {
position: posn_tr,
color: color_y,
},
Vertex2D {
position: posn_r,
color: color_g,
},
Vertex2D {
position: posn_br,
color: color_gb,
},
Vertex2D {
position: posn_b,
color: color_b,
},
Vertex2D {
position: posn_bl,
color: color_i,
},
Vertex2D {
position: posn_l,
color: color_v,
},
],
indices: vec![
0, 1, 2, // TL
0, 2, 3, // T
0, 3, 4, // TR
0, 4, 5, // R
0, 5, 6, // BR
0, 6, 7, // B
0, 7, 8, // BL
0, 8, 1, // L
],
},
mouse::Interaction::default(),
)
};
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
renderer.draw_primitive(mesh);
});
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

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 {
match theme {
Theme::Light => Default::default(),
@ -25,7 +25,7 @@ impl From<Theme> for Box<dyn container::StyleSheet> {
}
}
impl From<Theme> for Box<dyn radio::StyleSheet> {
impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@ -34,7 +34,7 @@ impl From<Theme> for Box<dyn radio::StyleSheet> {
}
}
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),

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 {
match theme {
Theme::Light => Default::default(),
@ -185,7 +185,7 @@ mod style {
}
}
impl From<Theme> for Box<dyn radio::StyleSheet> {
impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@ -194,7 +194,7 @@ mod style {
}
}
impl From<Theme> for Box<dyn text_input::StyleSheet> {
impl<'a> From<Theme> for Box<dyn text_input::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@ -203,7 +203,7 @@ mod style {
}
}
impl From<Theme> for Box<dyn button::StyleSheet> {
impl<'a> From<Theme> for Box<dyn button::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => light::Button.into(),
@ -212,7 +212,7 @@ mod style {
}
}
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@ -221,7 +221,7 @@ mod style {
}
}
impl From<Theme> for Box<dyn slider::StyleSheet> {
impl<'a> From<Theme> for Box<dyn slider::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
@ -239,7 +239,7 @@ mod style {
}
}
impl From<Theme> for Box<dyn checkbox::StyleSheet> {
impl<'a> From<Theme> for Box<dyn checkbox::StyleSheet + 'a> {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),

View file

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

View file

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

View file

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

View file

@ -66,13 +66,14 @@ pub mod qr_code;
#[doc(no_inline)]
pub use qr_code::QRCode;
pub use iced_native::{Image, Space};
pub use iced_native::widget::{Image, Space};
/// A container that distributes its contents vertically.
pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>;
pub type Column<'a, Message> =
iced_native::widget::Column<'a, Message, Renderer>;
/// A container that distributes its contents horizontally.
pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>;
pub type Row<'a, Message> = iced_native::widget::Row<'a, Message, Renderer>;
/// A paragraph of text.
pub type Text = iced_native::Text<Renderer>;
pub type Text = iced_native::widget::Text<Renderer>;

View file

@ -4,9 +4,10 @@
use crate::Renderer;
pub use iced_graphics::button::{Style, StyleSheet};
pub use iced_native::button::State;
pub use iced_native::widget::button::State;
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>;
pub type Button<'a, Message> =
iced_native::widget::Button<'a, Message, Renderer>;

View file

@ -6,4 +6,5 @@ pub use iced_graphics::checkbox::{Style, StyleSheet};
/// A box that can be checked.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>;
pub type Checkbox<'a, Message> =
iced_native::widget::Checkbox<'a, Message, Renderer>;

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
/// `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)
///
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
pub type PaneGrid<'a, Message> =
iced_native::widget::PaneGrid<'a, Message, Renderer>;
/// The content of a [`Pane`].
pub type Content<'a, Message> =
iced_native::pane_grid::Content<'a, Message, Renderer>;
iced_native::widget::pane_grid::Content<'a, Message, Renderer>;
/// The title bar of a [`Pane`].
pub type TitleBar<'a, Message> =
iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>;

View file

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

View file

@ -2,12 +2,5 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
use crate::Renderer;
pub use iced_graphics::progress_bar::{Style, StyleSheet};
/// A bar that displays progress.
///
/// This is an alias of an `iced_native` progress bar with an
/// `iced_wgpu::Renderer`.
pub type ProgressBar = iced_native::ProgressBar<Renderer>;
pub use iced_graphics::progress_bar::*;

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
/// `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.
use crate::Renderer;
pub use iced_graphics::rule::{FillMode, Style, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
///
/// This is an alias of an `iced_native` rule with an `iced_glow::Renderer`.
pub type Rule = iced_native::Rule<Renderer>;
pub use iced_graphics::rule::*;

View file

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

View file

@ -1,13 +1,5 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
use crate::Renderer;
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
pub use iced_native::slider::State;
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
pub use iced_native::widget::slider::{Slider, State};

View file

@ -4,9 +4,10 @@
use crate::Renderer;
pub use iced_graphics::text_input::{Style, StyleSheet};
pub use iced_native::text_input::State;
pub use iced_native::widget::text_input::State;
/// A field that can be filled with text.
///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>;
pub type TextInput<'a, Message> =
iced_native::widget::TextInput<'a, Message, Renderer>;

View file

@ -6,4 +6,5 @@ pub use iced_graphics::toggler::{Style, StyleSheet};
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
pub type Toggler<'a, Message> =
iced_native::widget::Toggler<'a, Message, Renderer>;

View file

@ -1,6 +1,6 @@
//! Display a widget over another.
/// A widget allowing the selection of a single value from a list of options.
pub type Tooltip<'a, Message> =
iced_native::Tooltip<'a, Message, crate::Renderer>;
iced_native::widget::Tooltip<'a, Message, crate::Renderer>;
pub use iced_native::tooltip::Position;
pub use iced_native::widget::tooltip::Position;

View file

@ -3,7 +3,6 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
use core::ffi::c_void;
use glow::HasContext;
use iced_graphics::{Antialiasing, Size};
use iced_native::mouse;
/// A window graphics backend for iced powered by `glow`.
#[allow(missing_debug_implementations)]
@ -59,14 +58,13 @@ impl iced_graphics::window::GLCompositor for Compositor {
}
}
fn draw<T: AsRef<str>>(
fn present<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction {
) {
let gl = &self.gl;
let [r, g, b, a] = color.into_linear();
@ -76,6 +74,8 @@ impl iced_graphics::window::GLCompositor for Compositor {
gl.clear(glow::COLOR_BUFFER_BIT);
}
renderer.backend_mut().draw(gl, viewport, output, overlay)
renderer.with_primitives(|backend, primitive| {
backend.present(gl, primitive, viewport, overlay);
});
}
}

View file

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

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

View file

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

View file

@ -1,116 +1,3 @@
//! Build and show dropdown menus.
use crate::alignment;
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::{mouse, overlay, Color, Font, Padding, Point, Rectangle};
pub use iced_style::menu::Style;
impl<B> overlay::menu::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Style;
fn decorate(
&mut self,
bounds: Rectangle,
_cursor_position: Point,
style: &Style,
(primitives, mouse_cursor): Self::Output,
) -> Self::Output {
(
Primitive::Group {
primitives: vec![
Primitive::Quad {
bounds,
background: style.background,
border_color: style.border_color,
border_width: style.border_width,
border_radius: 0.0,
},
primitives,
],
},
mouse_cursor,
)
}
fn draw<T: ToString>(
&mut self,
bounds: Rectangle,
cursor_position: Point,
viewport: &Rectangle,
options: &[T],
hovered_option: Option<usize>,
padding: Padding,
text_size: u16,
font: Font,
style: &Style,
) -> Self::Output {
use std::f32;
let is_mouse_over = bounds.contains(cursor_position);
let option_height = (text_size + padding.vertical()) as usize;
let mut primitives = Vec::new();
let offset = viewport.y - bounds.y;
let start = (offset / option_height as f32) as usize;
let end =
((offset + viewport.height) / option_height as f32).ceil() as usize;
let visible_options = &options[start..end.min(options.len())];
for (i, option) in visible_options.iter().enumerate() {
let i = start + i;
let is_selected = hovered_option == Some(i);
let bounds = Rectangle {
x: bounds.x,
y: bounds.y + (option_height * i) as f32,
width: bounds.width,
height: f32::from(text_size + padding.vertical()),
};
if is_selected {
primitives.push(Primitive::Quad {
bounds,
background: style.selected_background,
border_color: Color::TRANSPARENT,
border_width: 0.0,
border_radius: 0.0,
});
}
primitives.push(Primitive::Text {
content: option.to_string(),
bounds: Rectangle {
x: bounds.x + padding.left as f32,
y: bounds.center_y(),
width: f32::INFINITY,
..bounds
},
size: f32::from(text_size),
font,
color: if is_selected {
style.selected_text_color
} else {
style.text_color
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
});
}
(
Primitive::Group { primitives },
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}

View file

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

View file

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

View file

@ -1,111 +1,12 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
use crate::defaults::{self, Defaults};
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::{
Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
};
use crate::Renderer;
pub use iced_native::button::State;
pub use iced_style::button::{Style, StyleSheet};
pub use iced_native::widget::button::{State, Style, StyleSheet};
/// A widget that produces a message when clicked.
///
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
pub type Button<'a, Message, Backend> =
iced_native::Button<'a, Message, Renderer<Backend>>;
impl<B> iced_native::button::Renderer for Renderer<B>
where
B: Backend,
{
const DEFAULT_PADDING: Padding = Padding::new(5);
type Style = Box<dyn StyleSheet>;
fn draw<Message>(
&mut self,
_defaults: &Defaults,
bounds: Rectangle,
cursor_position: Point,
is_disabled: bool,
is_pressed: bool,
style: &Box<dyn StyleSheet>,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
let styling = if is_disabled {
style.disabled()
} else if is_mouse_over {
if is_pressed {
style.pressed()
} else {
style.hovered()
}
} else {
style.active()
};
let (content, _) = content.draw(
self,
&Defaults {
text: defaults::Text {
color: styling.text_color,
},
},
content_layout,
cursor_position,
&bounds,
);
(
if styling.background.is_some() || styling.border_width > 0.0 {
let background = Primitive::Quad {
bounds,
background: styling
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
border_radius: styling.border_radius,
border_width: styling.border_width,
border_color: styling.border_color,
};
if styling.shadow_offset == Vector::default() {
Primitive::Group {
primitives: vec![background, content],
}
} else {
// TODO: Implement proper shadow support
let shadow = Primitive::Quad {
bounds: Rectangle {
x: bounds.x + styling.shadow_offset.x,
y: bounds.y + styling.shadow_offset.y,
..bounds
},
background: Background::Color(
[0.0, 0.0, 0.0, 0.5].into(),
),
border_radius: styling.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
};
Primitive::Group {
primitives: vec![shadow, background, content],
}
}
} else {
content
},
if is_mouse_over && !is_disabled {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}
iced_native::widget::Button<'a, Message, Renderer<Backend>>;

View file

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

View file

@ -1,77 +1,10 @@
//! Show toggle controls using checkboxes.
use crate::alignment;
use crate::backend::{self, Backend};
use crate::{Primitive, Rectangle, Renderer};
use iced_native::checkbox;
use iced_native::mouse;
use crate::Renderer;
pub use iced_style::checkbox::{Style, StyleSheet};
/// A box that can be checked.
///
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
pub type Checkbox<Message, Backend> =
iced_native::Checkbox<Message, Renderer<Backend>>;
impl<B> checkbox::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_SIZE: u16 = 20;
const DEFAULT_SPACING: u16 = 15;
fn draw(
&mut self,
bounds: Rectangle,
is_checked: bool,
is_mouse_over: bool,
(label, _): Self::Output,
style_sheet: &Self::Style,
) -> Self::Output {
let style = if is_mouse_over {
style_sheet.hovered(is_checked)
} else {
style_sheet.active(is_checked)
};
let checkbox = Primitive::Quad {
bounds,
background: style.background,
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
};
(
Primitive::Group {
primitives: if is_checked {
let check = Primitive::Text {
content: B::CHECKMARK_ICON.to_string(),
font: B::ICON_FONT,
size: bounds.height * 0.7,
bounds: Rectangle {
x: bounds.center_x(),
y: bounds.center_y(),
..bounds
},
color: style.checkmark_color,
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
};
vec![checkbox, check, label]
} else {
vec![checkbox, label]
},
},
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}
pub type Checkbox<'a, Message, Backend> =
iced_native::widget::Checkbox<'a, Message, Renderer<Backend>>;

View file

@ -1,49 +1,5 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::column;
use iced_native::mouse;
use iced_native::{Element, Layout, Point, Rectangle};
use crate::Renderer;
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> =
iced_native::Column<'a, Message, Renderer<Backend>>;
impl<B> column::Renderer for Renderer<B>
where
B: Backend,
{
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
(
Primitive::Group {
primitives: content
.iter()
.zip(layout.children())
.map(|(child, layout)| {
let (primitive, new_mouse_interaction) = child.draw(
self,
defaults,
layout,
cursor_position,
viewport,
);
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
}
primitive
})
.collect(),
},
mouse_interaction,
)
}
}
iced_native::widget::Column<'a, Message, Renderer<Backend>>;

View file

@ -1,8 +1,5 @@
//! Decorate content and apply alignment.
use crate::container;
use crate::defaults::{self, Defaults};
use crate::{Backend, Primitive, Renderer};
use iced_native::{Background, Color, Element, Layout, Point, Rectangle};
use crate::Renderer;
pub use iced_style::container::{Style, StyleSheet};
@ -11,68 +8,4 @@ pub use iced_style::container::{Style, StyleSheet};
/// This is an alias of an `iced_native` container with a default
/// `Renderer`.
pub type Container<'a, Message, Backend> =
iced_native::Container<'a, Message, Renderer<Backend>>;
impl<B> iced_native::container::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn container::StyleSheet>;
fn draw<Message>(
&mut self,
defaults: &Defaults,
bounds: Rectangle,
cursor_position: Point,
viewport: &Rectangle,
style_sheet: &Self::Style,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
let style = style_sheet.style();
let defaults = Defaults {
text: defaults::Text {
color: style.text_color.unwrap_or(defaults.text.color),
},
};
let (content, mouse_interaction) = content.draw(
self,
&defaults,
content_layout,
cursor_position,
viewport,
);
if let Some(background) = background(bounds, &style) {
(
Primitive::Group {
primitives: vec![background, content],
},
mouse_interaction,
)
} else {
(content, mouse_interaction)
}
}
}
pub(crate) fn background(
bounds: Rectangle,
style: &container::Style,
) -> Option<Primitive> {
if style.background.is_some() || style.border_width > 0.0 {
Some(Primitive::Quad {
bounds,
background: style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
})
} else {
None
}
}
iced_native::widget::Container<'a, Message, Renderer<Backend>>;

View file

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

View file

@ -1,55 +1,2 @@
//! Zoom and pan on an image.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::image;
use iced_native::image::viewer;
use iced_native::mouse;
use iced_native::{Rectangle, Size, Vector};
impl<B> viewer::Renderer for Renderer<B>
where
B: Backend + backend::Image,
{
fn draw(
&mut self,
state: &viewer::State,
bounds: Rectangle,
image_size: Size,
translation: Vector,
handle: image::Handle,
is_mouse_over: bool,
) -> Self::Output {
(
{
Primitive::Clip {
bounds,
content: Box::new(Primitive::Translate {
translation,
content: Box::new(Primitive::Image {
handle,
bounds: Rectangle {
x: bounds.x,
y: bounds.y,
..Rectangle::with_size(image_size)
},
}),
}),
offset: Vector::new(0, 0),
}
},
{
if state.is_cursor_grabbed() {
mouse::Interaction::Grabbing
} else if is_mouse_over
&& (image_size.width > bounds.width
|| image_size.height > bounds.height)
{
mouse::Interaction::Grab
} else {
mouse::Interaction::Idle
}
},
)
}
}
pub use iced_native::widget::image::Viewer;

View file

@ -7,14 +7,9 @@
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::defaults;
use crate::{Backend, Color, Primitive, Renderer};
use iced_native::container;
use iced_native::mouse;
use iced_native::pane_grid;
use iced_native::{Element, Layout, Point, Rectangle, Vector};
use crate::Renderer;
pub use iced_native::pane_grid::{
pub use iced_native::widget::pane_grid::{
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
ResizeEvent, Split, State, TitleBar,
};
@ -28,277 +23,4 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
///
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
pub type PaneGrid<'a, Message, Backend> =
iced_native::PaneGrid<'a, Message, Renderer<Backend>>;
impl<B> pane_grid::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Content<'_, Message, Self>)],
dragging: Option<(Pane, Point)>,
resizing: Option<(Axis, Rectangle, bool)>,
layout: Layout<'_>,
style_sheet: &<Self as pane_grid::Renderer>::Style,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output {
let pane_cursor_position = if dragging.is_some() {
// TODO: Remove once cursor availability is encoded in the type
// system
Point::new(-1.0, -1.0)
} else {
cursor_position
};
let mut mouse_interaction = mouse::Interaction::default();
let mut dragged_pane = None;
let mut panes: Vec<_> = content
.iter()
.zip(layout.children())
.enumerate()
.map(|(i, ((id, pane), layout))| {
let (primitive, new_mouse_interaction) = pane.draw(
self,
defaults,
layout,
pane_cursor_position,
viewport,
);
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
}
if let Some((dragging, origin)) = dragging {
if *id == dragging {
dragged_pane = Some((i, layout, origin));
}
}
primitive
})
.collect();
let mut primitives = if let Some((index, layout, origin)) = dragged_pane
{
let pane = panes.remove(index);
let bounds = layout.bounds();
// TODO: Fix once proper layering is implemented.
// This is a pretty hacky way to achieve layering.
let clip = Primitive::Clip {
bounds: Rectangle {
x: cursor_position.x - origin.x,
y: cursor_position.y - origin.y,
width: bounds.width + 0.5,
height: bounds.height + 0.5,
},
offset: Vector::new(0, 0),
content: Box::new(Primitive::Translate {
translation: Vector::new(
cursor_position.x - bounds.x - origin.x,
cursor_position.y - bounds.y - origin.y,
),
content: Box::new(pane),
}),
};
panes.push(clip);
panes
} else {
panes
};
let (primitives, mouse_interaction) =
if let Some((axis, split_region, is_picked)) = resizing {
let highlight = if is_picked {
style_sheet.picked_split()
} else {
style_sheet.hovered_split()
};
if let Some(highlight) = highlight {
primitives.push(Primitive::Quad {
bounds: match axis {
Axis::Horizontal => Rectangle {
x: split_region.x,
y: (split_region.y
+ (split_region.height - highlight.width)
/ 2.0)
.round(),
width: split_region.width,
height: highlight.width,
},
Axis::Vertical => Rectangle {
x: (split_region.x
+ (split_region.width - highlight.width)
/ 2.0)
.round(),
y: split_region.y,
width: highlight.width,
height: split_region.height,
},
},
background: highlight.color.into(),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
});
}
(
primitives,
match axis {
Axis::Horizontal => {
mouse::Interaction::ResizingVertically
}
Axis::Vertical => {
mouse::Interaction::ResizingHorizontally
}
},
)
} else {
(primitives, mouse_interaction)
};
(
Primitive::Group { primitives },
if dragging.is_some() {
mouse::Interaction::Grabbing
} else {
mouse_interaction
},
)
}
fn draw_pane<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
style_sheet: &<Self as container::Renderer>::Style,
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
body: (&Element<'_, Message, Self>, Layout<'_>),
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output {
let style = style_sheet.style();
let (body, body_layout) = body;
let (body_primitive, body_interaction) =
body.draw(self, defaults, body_layout, cursor_position, viewport);
let background = crate::widget::container::background(bounds, &style);
if let Some((title_bar, title_bar_layout)) = title_bar {
let show_controls = bounds.contains(cursor_position);
let is_over_pick_area =
title_bar.is_over_pick_area(title_bar_layout, cursor_position);
let (title_bar_primitive, title_bar_interaction) = title_bar.draw(
self,
defaults,
title_bar_layout,
cursor_position,
viewport,
show_controls,
);
(
Primitive::Group {
primitives: vec![
background.unwrap_or(Primitive::None),
title_bar_primitive,
body_primitive,
],
},
if title_bar_interaction > body_interaction {
title_bar_interaction
} else if is_over_pick_area {
mouse::Interaction::Grab
} else {
body_interaction
},
)
} else {
(
if let Some(background) = background {
Primitive::Group {
primitives: vec![background, body_primitive],
}
} else {
body_primitive
},
body_interaction,
)
}
}
fn draw_title_bar<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
style_sheet: &<Self as container::Renderer>::Style,
content: (&Element<'_, Message, Self>, Layout<'_>),
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output {
let style = style_sheet.style();
let (title_content, title_layout) = content;
let defaults = Self::Defaults {
text: defaults::Text {
color: style.text_color.unwrap_or(defaults.text.color),
},
};
let background = crate::widget::container::background(bounds, &style);
let (title_primitive, title_interaction) = title_content.draw(
self,
&defaults,
title_layout,
cursor_position,
viewport,
);
if let Some((controls, controls_layout)) = controls {
let (controls_primitive, controls_interaction) = controls.draw(
self,
&defaults,
controls_layout,
cursor_position,
viewport,
);
(
Primitive::Group {
primitives: vec![
background.unwrap_or(Primitive::None),
title_primitive,
controls_primitive,
],
},
controls_interaction.max(title_interaction),
)
} else {
(
if let Some(background) = background {
Primitive::Group {
primitives: vec![background, title_primitive],
}
} else {
title_primitive
},
title_interaction,
)
}
}
}
iced_native::widget::PaneGrid<'a, Message, Renderer<Backend>>;

View file

@ -1,103 +1,9 @@
//! Display a dropdown list of selectable values.
use crate::alignment;
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use crate::Renderer;
use iced_native::{mouse, Font, Padding, Point, Rectangle};
use iced_style::menu;
pub use iced_native::pick_list::State;
pub use iced_native::widget::pick_list::State;
pub use iced_style::pick_list::{Style, StyleSheet};
/// A widget allowing the selection of a single value from a list of options.
pub type PickList<'a, T, Message, Backend> =
iced_native::PickList<'a, T, Message, Renderer<Backend>>;
impl<B> iced_native::pick_list::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_PADDING: Padding = Padding::new(5);
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
style.menu()
}
fn draw(
&mut self,
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
placeholder: Option<&str>,
padding: Padding,
text_size: u16,
font: Font,
style: &Box<dyn StyleSheet>,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
let is_selected = selected.is_some();
let style = if is_mouse_over {
style.hovered()
} else {
style.active()
};
let background = Primitive::Quad {
bounds,
background: style.background,
border_color: style.border_color,
border_width: style.border_width,
border_radius: style.border_radius,
};
let arrow_down = Primitive::Text {
content: B::ARROW_DOWN_ICON.to_string(),
font: B::ICON_FONT,
size: bounds.height * style.icon_size,
bounds: Rectangle {
x: bounds.x + bounds.width - f32::from(padding.horizontal()),
y: bounds.center_y(),
..bounds
},
color: style.text_color,
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Center,
};
(
Primitive::Group {
primitives: if let Some(label) =
selected.or_else(|| placeholder.map(str::to_string))
{
let label = Primitive::Text {
content: label,
size: f32::from(text_size),
font,
color: is_selected
.then(|| style.text_color)
.unwrap_or(style.placeholder_color),
bounds: Rectangle {
x: bounds.x + f32::from(padding.left),
y: bounds.center_y(),
..bounds
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
};
vec![background, label, arrow_down]
} else {
vec![background, arrow_down]
},
},
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}
iced_native::widget::PickList<'a, T, Message, Renderer<Backend>>;

View file

@ -2,73 +2,4 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::progress_bar;
use iced_native::{Color, Rectangle};
pub use iced_style::progress_bar::{Style, StyleSheet};
/// A bar that displays progress.
///
/// This is an alias of an `iced_native` progress bar with an
/// `iced_wgpu::Renderer`.
pub type ProgressBar<Backend> = iced_native::ProgressBar<Renderer<Backend>>;
impl<B> progress_bar::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_HEIGHT: u16 = 30;
fn draw(
&self,
bounds: Rectangle,
range: std::ops::RangeInclusive<f32>,
value: f32,
style_sheet: &Self::Style,
) -> Self::Output {
let style = style_sheet.style();
let (range_start, range_end) = range.into_inner();
let active_progress_width = if range_start >= range_end {
0.0
} else {
bounds.width * (value - range_start) / (range_end - range_start)
};
let background = Primitive::Group {
primitives: vec![Primitive::Quad {
bounds: Rectangle { ..bounds },
background: style.background,
border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}],
};
(
if active_progress_width > 0.0 {
let bar = Primitive::Quad {
bounds: Rectangle {
width: active_progress_width,
..bounds
},
background: style.bar,
border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
};
Primitive::Group {
primitives: vec![background, bar],
}
} else {
background
},
mouse::Interaction::default(),
)
}
}
pub use iced_native::widget::progress_bar::*;

View file

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

View file

@ -1,8 +1,5 @@
//! Create choices using radio buttons.
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::radio;
use iced_native::{Background, Color, Rectangle};
use crate::Renderer;
pub use iced_style::radio::{Style, StyleSheet};
@ -10,69 +7,5 @@ pub use iced_style::radio::{Style, StyleSheet};
///
/// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`.
pub type Radio<Message, Backend> =
iced_native::Radio<Message, Renderer<Backend>>;
impl<B> radio::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_SIZE: u16 = 28;
const DEFAULT_SPACING: u16 = 15;
fn draw(
&mut self,
bounds: Rectangle,
is_selected: bool,
is_mouse_over: bool,
(label, _): Self::Output,
style_sheet: &Self::Style,
) -> Self::Output {
let style = if is_mouse_over {
style_sheet.hovered()
} else {
style_sheet.active()
};
let size = bounds.width;
let dot_size = size / 2.0;
let radio = Primitive::Quad {
bounds,
background: style.background,
border_radius: size / 2.0,
border_width: style.border_width,
border_color: style.border_color,
};
(
Primitive::Group {
primitives: if is_selected {
let radio_circle = Primitive::Quad {
bounds: Rectangle {
x: bounds.x + dot_size / 2.0,
y: bounds.y + dot_size / 2.0,
width: bounds.width - dot_size,
height: bounds.height - dot_size,
},
background: Background::Color(style.dot_color),
border_radius: dot_size / 2.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
};
vec![radio, radio_circle, label]
} else {
vec![radio, label]
},
},
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}
pub type Radio<'a, Message, Backend> =
iced_native::widget::Radio<'a, Message, Renderer<Backend>>;

View file

@ -1,49 +1,5 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::row;
use iced_native::{Element, Layout, Point, Rectangle};
use crate::Renderer;
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> =
iced_native::Row<'a, Message, Renderer<Backend>>;
impl<B> row::Renderer for Renderer<B>
where
B: Backend,
{
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
(
Primitive::Group {
primitives: content
.iter()
.zip(layout.children())
.map(|(child, layout)| {
let (primitive, new_mouse_interaction) = child.draw(
self,
defaults,
layout,
cursor_position,
viewport,
);
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
}
primitive
})
.collect(),
},
mouse_interaction,
)
}
}
iced_native::widget::Row<'a, Message, Renderer<Backend>>;

View file

@ -1,73 +1,3 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::rule;
use iced_native::{Background, Color, Rectangle};
pub use iced_style::rule::{FillMode, Style, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
///
/// This is an alias of an `iced_native` rule with an `iced_graphics::Renderer`.
pub type Rule<Backend> = iced_native::Rule<Renderer<Backend>>;
impl<B> rule::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
fn draw(
&mut self,
bounds: Rectangle,
style_sheet: &Self::Style,
is_horizontal: bool,
) -> Self::Output {
let style = style_sheet.style();
let line = if is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
- (style.width as f32 / 2.0))
.round();
let (offset, line_width) = style.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset;
Primitive::Quad {
bounds: Rectangle {
x: line_x,
y: line_y,
width: line_width,
height: style.width as f32,
},
background: Background::Color(style.color),
border_radius: style.radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
} else {
let line_x = (bounds.x + (bounds.width / 2.0)
- (style.width as f32 / 2.0))
.round();
let (offset, line_height) = style.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset;
Primitive::Quad {
bounds: Rectangle {
x: line_x,
y: line_y,
width: style.width as f32,
height: line_height,
},
background: Background::Color(style.color),
border_radius: style.radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
};
(line, mouse::Interaction::default())
}
}
pub use iced_native::widget::rule::*;

View file

@ -1,10 +1,7 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::scrollable;
use iced_native::{Background, Color, Rectangle, Vector};
use crate::Renderer;
pub use iced_native::scrollable::State;
pub use iced_native::widget::scrollable::State;
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
/// A widget that can vertically display an infinite amount of content
@ -13,146 +10,4 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
/// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`.
pub type Scrollable<'a, Message, Backend> =
iced_native::Scrollable<'a, Message, Renderer<Backend>>;
impl<B> scrollable::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn iced_style::scrollable::StyleSheet>;
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
if content_bounds.height > bounds.height {
let outer_width =
scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
let outer_bounds = Rectangle {
x: bounds.x + bounds.width - outer_width as f32,
y: bounds.y,
width: outer_width as f32,
height: bounds.height,
};
let scrollbar_bounds = Rectangle {
x: bounds.x + bounds.width
- f32::from(outer_width / 2 + scrollbar_width / 2),
y: bounds.y,
width: scrollbar_width as f32,
height: bounds.height,
};
let ratio = bounds.height / content_bounds.height;
let scroller_height = bounds.height * ratio;
let y_offset = offset as f32 * ratio;
let scroller_bounds = Rectangle {
x: bounds.x + bounds.width
- f32::from(outer_width / 2 + scroller_width / 2),
y: scrollbar_bounds.y + y_offset,
width: scroller_width as f32,
height: scroller_height,
};
Some(scrollable::Scrollbar {
outer_bounds,
bounds: scrollbar_bounds,
margin: scrollbar_margin,
scroller: scrollable::Scroller {
bounds: scroller_bounds,
},
})
} else {
None
}
}
fn draw(
&mut self,
state: &scrollable::State,
bounds: Rectangle,
_content_bounds: Rectangle,
is_mouse_over: bool,
is_mouse_over_scrollbar: bool,
scrollbar: Option<scrollable::Scrollbar>,
offset: u32,
style_sheet: &Self::Style,
(content, mouse_interaction): Self::Output,
) -> Self::Output {
(
if let Some(scrollbar) = scrollbar {
let clip = Primitive::Clip {
bounds,
offset: Vector::new(0, offset),
content: Box::new(content),
};
let style = if state.is_scroller_grabbed() {
style_sheet.dragging()
} else if is_mouse_over_scrollbar {
style_sheet.hovered()
} else {
style_sheet.active()
};
let is_scrollbar_visible =
style.background.is_some() || style.border_width > 0.0;
let scroller = if is_mouse_over
|| state.is_scroller_grabbed()
|| is_scrollbar_visible
{
Primitive::Quad {
bounds: scrollbar.scroller.bounds,
background: Background::Color(style.scroller.color),
border_radius: style.scroller.border_radius,
border_width: style.scroller.border_width,
border_color: style.scroller.border_color,
}
} else {
Primitive::None
};
let scrollbar = if is_scrollbar_visible {
Primitive::Quad {
bounds: scrollbar.bounds,
background: style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
}
} else {
Primitive::None
};
let scroll = Primitive::Clip {
bounds,
offset: Vector::new(0, 0),
content: Box::new(Primitive::Group {
primitives: vec![scrollbar, scroller],
}),
};
Primitive::Group {
primitives: vec![clip, scroll],
}
} else {
content
},
if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
mouse::Interaction::Idle
} else {
mouse_interaction
},
)
}
}
iced_native::widget::Scrollable<'a, Message, Renderer<Backend>>;

View file

@ -1,123 +1,5 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::slider;
use iced_native::{Background, Color, Point, Rectangle};
pub use iced_native::slider::State;
pub use iced_native::widget::slider::{Slider, State};
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
pub type Slider<'a, T, Message, Backend> =
iced_native::Slider<'a, T, Message, Renderer<Backend>>;
impl<B> slider::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_HEIGHT: u16 = 22;
fn draw(
&mut self,
bounds: Rectangle,
cursor_position: Point,
range: std::ops::RangeInclusive<f32>,
value: f32,
is_dragging: bool,
style_sheet: &Self::Style,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
let style = if is_dragging {
style_sheet.dragging()
} else if is_mouse_over {
style_sheet.hovered()
} else {
style_sheet.active()
};
let rail_y = bounds.y + (bounds.height / 2.0).round();
let (rail_top, rail_bottom) = (
Primitive::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y,
width: bounds.width,
height: 2.0,
},
background: Background::Color(style.rail_colors.0),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Primitive::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y + 2.0,
width: bounds.width,
height: 2.0,
},
background: Background::Color(style.rail_colors.1),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
);
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
{
HandleShape::Circle { radius } => {
(radius * 2.0, radius * 2.0, radius)
}
HandleShape::Rectangle {
width,
border_radius,
} => (f32::from(width), f32::from(bounds.height), border_radius),
};
let (range_start, range_end) = range.into_inner();
let handle_offset = if range_start >= range_end {
0.0
} else {
(bounds.width - handle_width) * (value - range_start)
/ (range_end - range_start)
};
let handle = Primitive::Quad {
bounds: Rectangle {
x: bounds.x + handle_offset.round(),
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
},
background: Background::Color(style.handle.color),
border_radius: handle_border_radius,
border_width: style.handle.border_width,
border_color: style.handle.border_color,
};
(
Primitive::Group {
primitives: vec![rail_top, rail_bottom, handle],
},
if is_dragging {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
mouse::Interaction::default()
},
)
}
}

View file

@ -1,15 +1 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::space;
use iced_native::Rectangle;
pub use iced_native::Space;
impl<B> space::Renderer for Renderer<B>
where
B: Backend,
{
fn draw(&mut self, _bounds: Rectangle) -> Self::Output {
(Primitive::None, mouse::Interaction::default())
}
}
pub use iced_native::widget::Space;

View file

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

View file

@ -1,92 +1,7 @@
//! Write some text for your users to read.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::alignment;
use iced_native::mouse;
use iced_native::text;
use iced_native::{Color, Font, Point, Rectangle, Size};
use crate::Renderer;
/// A paragraph of text.
///
/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`.
pub type Text<Backend> = iced_native::Text<Renderer<Backend>>;
use std::f32;
impl<B> text::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Font = Font;
fn default_size(&self) -> u16 {
self.backend().default_size()
}
fn measure(
&self,
content: &str,
size: u16,
font: Font,
bounds: Size,
) -> (f32, f32) {
self.backend()
.measure(content, f32::from(size), font, bounds)
}
fn hit_test(
&self,
content: &str,
size: f32,
font: Font,
bounds: Size,
point: Point,
nearest_only: bool,
) -> Option<text::Hit> {
self.backend().hit_test(
content,
size,
font,
bounds,
point,
nearest_only,
)
}
fn draw(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
content: &str,
size: u16,
font: Font,
color: Option<Color>,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
) -> Self::Output {
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.center_x(),
alignment::Horizontal::Right => bounds.x + bounds.width,
};
let y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.center_y(),
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
(
Primitive::Text {
content: content.to_string(),
size: f32::from(size),
bounds: Rectangle { x, y, ..bounds },
color: color.unwrap_or(defaults.text.color),
font,
horizontal_alignment,
vertical_alignment,
},
mouse::Interaction::default(),
)
}
}
pub type Text<Backend> = iced_native::widget::Text<Renderer<Backend>>;

View file

@ -1,266 +1,13 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
use crate::alignment;
use crate::backend::{self, Backend};
use crate::{
Background, Color, Font, Point, Primitive, Rectangle, Renderer, Size,
Vector,
};
use crate::Renderer;
use iced_native::mouse;
use iced_native::text_input::{self, cursor};
use std::f32;
pub use iced_native::text_input::State;
pub use iced_native::widget::text_input::State;
pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text.
///
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
pub type TextInput<'a, Message, Backend> =
iced_native::TextInput<'a, Message, Renderer<Backend>>;
impl<B> text_input::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
let backend = self.backend();
let (width, _) =
backend.measure(value, f32::from(size), font, Size::INFINITY);
width
}
fn offset(
&self,
text_bounds: Rectangle,
font: Font,
size: u16,
value: &text_input::Value,
state: &text_input::State,
) -> f32 {
if state.is_focused() {
let cursor = state.cursor();
let focus_position = match cursor.state(value) {
cursor::State::Index(i) => i,
cursor::State::Selection { end, .. } => end,
};
let (_, offset) = measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
focus_position,
font,
);
offset
} else {
0.0
}
}
fn draw(
&mut self,
bounds: Rectangle,
text_bounds: Rectangle,
cursor_position: Point,
font: Font,
size: u16,
placeholder: &str,
value: &text_input::Value,
state: &text_input::State,
style_sheet: &Self::Style,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
let style = if state.is_focused() {
style_sheet.focused()
} else if is_mouse_over {
style_sheet.hovered()
} else {
style_sheet.active()
};
let input = Primitive::Quad {
bounds,
background: style.background,
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
};
let text = value.to_string();
let text_value = Primitive::Text {
content: if text.is_empty() {
placeholder.to_string()
} else {
text.clone()
},
color: if text.is_empty() {
style_sheet.placeholder_color()
} else {
style_sheet.value_color()
},
font,
bounds: Rectangle {
y: text_bounds.center_y(),
width: f32::INFINITY,
..text_bounds
},
size: f32::from(size),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
};
let (contents_primitive, offset) = if state.is_focused() {
let cursor = state.cursor();
let (cursor_primitive, offset) = match cursor.state(value) {
cursor::State::Index(position) => {
let (text_value_width, offset) =
measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
position,
font,
);
(
Primitive::Quad {
bounds: Rectangle {
x: text_bounds.x + text_value_width,
y: text_bounds.y,
width: 1.0,
height: text_bounds.height,
},
background: Background::Color(
style_sheet.value_color(),
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
offset,
)
}
cursor::State::Selection { start, end } => {
let left = start.min(end);
let right = end.max(start);
let (left_position, left_offset) =
measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
left,
font,
);
let (right_position, right_offset) =
measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
right,
font,
);
let width = right_position - left_position;
(
Primitive::Quad {
bounds: Rectangle {
x: text_bounds.x + left_position,
y: text_bounds.y,
width,
height: text_bounds.height,
},
background: Background::Color(
style_sheet.selection_color(),
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
if end == right {
right_offset
} else {
left_offset
},
)
}
};
(
Primitive::Group {
primitives: vec![cursor_primitive, text_value],
},
Vector::new(offset as u32, 0),
)
} else {
(text_value, Vector::new(0, 0))
};
let text_width = self.measure_value(
if text.is_empty() { placeholder } else { &text },
size,
font,
);
let contents = if text_width > text_bounds.width {
Primitive::Clip {
bounds: text_bounds,
offset,
content: Box::new(contents_primitive),
}
} else {
contents_primitive
};
(
Primitive::Group {
primitives: vec![input, contents],
},
if is_mouse_over {
mouse::Interaction::Text
} else {
mouse::Interaction::default()
},
)
}
}
fn measure_cursor_and_scroll_offset<B>(
renderer: &Renderer<B>,
text_bounds: Rectangle,
value: &text_input::Value,
size: u16,
cursor_index: usize,
font: Font,
) -> (f32, f32)
where
B: Backend + backend::Text,
{
use iced_native::text_input::Renderer;
let text_before_cursor = value.until(cursor_index).to_string();
let text_value_width =
renderer.measure_value(&text_before_cursor, size, font);
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
(text_value_width, offset)
}
iced_native::widget::TextInput<'a, Message, Renderer<Backend>>;

View file

@ -1,99 +1,10 @@
//! Show toggle controls using togglers.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::mouse;
use iced_native::toggler;
use iced_native::Rectangle;
use crate::Renderer;
pub use iced_style::toggler::{Style, StyleSheet};
/// Makes sure that the border radius of the toggler looks good at every size.
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
/// The space ratio between the background Quad and the Toggler bounds, and
/// between the background Quad and foreground Quad.
const SPACE_RATIO: f32 = 0.05;
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
pub type Toggler<Message, Backend> =
iced_native::Toggler<Message, Renderer<Backend>>;
impl<B> toggler::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_SIZE: u16 = 20;
fn draw(
&mut self,
bounds: Rectangle,
is_active: bool,
is_mouse_over: bool,
label: Option<Self::Output>,
style_sheet: &Self::Style,
) -> Self::Output {
let style = if is_mouse_over {
style_sheet.hovered(is_active)
} else {
style_sheet.active(is_active)
};
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height as f32;
let toggler_background_bounds = Rectangle {
x: bounds.x + space,
y: bounds.y + space,
width: bounds.width - (2.0 * space),
height: bounds.height - (2.0 * space),
};
let toggler_background = Primitive::Quad {
bounds: toggler_background_bounds,
background: style.background.into(),
border_radius,
border_width: 1.0,
border_color: style.background_border.unwrap_or(style.background),
};
let toggler_foreground_bounds = Rectangle {
x: bounds.x
+ if is_active {
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
} else {
2.0 * space
},
y: bounds.y + (2.0 * space),
width: bounds.height - (4.0 * space),
height: bounds.height - (4.0 * space),
};
let toggler_foreground = Primitive::Quad {
bounds: toggler_foreground_bounds,
background: style.foreground.into(),
border_radius,
border_width: 1.0,
border_color: style.foreground_border.unwrap_or(style.foreground),
};
(
Primitive::Group {
primitives: match label {
Some((l, _)) => {
vec![l, toggler_background, toggler_foreground]
}
None => vec![toggler_background, toggler_foreground],
},
},
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
},
)
}
}
pub type Toggler<'a, Message, Backend> =
iced_native::widget::Toggler<'a, Message, Renderer<Backend>>;

View file

@ -1,168 +1,11 @@
//! Decorate content and apply alignment.
use crate::backend::{self, Backend};
use crate::defaults::{self, Defaults};
use crate::{Primitive, Renderer, Vector};
use iced_native::container;
use iced_native::layout::{self, Layout};
use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
use crate::Renderer;
/// An element decorating some content.
///
/// This is an alias of an `iced_native` tooltip with a default
/// `Renderer`.
pub type Tooltip<'a, Message, Backend> =
iced_native::Tooltip<'a, Message, Renderer<Backend>>;
iced_native::widget::Tooltip<'a, Message, Renderer<Backend>>;
pub use iced_native::tooltip::Position;
impl<B> iced_native::tooltip::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
const DEFAULT_PADDING: u16 = 5;
fn draw<Message>(
&mut self,
defaults: &Defaults,
cursor_position: Point,
content_layout: Layout<'_>,
viewport: &Rectangle,
content: &Element<'_, Message, Self>,
tooltip: &Text<Self>,
position: Position,
style_sheet: &<Self as container::Renderer>::Style,
gap: u16,
padding: u16,
) -> Self::Output {
let (content, mouse_interaction) = content.draw(
self,
&defaults,
content_layout,
cursor_position,
viewport,
);
let bounds = content_layout.bounds();
if bounds.contains(cursor_position) {
use iced_native::Widget;
let gap = f32::from(gap);
let style = style_sheet.style();
let defaults = Defaults {
text: defaults::Text {
color: style.text_color.unwrap_or(defaults.text.color),
},
};
let text_layout = Widget::<(), Self>::layout(
tooltip,
self,
&layout::Limits::new(Size::ZERO, viewport.size())
.pad(Padding::new(padding)),
);
let padding = f32::from(padding);
let text_bounds = text_layout.bounds();
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
let y_center =
bounds.y + (bounds.height - text_bounds.height) / 2.0;
let mut tooltip_bounds = {
let offset = match position {
Position::Top => Vector::new(
x_center,
bounds.y - text_bounds.height - gap - padding,
),
Position::Bottom => Vector::new(
x_center,
bounds.y + bounds.height + gap + padding,
),
Position::Left => Vector::new(
bounds.x - text_bounds.width - gap - padding,
y_center,
),
Position::Right => Vector::new(
bounds.x + bounds.width + gap + padding,
y_center,
),
Position::FollowCursor => Vector::new(
cursor_position.x,
cursor_position.y - text_bounds.height,
),
};
Rectangle {
x: offset.x - padding,
y: offset.y - padding,
width: text_bounds.width + padding * 2.0,
height: text_bounds.height + padding * 2.0,
}
};
if tooltip_bounds.x < viewport.x {
tooltip_bounds.x = viewport.x;
} else if viewport.x + viewport.width
< tooltip_bounds.x + tooltip_bounds.width
{
tooltip_bounds.x =
viewport.x + viewport.width - tooltip_bounds.width;
}
if tooltip_bounds.y < viewport.y {
tooltip_bounds.y = viewport.y;
} else if viewport.y + viewport.height
< tooltip_bounds.y + tooltip_bounds.height
{
tooltip_bounds.y =
viewport.y + viewport.height - tooltip_bounds.height;
}
let (tooltip, _) = Widget::<(), Self>::draw(
tooltip,
self,
&defaults,
Layout::with_offset(
Vector::new(
tooltip_bounds.x + padding,
tooltip_bounds.y + padding,
),
&text_layout,
),
cursor_position,
viewport,
);
(
Primitive::Group {
primitives: vec![
content,
Primitive::Clip {
bounds: *viewport,
offset: Vector::new(0, 0),
content: Box::new(
if let Some(background) =
crate::container::background(
tooltip_bounds,
&style,
)
{
Primitive::Group {
primitives: vec![background, tooltip],
}
} else {
tooltip
},
),
},
],
},
mouse_interaction,
)
} else {
(content, mouse_interaction)
}
}
}
pub use iced_native::widget::tooltip::Position;

View file

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

View file

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

View file

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

View file

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

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.
mod debugger;
mod limits;
mod node;
pub mod flex;
pub use debugger::Debugger;
pub use limits::Limits;
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 command;
pub mod event;
pub mod image;
pub mod keyboard;
pub mod layout;
pub mod mouse;
@ -43,6 +44,8 @@ pub mod overlay;
pub mod program;
pub mod renderer;
pub mod subscription;
pub mod svg;
pub mod text;
pub mod touch;
pub mod widget;
pub mod window;
@ -84,4 +87,4 @@ pub use renderer::Renderer;
pub use runtime::Runtime;
pub use subscription::Subscription;
pub use user_interface::{Cache, UserInterface};
pub use widget::*;
pub use widget::Widget;

View file

@ -8,7 +8,9 @@ pub use menu::Menu;
use crate::event::{self, Event};
use crate::layout;
use crate::{Clipboard, Hasher, Layout, Point, Size};
use crate::mouse;
use crate::renderer;
use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
@ -32,10 +34,10 @@ where
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output;
);
/// Computes the _layout_ hash of the [`Overlay`].
///
@ -73,4 +75,16 @@ where
) -> event::Status {
event::Status::Ignored
}
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
///
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> mouse::Interaction {
mouse::Interaction::Idle
}
}

View file

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

View file

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

View file

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

View file

@ -19,28 +19,17 @@
//! [`text::Renderer`]: crate::widget::text::Renderer
//! [`Checkbox`]: crate::widget::Checkbox
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
use crate::{layout, Element, Rectangle};
use crate::layout;
use crate::{Background, Color, Element, Rectangle, Vector};
/// A component that can take the state of a user interface and produce an
/// output for its users.
pub trait Renderer: Sized {
/// The type of output of the [`Renderer`].
///
/// If you are implementing a graphical renderer, your output will most
/// likely be a tree of visual primitives.
type Output;
/// The default styling attributes of the [`Renderer`].
///
/// This type can be leveraged to implement style inheritance.
type Defaults: Default;
/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
@ -53,12 +42,52 @@ pub trait Renderer: Sized {
element.layout(self, limits)
}
/// Overlays the `overlay` output with the given bounds on top of the `base`
/// output.
fn overlay(
/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
/// Applies a `translation` to the primitives recorded in the given closure.
fn with_translation(
&mut self,
base: Self::Output,
overlay: Self::Output,
overlay_bounds: Rectangle,
) -> Self::Output;
translation: Vector,
f: impl FnOnce(&mut Self),
);
/// Clears all of the recorded primitives in the [`Renderer`].
fn clear(&mut self);
/// Fills a [`Quad`] with the provided [`Background`].
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
}
/// A polygon with four sides.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quad {
/// The bounds of the [`Quad`].
pub bounds: Rectangle,
/// The border radius of the [`Quad`].
pub border_radius: f32,
/// The border width of the [`Quad`].
pub border_width: f32,
/// The border color of the [`Quad`].
pub border_color: Color,
}
/// The styling attributes of a [`Renderer`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The text color
pub text_color: Color,
}
impl Default for Style {
fn default() -> Self {
Style {
text_color: Color::BLACK,
}
}
}

View file

@ -1,20 +1,6 @@
use crate::alignment;
use crate::button;
use crate::checkbox;
use crate::column;
use crate::container;
use crate::pane_grid;
use crate::progress_bar;
use crate::radio;
use crate::row;
use crate::scrollable;
use crate::slider;
use crate::text;
use crate::text_input;
use crate::toggler;
use crate::{
Color, Element, Font, Layout, Padding, Point, Rectangle, Renderer, Size,
};
use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Vector};
/// A renderer that does nothing.
///
@ -30,33 +16,21 @@ impl Null {
}
impl Renderer for Null {
type Output = ();
type Defaults = ();
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) {
}
}
impl column::Renderer for Null {
fn draw<Message>(
fn with_translation(
&mut self,
_defaults: &Self::Defaults,
_content: &[Element<'_, Message, Self>],
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
_translation: Vector,
_f: impl FnOnce(&mut Self),
) {
}
}
impl row::Renderer for Null {
fn draw<Message>(
fn clear(&mut self) {}
fn fill_quad(
&mut self,
_defaults: &Self::Defaults,
_content: &[Element<'_, Message, Self>],
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
_quad: renderer::Quad,
_background: impl Into<Background>,
) {
}
}
@ -64,6 +38,10 @@ impl row::Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
const ICON_FONT: Font = Font::Default;
const CHECKMARK_ICON: char = '0';
const ARROW_DOWN_ICON: char = '0';
fn default_size(&self) -> u16 {
20
}
@ -90,240 +68,5 @@ impl text::Renderer for Null {
None
}
fn draw(
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
_content: &str,
_size: u16,
_font: Font,
_color: Option<Color>,
_horizontal_alignment: alignment::Horizontal,
_vertical_alignment: alignment::Vertical,
) {
}
}
impl scrollable::Renderer for Null {
type Style = ();
fn scrollbar(
&self,
_bounds: Rectangle,
_content_bounds: Rectangle,
_offset: u32,
_scrollbar_width: u16,
_scrollbar_margin: u16,
_scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
None
}
fn draw(
&mut self,
_scrollable: &scrollable::State,
_bounds: Rectangle,
_content_bounds: Rectangle,
_is_mouse_over: bool,
_is_mouse_over_scrollbar: bool,
_scrollbar: Option<scrollable::Scrollbar>,
_offset: u32,
_style: &Self::Style,
_content: Self::Output,
) {
}
}
impl text_input::Renderer for Null {
type Style = ();
fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
0.0
}
fn offset(
&self,
_text_bounds: Rectangle,
_font: Font,
_size: u16,
_value: &text_input::Value,
_state: &text_input::State,
) -> f32 {
0.0
}
fn draw(
&mut self,
_bounds: Rectangle,
_text_bounds: Rectangle,
_cursor_position: Point,
_font: Font,
_size: u16,
_placeholder: &str,
_value: &text_input::Value,
_state: &text_input::State,
_style: &Self::Style,
) -> Self::Output {
}
}
impl button::Renderer for Null {
const DEFAULT_PADDING: Padding = Padding::ZERO;
type Style = ();
fn draw<Message>(
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
_cursor_position: Point,
_is_disabled: bool,
_is_pressed: bool,
_style: &Self::Style,
_content: &Element<'_, Message, Self>,
_content_layout: Layout<'_>,
) -> Self::Output {
}
}
impl radio::Renderer for Null {
type Style = ();
const DEFAULT_SIZE: u16 = 20;
const DEFAULT_SPACING: u16 = 15;
fn draw(
&mut self,
_bounds: Rectangle,
_is_selected: bool,
_is_mouse_over: bool,
_label: Self::Output,
_style: &Self::Style,
) {
}
}
impl checkbox::Renderer for Null {
type Style = ();
const DEFAULT_SIZE: u16 = 20;
const DEFAULT_SPACING: u16 = 15;
fn draw(
&mut self,
_bounds: Rectangle,
_is_checked: bool,
_is_mouse_over: bool,
_label: Self::Output,
_style: &Self::Style,
) {
}
}
impl slider::Renderer for Null {
type Style = ();
const DEFAULT_HEIGHT: u16 = 30;
fn draw(
&mut self,
_bounds: Rectangle,
_cursor_position: Point,
_range: std::ops::RangeInclusive<f32>,
_value: f32,
_is_dragging: bool,
_style_sheet: &Self::Style,
) {
}
}
impl progress_bar::Renderer for Null {
type Style = ();
const DEFAULT_HEIGHT: u16 = 30;
fn draw(
&self,
_bounds: Rectangle,
_range: std::ops::RangeInclusive<f32>,
_value: f32,
_style: &Self::Style,
) {
}
}
impl container::Renderer for Null {
type Style = ();
fn draw<Message>(
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
_cursor_position: Point,
_viewport: &Rectangle,
_style: &Self::Style,
_content: &Element<'_, Message, Self>,
_content_layout: Layout<'_>,
) {
}
}
impl pane_grid::Renderer for Null {
type Style = ();
fn draw<Message>(
&mut self,
_defaults: &Self::Defaults,
_content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
_dragging: Option<(pane_grid::Pane, Point)>,
_resizing: Option<(pane_grid::Axis, Rectangle, bool)>,
_layout: Layout<'_>,
_style: &<Self as pane_grid::Renderer>::Style,
_cursor_position: Point,
_viewport: &Rectangle,
) {
}
fn draw_pane<Message>(
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
_style: &<Self as container::Renderer>::Style,
_title_bar: Option<(
&pane_grid::TitleBar<'_, Message, Self>,
Layout<'_>,
)>,
_body: (&Element<'_, Message, Self>, Layout<'_>),
_cursor_position: Point,
_viewport: &Rectangle,
) {
}
fn draw_title_bar<Message>(
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
_style: &<Self as container::Renderer>::Style,
_content: (&Element<'_, Message, Self>, Layout<'_>),
_controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
}
}
impl toggler::Renderer for Null {
type Style = ();
const DEFAULT_SIZE: u16 = 20;
fn draw(
&mut self,
_bounds: Rectangle,
_is_checked: bool,
_is_mouse_over: bool,
_label: Option<Self::Output>,
_style: &Self::Style,
) {
}
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
}

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::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@ -47,7 +49,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
/// # use iced_native::Column;
/// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@ -141,7 +143,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
/// # use iced_native::Column;
/// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@ -277,7 +279,7 @@ where
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
/// # use iced_native::Column;
/// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
@ -333,10 +335,13 @@ where
&mut self,
renderer: &mut Renderer,
cursor_position: Point,
) -> Renderer::Output {
) -> mouse::Interaction {
// TODO: Move to shell level (?)
renderer.clear();
let viewport = Rectangle::with_size(self.bounds);
let overlay = if let Some(mut overlay) =
if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
let layer = Self::overlay_layer(
@ -346,51 +351,81 @@ where
renderer,
);
let overlay_bounds = layer.layout.bounds();
let overlay_primitives = overlay.draw(
renderer,
&Renderer::Defaults::default(),
Layout::new(&layer.layout),
cursor_position,
);
self.overlay = Some(layer);
Some((overlay_primitives, overlay_bounds))
} else {
None
};
if let Some((overlay_primitives, overlay_bounds)) = overlay {
let base_cursor = if overlay_bounds.contains(cursor_position) {
if let Some(layer) = &self.overlay {
let base_cursor = if layer.layout.bounds().contains(cursor_position)
{
Point::new(-1.0, -1.0)
} else {
cursor_position
};
let base_primitives = self.root.widget.draw(
self.root.widget.draw(
renderer,
&Renderer::Defaults::default(),
&renderer::Style::default(),
Layout::new(&self.base.layout),
base_cursor,
&viewport,
);
renderer.overlay(
base_primitives,
overlay_primitives,
overlay_bounds,
)
} else {
self.root.widget.draw(
renderer,
&Renderer::Defaults::default(),
&renderer::Style::default(),
Layout::new(&self.base.layout),
cursor_position,
&viewport,
)
}
);
};
let base_interaction = self.root.widget.mouse_interaction(
Layout::new(&self.base.layout),
cursor_position,
&viewport,
);
let Self {
overlay,
root,
base,
..
} = self;
// TODO: Currently, we need to call Widget::overlay twice to
// implement the painter's algorithm properly.
//
// Once we have a proper persistent widget tree, we should be able to
// avoid this additional call.
overlay
.as_ref()
.and_then(|layer| {
root.overlay(Layout::new(&base.layout)).map(|overlay| {
let overlay_interaction = overlay.mouse_interaction(
Layout::new(&layer.layout),
cursor_position,
&viewport,
);
let overlay_bounds = layer.layout.bounds();
renderer.with_layer(viewport, |renderer| {
overlay.draw(
renderer,
&renderer::Style::default(),
Layout::new(&layer.layout),
cursor_position,
);
});
if overlay_bounds.contains(cursor_position) {
overlay_interaction
} else {
base_interaction
}
})
})
.unwrap_or(base_interaction)
}
/// Relayouts and returns a new [`UserInterface`] using the provided

View file

@ -10,14 +10,6 @@
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
//! source of inspiration.
//!
//! # Re-exports
//! For convenience, the contents of this module are available at the root
//! module. Therefore, you can directly type:
//!
//! ```
//! use iced_native::{button, Button, Widget};
//! ```
//!
//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
@ -80,7 +72,9 @@ pub use tooltip::Tooltip;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction.
@ -131,11 +125,11 @@ where
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Renderer::Output;
);
/// Computes the _layout_ hash of the [`Widget`].
///
@ -174,6 +168,18 @@ where
event::Status::Ignored
}
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
///
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> mouse::Interaction {
mouse::Interaction::Idle
}
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay(
&mut self,

View file

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

View file

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

View file

@ -3,7 +3,9 @@ use std::hash::Hash;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget,
@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@ -162,21 +164,37 @@ where
.fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
&self.children,
layout,
cursor_position,
viewport,
)
) -> mouse::Interaction {
self.children
.iter()
.zip(layout.children())
.map(|(child, layout)| {
child.widget.mouse_interaction(
layout,
cursor_position,
viewport,
)
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
renderer: &mut Renderer,
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) {
@ -208,33 +226,10 @@ where
}
}
/// The renderer of a [`Column`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Column`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Column`].
///
/// It receives:
/// - the children of the [`Column`]
/// - the [`Layout`] of the [`Column`] and its children
/// - the cursor position
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(

View file

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

View file

@ -2,21 +2,19 @@
pub mod viewer;
pub use viewer::Viewer;
use crate::image::{self, Handle};
use crate::layout;
use crate::renderer;
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
path::PathBuf,
sync::Arc,
};
use std::hash::Hash;
/// A frame that displays an image while keeping aspect ratio.
///
/// # Example
///
/// ```
/// # use iced_native::Image;
/// # use iced_native::widget::Image;
/// #
/// let image = Image::new("resources/ferris.png");
/// ```
@ -54,7 +52,7 @@ impl Image {
impl<Message, Renderer> Widget<Message, Renderer> for Image
where
Renderer: self::Renderer,
Renderer: image::Renderer,
{
fn width(&self) -> Length {
self.width
@ -92,12 +90,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
) {
renderer.draw(self.handle.clone(), layout.bounds());
}
fn hash_layout(&self, state: &mut Hasher) {
@ -110,129 +108,9 @@ where
}
}
/// An [`Image`] handle.
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
data: Arc<Data>,
}
impl Handle {
/// Creates an image [`Handle`] pointing to the image of the given path.
///
/// Makes an educated guess about the image format by examining the data in the file.
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
Self::from_data(Data::Path(path.into()))
}
/// Creates an image [`Handle`] containing the image pixels directly. This
/// function expects the input data to be provided as a `Vec<u8>` of BGRA
/// pixels.
///
/// This is useful if you have already decoded your image.
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
Self::from_data(Data::Pixels {
width,
height,
pixels,
})
}
/// Creates an image [`Handle`] containing the image data directly.
///
/// Makes an educated guess about the image format by examining the given data.
///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
pub fn from_memory(bytes: Vec<u8>) -> Handle {
Self::from_data(Data::Bytes(bytes))
}
fn from_data(data: Data) -> Handle {
let mut hasher = Hasher::default();
data.hash(&mut hasher);
Handle {
id: hasher.finish(),
data: Arc::new(data),
}
}
/// Returns the unique identifier of the [`Handle`].
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the image [`Data`].
pub fn data(&self) -> &Data {
&self.data
}
}
impl<T> From<T> for Handle
where
T: Into<PathBuf>,
{
fn from(path: T) -> Handle {
Handle::from_path(path.into())
}
}
impl Hash for Handle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
/// The data of an [`Image`].
#[derive(Clone, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
/// In-memory data
Bytes(Vec<u8>),
/// Decoded image pixels in BGRA format.
Pixels {
/// The width of the image.
width: u32,
/// The height of the image.
height: u32,
/// The pixels.
pixels: Vec<u8>,
},
}
impl std::fmt::Debug for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Data::Path(path) => write!(f, "Path({:?})", path),
Data::Bytes(_) => write!(f, "Bytes(...)"),
Data::Pixels { width, height, .. } => {
write!(f, "Pixels({} * {})", width, height)
}
}
}
}
/// The renderer of an [`Image`].
///
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Image`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an [`Image`] located on the given path.
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Image`].
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: image::Renderer,
{
fn from(image: Image) -> Element<'a, Message, Renderer> {
Element::new(image)

View file

@ -3,6 +3,7 @@ use crate::event::{self, Event};
use crate::image;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Widget,
@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {
/// will be respected.
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
where
Renderer: self::Renderer + image::Renderer,
Renderer: image::Renderer,
{
let (width, height) = renderer.dimensions(&self.handle);
@ -115,7 +116,7 @@ impl<'a> Viewer<'a> {
impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
where
Renderer: self::Renderer + image::Renderer,
Renderer: image::Renderer,
{
fn width(&self) -> Length {
self.width
@ -280,14 +281,32 @@ where
}
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
) -> mouse::Interaction {
let bounds = layout.bounds();
let 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 image_size = self.image_size(renderer, bounds.size());
@ -301,17 +320,19 @@ where
image_top_left - self.state.offset(bounds, image_size)
};
let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer,
&self.state,
bounds,
image_size,
translation,
self.handle.clone(),
is_mouse_over,
)
renderer.with_layer(bounds, |renderer| {
renderer.with_translation(translation, |renderer| {
image::Renderer::draw(
renderer,
self.handle.clone(),
Rectangle {
x: bounds.x,
y: bounds.y,
..Rectangle::with_size(image_size)
},
)
});
});
}
fn hash_layout(&self, state: &mut Hasher) {
@ -373,38 +394,9 @@ impl State {
}
}
/// The renderer of an [`Viewer`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Viewer`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws the [`Viewer`].
///
/// It receives:
/// - the [`State`] of the [`Viewer`]
/// - the bounds of the [`Viewer`] widget
/// - the [`Size`] of the scaled [`Viewer`] image
/// - the translation of the clipped image
/// - the [`Handle`] to the underlying image
/// - whether the mouse is over the [`Viewer`] or not
///
/// [`Handle`]: image::Handle
fn draw(
&mut self,
state: &State,
bounds: Rectangle,
image_size: Size,
translation: Vector,
handle: image::Handle,
is_mouse_over: bool,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer + image::Renderer,
Renderer: 'a + image::Renderer,
Message: 'a,
{
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {

View file

@ -27,18 +27,19 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
use crate::container;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::row;
use crate::renderer;
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Widget,
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
Vector, Widget,
};
pub use iced_style::pane_grid::{Line, StyleSheet};
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@ -61,10 +62,10 @@ use crate::{
/// ## Example
///
/// ```
/// # use iced_native::{pane_grid, Text};
/// # use iced_native::widget::{pane_grid, Text};
/// #
/// # type PaneGrid<'a, Message> =
/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>;
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
/// #
/// enum PaneState {
/// SomePane,
@ -89,7 +90,7 @@ use crate::{
/// .on_resize(10, Message::PaneResized);
/// ```
#[allow(missing_debug_implementations)]
pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
pub struct PaneGrid<'a, Message, Renderer> {
state: &'a mut state::Internal,
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length,
@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
style: <Renderer as self::Renderer>::Style,
style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
@ -130,7 +131,7 @@ where
on_click: None,
on_drag: None,
on_resize: None,
style: Default::default(),
style_sheet: Default::default(),
}
}
@ -190,18 +191,15 @@ where
}
/// Sets the style of the [`PaneGrid`].
pub fn style(
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
) -> Self {
self.style = style.into();
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
self.style_sheet = style.into();
self
}
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn click_pane(
&mut self,
@ -318,7 +316,7 @@ pub struct ResizeEvent {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
Renderer: self::Renderer + container::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@ -473,14 +471,43 @@ where
.fold(event_status, event::Status::merge)
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
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
.state
.picked_split()
@ -529,17 +556,89 @@ where
None => None,
});
self::Renderer::draw(
renderer,
defaults,
&self.elements,
self.state.picked_pane(),
picked_split,
layout,
&self.style,
cursor_position,
viewport,
)
let pane_cursor_position = if picked_pane.is_some() {
// TODO: Remove once cursor availability is encoded in the type
// system
Point::new(-1.0, -1.0)
} else {
cursor_position
};
for ((id, pane), layout) in self.elements.iter().zip(layout.children())
{
match picked_pane {
Some((dragging, origin)) if *id == dragging => {
let bounds = layout.bounds();
renderer.with_translation(
cursor_position
- Point::new(
bounds.x + origin.x,
bounds.y + origin.y,
),
|renderer| {
renderer.with_layer(bounds, |renderer| {
pane.draw(
renderer,
style,
layout,
pane_cursor_position,
viewport,
);
});
},
);
}
_ => {
pane.draw(
renderer,
style,
layout,
pane_cursor_position,
viewport,
);
}
}
}
if let Some((axis, split_region, is_picked)) = picked_split {
let highlight = if is_picked {
self.style_sheet.picked_split()
} else {
self.style_sheet.hovered_split()
};
if let Some(highlight) = highlight {
renderer.fill_quad(
renderer::Quad {
bounds: match axis {
Axis::Horizontal => Rectangle {
x: split_region.x,
y: (split_region.y
+ (split_region.height - highlight.width)
/ 2.0)
.round(),
width: split_region.width,
height: highlight.width,
},
Axis::Vertical => Rectangle {
x: (split_region.x
+ (split_region.width - highlight.width)
/ 2.0)
.round(),
y: split_region.y,
width: highlight.width,
height: split_region.height,
},
},
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
highlight.color,
);
}
}
}
fn hash_layout(&self, state: &mut Hasher) {
@ -569,78 +668,10 @@ where
}
}
/// The renderer of a [`PaneGrid`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PaneGrid`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + container::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`PaneGrid`].
///
/// It receives:
/// - the elements of the [`PaneGrid`]
/// - the [`Pane`] that is currently being dragged
/// - the [`Axis`] that is currently being resized
/// - the [`Layout`] of the [`PaneGrid`] and its elements
/// - the cursor position
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Content<'_, Message, Self>)],
dragging: Option<(Pane, Point)>,
resizing: Option<(Axis, Rectangle, bool)>,
layout: Layout<'_>,
style: &<Self as self::Renderer>::Style,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output;
/// Draws a [`Pane`].
///
/// It receives:
/// - the [`TitleBar`] of the [`Pane`], if any
/// - the [`Content`] of the [`Pane`]
/// - the [`Layout`] of the [`Pane`] and its elements
/// - the cursor position
fn draw_pane<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
style: &<Self as container::Renderer>::Style,
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
body: (&Element<'_, Message, Self>, Layout<'_>),
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output;
/// Draws a [`TitleBar`].
///
/// It receives:
/// - the bounds, style of the [`TitleBar`]
/// - the style of the [`TitleBar`]
/// - the content of the [`TitleBar`] with its layout
/// - the controls of the [`TitleBar`] with their [`Layout`], if any
/// - the cursor position
fn draw_title_bar<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
style: &<Self as container::Renderer>::Style,
content: (&Element<'_, Message, Self>, Layout<'_>),
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer + row::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,13 @@
//! Display a dropdown list of selectable values.
use crate::alignment;
use crate::event::{self, Event};
use crate::keyboard;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::overlay::menu::{self, Menu};
use crate::scrollable;
use crate::text;
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
@ -14,9 +15,11 @@ use crate::{
};
use std::borrow::Cow;
pub use iced_style::pick_list::{Style, StyleSheet};
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
pub struct PickList<'a, T, Message, Renderer: self::Renderer>
pub struct PickList<'a, T, Message, Renderer: text::Renderer>
where
[T]: ToOwned<Owned = Vec<T>>,
{
@ -33,7 +36,7 @@ where
padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
style_sheet: Box<dyn StyleSheet + 'a>,
}
/// The local state of a [`PickList`].
@ -58,12 +61,15 @@ impl<T> Default for State<T> {
}
}
impl<'a, T: 'a, Message, Renderer: self::Renderer>
impl<'a, T: 'a, Message, Renderer: text::Renderer>
PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
{
/// The default padding of a [`PickList`].
pub const DEFAULT_PADDING: Padding = Padding::new(5);
/// Creates a new [`PickList`] with the given [`State`], a list of options,
/// the current selected value, and the message to produce when an option is
/// selected.
@ -93,9 +99,9 @@ where
selected,
width: Length::Shrink,
text_size: None,
padding: Renderer::DEFAULT_PADDING,
padding: Self::DEFAULT_PADDING,
font: Default::default(),
style: Default::default(),
style_sheet: Default::default(),
}
}
@ -132,9 +138,9 @@ where
/// Sets the style of the [`PickList`].
pub fn style(
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style = style.into();
self.style_sheet = style_sheet.into();
self
}
}
@ -145,7 +151,7 @@ where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'static,
Renderer: self::Renderer + scrollable::Renderer + 'a,
Renderer: text::Renderer + 'a,
{
fn width(&self) -> Length {
self.width
@ -320,25 +326,90 @@ where
}
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
self::Renderer::draw(
renderer,
layout.bounds(),
cursor_position,
self.selected.as_ref().map(ToString::to_string),
self.placeholder.as_ref().map(String::as_str),
self.padding,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
&self.style,
)
) -> mouse::Interaction {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
renderer: &mut Renderer,
_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()),
),
font: self.font,
color: is_selected
.then(|| style.text_color)
.unwrap_or(style.placeholder_color),
bounds: Rectangle {
x: bounds.x + f32::from(self.padding.left),
y: bounds.center_y(),
..bounds
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
})
}
}
fn overlay(
@ -357,7 +428,7 @@ where
.width(bounds.width.round() as u16)
.padding(self.padding)
.font(self.font)
.style(Renderer::menu_style(&self.style));
.style(self.style_sheet.menu());
if let Some(text_size) = self.text_size {
menu = menu.text_size(text_size);
@ -370,44 +441,12 @@ where
}
}
/// The renderer of a [`PickList`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PickList`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + menu::Renderer {
/// The default padding of a [`PickList`].
const DEFAULT_PADDING: Padding;
/// The [`PickList`] style supported by this renderer.
type Style: Default;
/// Returns the style of the [`Menu`] of the [`PickList`].
fn menu_style(
style: &<Self as Renderer>::Style,
) -> <Self as menu::Renderer>::Style;
/// Draws a [`PickList`].
fn draw(
&mut self,
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
placeholder: Option<&str>,
padding: Padding,
text_size: u16,
font: Self::Font,
style: &<Self as Renderer>::Style,
) -> Self::Output;
}
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
for PickList<'a, T, Message, Renderer>
where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: self::Renderer + 'a,
Renderer: text::Renderer + 'a,
Message: 'static,
{
fn into(self) -> Element<'a, Message, Renderer> {

View file

@ -1,17 +1,19 @@
//! Provide progress feedback to your users.
use crate::layout;
use crate::renderer;
use crate::{
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
pub use iced_style::progress_bar::{Style, StyleSheet};
/// A bar that displays progress.
///
/// # Example
/// ```
/// # use iced_native::renderer::Null;
/// #
/// # pub type ProgressBar = iced_native::ProgressBar<Null>;
/// # use iced_native::widget::ProgressBar;
/// let value = 50.0;
///
/// ProgressBar::new(0.0..=100.0, value);
@ -19,15 +21,18 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
pub struct ProgressBar<Renderer: self::Renderer> {
pub struct ProgressBar<'a> {
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
style: Renderer::Style,
style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<Renderer: self::Renderer> ProgressBar<Renderer> {
impl<'a> ProgressBar<'a> {
/// The default height of a [`ProgressBar`].
pub const DEFAULT_HEIGHT: u16 = 30;
/// Creates a new [`ProgressBar`].
///
/// It expects:
@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
range,
width: Length::Fill,
height: None,
style: Renderer::Style::default(),
style_sheet: Default::default(),
}
}
@ -56,23 +61,25 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
}
/// Sets the style of the [`ProgressBar`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
pub fn style(
mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT))
self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))
}
fn layout(
@ -80,10 +87,9 @@ where
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(
self.height
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)),
);
let limits = limits
.width(self.width)
.height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));
let size = limits.resolve(Size::ZERO);
@ -93,17 +99,47 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
layout.bounds(),
self.range.clone(),
self.value,
&self.style,
)
) {
let bounds = layout.bounds();
let (range_start, range_end) = self.range.clone().into_inner();
let active_progress_width = if range_start >= range_end {
0.0
} else {
bounds.width * (self.value - range_start)
/ (range_end - range_start)
};
let style = self.style_sheet.style();
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle { ..bounds },
border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.background,
);
if active_progress_width > 0.0 {
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
width: active_progress_width,
..bounds
},
border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.bar,
);
}
}
fn hash_layout(&self, state: &mut Hasher) {
@ -115,45 +151,13 @@ where
}
}
/// The renderer of a [`ProgressBar`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`ProgressBar`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`ProgressBar`].
const DEFAULT_HEIGHT: u16;
/// Draws a [`ProgressBar`].
///
/// It receives:
/// * the bounds of the [`ProgressBar`]
/// * the range of values of the [`ProgressBar`]
/// * the current value of the [`ProgressBar`]
/// * maybe a specific background of the [`ProgressBar`]
/// * maybe a specific active color of the [`ProgressBar`]
fn draw(
&self,
bounds: Rectangle,
range: RangeInclusive<f32>,
value: f32,
style: &Self::Style,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
impl<'a, Message, Renderer> From<ProgressBar<'a>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(
progress_bar: ProgressBar<Renderer>,
) -> Element<'a, Message, Renderer> {
fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {
Element::new(progress_bar)
}
}

View file

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

View file

@ -1,7 +1,9 @@
//! Distribute content horizontally.
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Widget,
@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@ -161,21 +163,37 @@ where
.fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
&self.children,
layout,
cursor_position,
viewport,
)
) -> mouse::Interaction {
self.children
.iter()
.zip(layout.children())
.map(|(child, layout)| {
child.widget.mouse_interaction(
layout,
cursor_position,
viewport,
)
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
renderer: &mut Renderer,
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) {
@ -207,33 +225,10 @@ where
}
}
/// The renderer of a [`Row`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Row`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Row`].
///
/// It receives:
/// - the children of the [`Row`]
/// - the [`Layout`] of the [`Row`] and its children
/// - the cursor position
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
children: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {

View file

@ -1,28 +1,31 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::layout;
use crate::renderer;
use crate::{
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::hash::Hash;
use crate::{
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
pub use iced_style::rule::{FillMode, Style, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
#[derive(Debug, Copy, Clone)]
pub struct Rule<Renderer: self::Renderer> {
#[allow(missing_debug_implementations)]
pub struct Rule<'a> {
width: Length,
height: Length,
style: Renderer::Style,
is_horizontal: bool,
style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<Renderer: self::Renderer> Rule<Renderer> {
impl<'a> Rule<'a> {
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
pub fn horizontal(spacing: u16) -> Self {
Rule {
width: Length::Fill,
height: Length::from(Length::Units(spacing)),
style: Renderer::Style::default(),
is_horizontal: true,
style_sheet: Default::default(),
}
}
@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
Rule {
width: Length::from(Length::Units(spacing)),
height: Length::Fill,
style: Renderer::Style::default(),
is_horizontal: false,
style_sheet: Default::default(),
}
}
/// Sets the style of the [`Rule`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
pub fn style(
mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@ -68,12 +74,53 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
) {
let bounds = layout.bounds();
let style = self.style_sheet.style();
let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
- (style.width as f32 / 2.0))
.round();
let (offset, line_width) = style.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset;
Rectangle {
x: line_x,
y: line_y,
width: line_width,
height: style.width as f32,
}
} else {
let line_x = (bounds.x + (bounds.width / 2.0)
- (style.width as f32 / 2.0))
.round();
let (offset, line_height) = style.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset;
Rectangle {
x: line_x,
y: line_y,
width: style.width as f32,
height: line_height,
}
};
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: style.radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.color,
);
}
fn hash_layout(&self, state: &mut Hasher) {
@ -85,32 +132,12 @@ where
}
}
/// The renderer of a [`Rule`].
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Rule`].
///
/// It receives:
/// * the bounds of the [`Rule`]
/// * the style of the [`Rule`]
/// * whether the [`Rule`] is horizontal (true) or vertical (false)
fn draw(
&mut self,
bounds: Rectangle,
style: &Self::Style,
is_horizontal: bool,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Rule<Renderer>>
for Element<'a, Message, Renderer>
impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {
Element::new(rule)
}
}

View file

@ -1,21 +1,24 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::column;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::Column;
use crate::{
Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding,
Point, Rectangle, Size, Vector, Widget,
Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
Padding, Point, Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
pub use iced_style::scrollable::StyleSheet;
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
#[allow(missing_debug_implementations)]
pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
pub struct Scrollable<'a, Message, Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scroller_width: u16,
content: Column<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
pub fn new(state: &'a mut State) -> Self {
Scrollable {
@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scroller_width: 10,
content: Column::new(),
on_scroll: None,
style: Renderer::Style::default(),
style_sheet: Default::default(),
}
}
@ -120,8 +123,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
pub fn style(
mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self
}
@ -151,12 +157,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
));
}
}
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
) -> Option<Scrollbar> {
let offset = self.state.offset(bounds, content_bounds);
if content_bounds.height > bounds.height {
let outer_width = self.scrollbar_width.max(self.scroller_width)
+ 2 * self.scrollbar_margin;
let outer_bounds = Rectangle {
x: bounds.x + bounds.width - outer_width as f32,
y: bounds.y,
width: outer_width as f32,
height: bounds.height,
};
let scrollbar_bounds = Rectangle {
x: bounds.x + bounds.width
- f32::from(outer_width / 2 + self.scrollbar_width / 2),
y: bounds.y,
width: self.scrollbar_width as f32,
height: bounds.height,
};
let ratio = bounds.height / content_bounds.height;
let scroller_height = bounds.height * ratio;
let y_offset = offset as f32 * ratio;
let scroller_bounds = Rectangle {
x: bounds.x + bounds.width
- f32::from(outer_width / 2 + self.scroller_width / 2),
y: scrollbar_bounds.y + y_offset,
width: self.scroller_width as f32,
height: scroller_height,
};
Some(Scrollbar {
outer_bounds,
bounds: scrollbar_bounds,
margin: self.scrollbar_margin,
scroller: Scroller {
bounds: scroller_bounds,
},
})
} else {
None
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content)
@ -202,15 +259,7 @@ where
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
let offset = self.state.offset(bounds, content_bounds);
let scrollbar = renderer.scrollbar(
bounds,
content_bounds,
offset,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
);
let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over_scrollbar = scrollbar
.as_ref()
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
@ -374,26 +423,16 @@ where
event::Status::Ignored
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
) -> mouse::Interaction {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = self.state.offset(bounds, content_bounds);
let scrollbar = renderer.scrollbar(
bounds,
content_bounds,
offset,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
);
let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@ -401,16 +440,18 @@ where
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
.unwrap_or(false);
let content = {
if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() {
mouse::Interaction::Idle
} else {
let offset = self.state.offset(bounds, content_bounds);
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else {
Point::new(cursor_position.x, -1.0)
};
self.content.draw(
renderer,
defaults,
self.content.mouse_interaction(
content_layout,
cursor_position,
&Rectangle {
@ -418,20 +459,114 @@ where
..bounds
},
)
}
}
fn draw(
&self,
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = self.state.offset(bounds, content_bounds);
let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
.as_ref()
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
.unwrap_or(false);
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else {
Point::new(cursor_position.x, -1.0)
};
self::Renderer::draw(
renderer,
&self.state,
bounds,
content_layout.bounds(),
is_mouse_over,
is_mouse_over_scrollbar,
scrollbar,
offset,
&self.style,
content,
)
if let Some(scrollbar) = scrollbar {
renderer.with_layer(bounds, |renderer| {
renderer.with_translation(
Vector::new(0.0, -(offset as f32)),
|renderer| {
self.content.draw(
renderer,
style,
content_layout,
cursor_position,
&Rectangle {
y: bounds.y + offset as f32,
..bounds
},
);
},
);
});
let style = if self.state.is_scroller_grabbed() {
self.style_sheet.dragging()
} else if is_mouse_over_scrollbar {
self.style_sheet.hovered()
} else {
self.style_sheet.active()
};
let is_scrollbar_visible =
style.background.is_some() || style.border_width > 0.0;
renderer.with_layer(
Rectangle {
width: bounds.width + 2.0,
height: bounds.height + 2.0,
..bounds
},
|renderer| {
if is_scrollbar_visible {
renderer.fill_quad(
renderer::Quad {
bounds: scrollbar.bounds,
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
},
style.background.unwrap_or(Background::Color(
Color::TRANSPARENT,
)),
);
}
if is_mouse_over
|| self.state.is_scroller_grabbed()
|| is_scrollbar_visible
{
renderer.fill_quad(
renderer::Quad {
bounds: scrollbar.scroller.bounds,
border_radius: style.scroller.border_radius,
border_width: style.scroller.border_width,
border_color: style.scroller.border_color,
},
style.scroller.color,
);
}
},
);
} else {
self.content.draw(
renderer,
style,
content_layout,
cursor_position,
&Rectangle {
y: bounds.y + offset as f32,
..bounds
},
);
}
}
fn hash_layout(&self, state: &mut Hasher) {
@ -577,19 +712,19 @@ impl State {
/// The scrollbar of a [`Scrollable`].
#[derive(Debug)]
pub struct Scrollbar {
struct Scrollbar {
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
/// [`Scroller`].
pub outer_bounds: Rectangle,
outer_bounds: Rectangle,
/// The bounds of the [`Scrollbar`].
pub bounds: Rectangle,
bounds: Rectangle,
/// The margin within the [`Scrollbar`].
pub margin: u16,
margin: u16,
/// The bounds of the [`Scroller`].
pub scroller: Scroller,
scroller: Scroller,
}
impl Scrollbar {
@ -624,62 +759,15 @@ impl Scrollbar {
/// The handle of a [`Scrollbar`].
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
struct Scroller {
/// The bounds of the [`Scroller`].
pub bounds: Rectangle,
}
/// The renderer of a [`Scrollable`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Scrollable`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: column::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Returns the [`Scrollbar`] given the bounds and content bounds of a
/// [`Scrollable`].
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
) -> Option<Scrollbar>;
/// Draws the [`Scrollable`].
///
/// It receives:
/// - the [`State`] of the [`Scrollable`]
/// - the bounds of the [`Scrollable`] widget
/// - the bounds of the [`Scrollable`] content
/// - whether the mouse is over the [`Scrollable`] or not
/// - whether the mouse is over the [`Scrollbar`] or not
/// - a optional [`Scrollbar`] to be rendered
/// - the scrolling offset
/// - the drawn content
fn draw(
&mut self,
scrollable: &State,
bounds: Rectangle,
content_bounds: Rectangle,
is_mouse_over: bool,
is_mouse_over_scrollbar: bool,
scrollbar: Option<Scrollbar>,
offset: u32,
style: &Self::Style,
content: Self::Output,
) -> Self::Output;
bounds: Rectangle,
}
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(

View file

@ -4,12 +4,17 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
Background, Clipboard, Color, Element, Hasher, Layout, Length, Point,
Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
use std::hash::Hash;
use std::ops::RangeInclusive;
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// # Example
/// ```
/// # use iced_native::{slider, renderer::Null};
/// # use iced_native::widget::slider::{self, Slider};
/// #
/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
@ -37,7 +41,7 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
pub struct Slider<'a, T, Message> {
state: &'a mut State,
range: RangeInclusive<T>,
step: T,
@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
on_release: Option<Message>,
width: Length,
height: u16,
style: Renderer::Style,
style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
impl<'a, T, Message> Slider<'a, T, Message>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Renderer: self::Renderer,
{
/// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: u16 = 22;
/// Creates a new [`Slider`].
///
/// It expects:
@ -93,8 +99,8 @@ where
on_change: Box::new(on_change),
on_release: None,
width: Length::Fill,
height: Renderer::DEFAULT_HEIGHT,
style: Renderer::Style::default(),
height: Self::DEFAULT_HEIGHT,
style_sheet: Default::default(),
}
}
@ -122,8 +128,11 @@ where
}
/// Sets the style of the [`Slider`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
pub fn style(
mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self
}
@ -148,11 +157,11 @@ impl State {
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for Slider<'a, T, Message, Renderer>
for Slider<'a, T, Message>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@ -246,22 +255,113 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
let start = *self.range.start();
let end = *self.range.end();
) {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
renderer.draw(
layout.bounds(),
cursor_position,
start.into() as f32..=end.into() as f32,
self.value.into() as f32,
self.state.is_dragging,
&self.style,
)
let style = if self.state.is_dragging {
self.style_sheet.dragging()
} else if is_mouse_over {
self.style_sheet.hovered()
} else {
self.style_sheet.active()
};
let rail_y = bounds.y + (bounds.height / 2.0).round();
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y,
width: bounds.width,
height: 2.0,
},
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.rail_colors.0,
);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y + 2.0,
width: bounds.width,
height: 2.0,
},
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color(style.rail_colors.1),
);
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
{
HandleShape::Circle { radius } => {
(radius * 2.0, radius * 2.0, radius)
}
HandleShape::Rectangle {
width,
border_radius,
} => (f32::from(width), f32::from(bounds.height), border_radius),
};
let value = self.value.into() as f32;
let (range_start, range_end) = {
let (start, end) = self.range.clone().into_inner();
(start.into() as f32, end.into() as f32)
};
let handle_offset = if range_start >= range_end {
0.0
} else {
(bounds.width - handle_width) * (value - range_start)
/ (range_end - range_start)
};
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x + handle_offset.round(),
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
},
border_radius: handle_border_radius,
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
style.handle.color,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) -> mouse::Interaction {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
if self.state.is_dragging {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
mouse::Interaction::default()
}
}
fn hash_layout(&self, state: &mut Hasher) {
@ -272,48 +372,14 @@ where
}
}
/// The renderer of a [`Slider`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Slider`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`Slider`].
const DEFAULT_HEIGHT: u16;
/// Draws a [`Slider`].
///
/// It receives:
/// * the current cursor position
/// * the bounds of the [`Slider`]
/// * the local state of the [`Slider`]
/// * the range of values of the [`Slider`]
/// * the current value of the [`Slider`]
fn draw(
&mut self,
bounds: Rectangle,
cursor_position: Point,
range: RangeInclusive<f32>,
value: f32,
is_dragging: bool,
style: &Self::Style,
) -> Self::Output;
}
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
{
fn from(
slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
}

View file

@ -1,9 +1,9 @@
//! Distribute content vertically.
use std::hash::Hash;
use crate::layout;
use crate::renderer;
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use crate::{
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::hash::Hash;
/// An amount of empty space.
///
@ -39,7 +39,7 @@ impl Space {
impl<Message, Renderer> Widget<Message, Renderer> for Space
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
@ -61,13 +61,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_renderer: &mut Renderer,
_style: &renderer::Style,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds())
) {
}
fn hash_layout(&self, state: &mut Hasher) {
@ -78,17 +77,9 @@ where
}
}
/// The renderer of an amount of [`Space`].
pub trait Renderer: crate::Renderer {
/// Draws an amount of empty [`Space`].
///
/// You should most likely return an empty primitive here.
fn draw(&mut self, bounds: Rectangle) -> Self::Output;
}
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
Message: 'a,
{
fn from(space: Space) -> Element<'a, Message, Renderer> {

View file

@ -1,12 +1,11 @@
//! Display vector graphics in your application.
use crate::layout;
use crate::renderer;
use crate::svg::{self, Handle};
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
path::PathBuf,
sync::Arc,
};
use std::hash::Hash;
use std::path::PathBuf;
/// A vector graphics image.
///
@ -52,7 +51,7 @@ impl Svg {
impl<Message, Renderer> Widget<Message, Renderer> for Svg
where
Renderer: self::Renderer,
Renderer: svg::Renderer,
{
fn width(&self) -> Length {
self.width
@ -90,12 +89,12 @@ where
fn draw(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
) {
renderer.draw(self.handle.clone(), layout.bounds())
}
fn hash_layout(&self, state: &mut Hasher) {
@ -107,94 +106,9 @@ where
}
}
/// An [`Svg`] handle.
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
data: Arc<Data>,
}
impl Handle {
/// Creates an SVG [`Handle`] pointing to the vector image of the given
/// path.
pub fn from_path(path: impl Into<PathBuf>) -> Handle {
Self::from_data(Data::Path(path.into()))
}
/// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
/// or gzip compressed data.
///
/// This is useful if you already have your SVG data in-memory, maybe
/// because you downloaded or generated it procedurally.
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
Self::from_data(Data::Bytes(bytes.into()))
}
fn from_data(data: Data) -> Handle {
let mut hasher = Hasher::default();
data.hash(&mut hasher);
Handle {
id: hasher.finish(),
data: Arc::new(data),
}
}
/// Returns the unique identifier of the [`Handle`].
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the SVG [`Data`].
pub fn data(&self) -> &Data {
&self.data
}
}
impl Hash for Handle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
/// The data of an [`Svg`].
#[derive(Clone, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
/// In-memory data
///
/// Can contain an SVG string or a gzip compressed data.
Bytes(Vec<u8>),
}
impl std::fmt::Debug for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Data::Path(path) => write!(f, "Path({:?})", path),
Data::Bytes(_) => write!(f, "Bytes(...)"),
}
}
}
/// The renderer of an [`Svg`].
///
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Svg`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Svg`].
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: svg::Renderer,
{
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
Element::new(icon)

View file

@ -1,12 +1,12 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
use crate::renderer;
use crate::text;
use crate::{
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
pub use iced_core::text::Hit;
use std::hash::Hash;
/// A paragraph of text.
@ -14,7 +14,7 @@ use std::hash::Hash;
/// # Example
///
/// ```
/// # type Text = iced_native::Text<iced_native::renderer::Null>;
/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .color([0.0, 0.0, 1.0])
@ -23,7 +23,7 @@ use std::hash::Hash;
///
/// ![Text drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[derive(Debug)]
pub struct Text<Renderer: self::Renderer> {
pub struct Text<Renderer: text::Renderer> {
content: String,
size: Option<u16>,
color: Option<Color>,
@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {
vertical_alignment: alignment::Vertical,
}
impl<Renderer: self::Renderer> Text<Renderer> {
impl<Renderer: text::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
where
Renderer: self::Renderer,
Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@ -134,21 +134,22 @@ where
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
layout.bounds(),
) {
draw(
renderer,
style,
layout,
&self.content,
self.size.unwrap_or(renderer.default_size()),
self.font,
self.size,
self.color,
self.horizontal_alignment,
self.vertical_alignment,
)
);
}
fn hash_layout(&self, state: &mut Hasher) {
@ -162,79 +163,65 @@ where
}
}
/// The renderer of a [`Text`] fragment.
/// Draws text using the same logic as the [`Text`] widget.
///
/// Your [renderer] will need to implement this trait before being
/// able to use [`Text`] in your user interface.
/// Specifically:
///
/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The font type used for [`Text`].
type Font: Default + Copy;
/// * If no `size` is provided, the default text size of the `Renderer` will be
/// used.
/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
/// used.
/// * The alignment attributes do not affect the position of the bounds of the
/// [`Layout`].
pub fn draw<Renderer>(
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
content: &str,
font: Renderer::Font,
size: Option<u16>,
color: Option<Color>,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
) where
Renderer: text::Renderer,
{
let bounds = layout.bounds();
/// Returns the default size of [`Text`].
fn default_size(&self) -> u16;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.center_x(),
alignment::Horizontal::Right => bounds.x + bounds.width,
};
/// 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);
let y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.center_y(),
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
/// Tests whether the provided point is within the boundaries of [`Text`]
/// laid out with the given parameters, returning information about
/// the nearest character.
///
/// If `nearest_only` is true, the hit test does not consider whether the
/// the point is interior to any glyph bounds, returning only the character
/// with the nearest centeroid.
fn hit_test(
&self,
contents: &str,
size: f32,
font: Self::Font,
bounds: Size,
point: Point,
nearest_only: bool,
) -> Option<Hit>;
/// Draws a [`Text`] fragment.
///
/// It receives:
/// * the bounds of the [`Text`]
/// * the contents of the [`Text`]
/// * the size of the [`Text`]
/// * the color of the [`Text`]
/// * the [`HorizontalAlignment`] of the [`Text`]
/// * the [`VerticalAlignment`] of the [`Text`]
fn draw(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
content: &str,
size: u16,
font: Self::Font,
color: Option<Color>,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
) -> Self::Output;
renderer.fill_text(crate::text::Text {
content,
size: f32::from(size.unwrap_or(renderer.default_size())),
bounds: Rectangle { x, y, ..bounds },
color: color.unwrap_or(style.text_color),
font,
horizontal_alignment,
vertical_alignment,
});
}
impl<'a, Message, Renderer> From<Text<Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: self::Renderer + 'a,
Renderer: text::Renderer + 'a,
{
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(text)
}
}
impl<Renderer: self::Renderer> Clone for Text<Renderer> {
impl<Renderer: text::Renderer> Clone for Text<Renderer> {
fn clone(&self) -> Self {
Self {
content: self.content.clone(),

View file

@ -11,26 +11,31 @@ pub use value::Value;
use editor::Editor;
use crate::alignment;
use crate::event::{self, Event};
use crate::keyboard;
use crate::layout;
use crate::mouse::{self, click};
use crate::text;
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
use crate::{
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
Size, Widget,
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Size, Vector, Widget,
};
use std::u32;
pub use iced_style::text_input::{Style, StyleSheet};
/// A field that can be filled with text.
///
/// # Example
/// ```
/// # use iced_native::{text_input, renderer::Null};
/// # use iced_native::renderer::Null;
/// # use iced_native::widget::text_input;
/// #
/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),
@ -49,7 +54,7 @@ use std::u32;
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct TextInput<'a, Message, Renderer: self::Renderer> {
pub struct TextInput<'a, Message, Renderer: text::Renderer> {
state: &'a mut State,
placeholder: String,
value: Value,
@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
style: Renderer::Style,
style_sheet: Box<dyn StyleSheet + 'a>,
}
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: self::Renderer,
Renderer: text::Renderer,
{
/// Creates a new [`TextInput`].
///
@ -97,7 +102,7 @@ where
size: None,
on_change: Box::new(on_change),
on_submit: None,
style: Renderer::Style::default(),
style_sheet: Default::default(),
}
}
@ -147,8 +152,11 @@ where
}
/// Sets the style of the [`TextInput`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
pub fn style(
mut self,
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
) -> Self {
self.style_sheet = style_sheet.into();
self
}
@ -160,7 +168,7 @@ where
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: text::Renderer,
{
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
@ -170,37 +178,165 @@ where
layout: Layout<'_>,
cursor_position: Point,
value: Option<&Value>,
) -> Renderer::Output {
) {
let value = value.unwrap_or(&self.value);
let secure_value = self.is_secure.then(|| value.secure());
let value = secure_value.as_ref().unwrap_or(&value);
let bounds = layout.bounds();
let text_bounds = layout.children().next().unwrap().bounds();
if self.is_secure {
self::Renderer::draw(
renderer,
bounds,
text_bounds,
cursor_position,
self.font,
self.size.unwrap_or(renderer.default_size()),
&self.placeholder,
&value.secure(),
&self.state,
&self.style,
)
let is_mouse_over = bounds.contains(cursor_position);
let style = if self.state.is_focused() {
self.style_sheet.focused()
} else if is_mouse_over {
self.style_sheet.hovered()
} else {
self::Renderer::draw(
renderer,
self.style_sheet.active()
};
renderer.fill_quad(
renderer::Quad {
bounds,
text_bounds,
cursor_position,
self.font,
self.size.unwrap_or(renderer.default_size()),
&self.placeholder,
value,
&self.state,
&self.style,
)
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
},
style.background,
);
let text = value.to_string();
let size = self.size.unwrap_or(renderer.default_size());
let (cursor, offset) = if self.state.is_focused() {
match self.state.cursor.state(&value) {
cursor::State::Index(position) => {
let (text_value_width, offset) =
measure_cursor_and_scroll_offset(
renderer,
text_bounds,
&value,
size,
position,
self.font,
);
(
Some((
renderer::Quad {
bounds: Rectangle {
x: text_bounds.x + text_value_width,
y: text_bounds.y,
width: 1.0,
height: text_bounds.height,
},
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
self.style_sheet.value_color(),
)),
offset,
)
}
cursor::State::Selection { start, end } => {
let left = start.min(end);
let right = end.max(start);
let (left_position, left_offset) =
measure_cursor_and_scroll_offset(
renderer,
text_bounds,
&value,
size,
left,
self.font,
);
let (right_position, right_offset) =
measure_cursor_and_scroll_offset(
renderer,
text_bounds,
&value,
size,
right,
self.font,
);
let width = right_position - left_position;
(
Some((
renderer::Quad {
bounds: Rectangle {
x: text_bounds.x + left_position,
y: text_bounds.y,
width,
height: text_bounds.height,
},
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
self.style_sheet.selection_color(),
)),
if end == right {
right_offset
} else {
left_offset
},
)
}
}
} else {
(None, 0.0)
};
let text_width = renderer.measure_width(
if text.is_empty() {
&self.placeholder
} else {
&text
},
size,
self.font,
);
let render = |renderer: &mut Renderer| {
if let Some((cursor, color)) = cursor {
renderer.fill_quad(cursor, color);
}
renderer.fill_text(Text {
content: if text.is_empty() {
&self.placeholder
} else {
&text
},
color: if text.is_empty() {
self.style_sheet.placeholder_color()
} else {
self.style_sheet.value_color()
},
font: self.font,
bounds: Rectangle {
y: text_bounds.center_y(),
width: f32::INFINITY,
..text_bounds
},
size: f32::from(size),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
});
};
if text_width > text_bounds.width {
renderer.with_layer(text_bounds, |renderer| {
renderer.with_translation(Vector::new(-offset, 0.0), render)
});
} else {
render(renderer);
}
}
}
@ -209,7 +345,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: self::Renderer,
Renderer: text::Renderer,
{
fn width(&self) -> Length {
self.width
@ -275,7 +411,8 @@ where
self.value.clone()
};
renderer.find_cursor_position(
find_cursor_position(
renderer,
text_layout.bounds(),
self.font,
self.size,
@ -294,16 +431,16 @@ where
if self.is_secure {
self.state.cursor.select_all(&self.value);
} else {
let position = renderer
.find_cursor_position(
text_layout.bounds(),
self.font,
self.size,
&self.value,
&self.state,
target,
)
.unwrap_or(0);
let position = find_cursor_position(
renderer,
text_layout.bounds(),
self.font,
self.size,
&self.value,
&self.state,
target,
)
.unwrap_or(0);
self.state.cursor.select_range(
self.value.previous_start_of_word(position),
@ -341,16 +478,16 @@ where
self.value.clone()
};
let position = renderer
.find_cursor_position(
text_layout.bounds(),
self.font,
self.size,
&value,
&self.state,
target,
)
.unwrap_or(0);
let position = find_cursor_position(
renderer,
text_layout.bounds(),
self.font,
self.size,
&value,
&self.state,
target,
)
.unwrap_or(0);
self.state.cursor.select_range(
self.state.cursor.start(&value),
@ -621,14 +758,27 @@ where
event::Status::Ignored
}
fn draw(
fn mouse_interaction(
&self,
renderer: &mut Renderer,
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
_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)
}
@ -644,87 +794,11 @@ where
}
}
/// The renderer of a [`TextInput`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`TextInput`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Returns the width of the value of the [`TextInput`].
fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
/// Returns the current horizontal offset of the value of the
/// [`TextInput`].
///
/// This is the amount of horizontal scrolling applied when the [`Value`]
/// does not fit the [`TextInput`].
fn offset(
&self,
text_bounds: Rectangle,
font: Self::Font,
size: u16,
value: &Value,
state: &State,
) -> f32;
/// Draws a [`TextInput`].
///
/// It receives:
/// - the bounds of the [`TextInput`]
/// - the bounds of the text (i.e. the current value)
/// - the cursor position
/// - the placeholder to show when the value is empty
/// - the current [`Value`]
/// - the current [`State`]
fn draw(
&mut self,
bounds: Rectangle,
text_bounds: Rectangle,
cursor_position: Point,
font: Self::Font,
size: u16,
placeholder: &str,
value: &Value,
state: &State,
style: &Self::Style,
) -> Self::Output;
/// Computes the position of the text cursor at the given X coordinate of
/// a [`TextInput`].
fn find_cursor_position(
&self,
text_bounds: Rectangle,
font: Self::Font,
size: Option<u16>,
value: &Value,
state: &State,
x: f32,
) -> Option<usize> {
let size = size.unwrap_or(self.default_size());
let offset = self.offset(text_bounds, font, size, &value, &state);
self.hit_test(
&value.to_string(),
size.into(),
font,
Size::INFINITY,
Point::new(x + offset, text_bounds.height / 2.0),
true,
)
.map(text::Hit::cursor)
}
}
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + self::Renderer,
Renderer: 'a + text::Renderer,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
@ -815,3 +889,88 @@ mod platform {
}
}
}
fn offset<Renderer>(
renderer: &Renderer,
text_bounds: Rectangle,
font: Renderer::Font,
size: u16,
value: &Value,
state: &State,
) -> f32
where
Renderer: text::Renderer,
{
if state.is_focused() {
let cursor = state.cursor();
let focus_position = match cursor.state(value) {
cursor::State::Index(i) => i,
cursor::State::Selection { end, .. } => end,
};
let (_, offset) = measure_cursor_and_scroll_offset(
renderer,
text_bounds,
value,
size,
focus_position,
font,
);
offset
} else {
0.0
}
}
fn measure_cursor_and_scroll_offset<Renderer>(
renderer: &Renderer,
text_bounds: Rectangle,
value: &Value,
size: u16,
cursor_index: usize,
font: Renderer::Font,
) -> (f32, f32)
where
Renderer: text::Renderer,
{
let text_before_cursor = value.until(cursor_index).to_string();
let text_value_width =
renderer.measure_width(&text_before_cursor, size, font);
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
(text_value_width, offset)
}
/// Computes the position of the text cursor at the given X coordinate of
/// a [`TextInput`].
fn find_cursor_position<Renderer>(
renderer: &Renderer,
text_bounds: Rectangle,
font: Renderer::Font,
size: Option<u16>,
value: &Value,
state: &State,
x: f32,
) -> Option<usize>
where
Renderer: text::Renderer,
{
let size = size.unwrap_or(renderer.default_size());
let offset = offset(renderer, text_bounds, font, size, &value, &state);
renderer
.hit_test(
&value.to_string(),
size.into(),
font,
Size::INFINITY,
Point::new(x + offset, text_bounds.height / 2.0),
true,
)
.map(text::Hit::cursor)
}

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