Merge pull request #1110 from iced-rs/remove-renderer-traits
Reduce the surface of the `Renderer` APIs
This commit is contained in:
commit
eafad00af2
142 changed files with 3344 additions and 4588 deletions
|
|
@ -17,7 +17,6 @@
|
||||||
pub mod alignment;
|
pub mod alignment;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
pub mod text;
|
|
||||||
|
|
||||||
mod background;
|
mod background;
|
||||||
mod color;
|
mod color;
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,4 +8,3 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../.." }
|
iced = { path = "../.." }
|
||||||
iced_native = { path = "../../native" }
|
iced_native = { path = "../../native" }
|
||||||
iced_graphics = { path = "../../graphics" }
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ mod circle {
|
||||||
// Of course, you can choose to make the implementation renderer-agnostic,
|
// Of course, you can choose to make the implementation renderer-agnostic,
|
||||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||||
// implemented by `iced_wgpu` and other renderers.
|
// implemented by `iced_wgpu` and other renderers.
|
||||||
use iced_graphics::{Backend, Defaults, Primitive, Renderer};
|
use iced_native::layout::{self, Layout};
|
||||||
|
use iced_native::renderer;
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
layout, mouse, Background, Color, Element, Hasher, Layout, Length,
|
Color, Element, Hasher, Length, Point, Rectangle, Size, Widget,
|
||||||
Point, Rectangle, Size, Widget,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Circle {
|
pub struct Circle {
|
||||||
|
|
@ -25,9 +25,9 @@ mod circle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, B> Widget<Message, Renderer<B>> for Circle
|
impl<Message, Renderer> Widget<Message, Renderer> for Circle
|
||||||
where
|
where
|
||||||
B: Backend,
|
Renderer: renderer::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
Length::Shrink
|
Length::Shrink
|
||||||
|
|
@ -39,7 +39,7 @@ mod circle {
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_renderer: &Renderer<B>,
|
_renderer: &Renderer,
|
||||||
_limits: &layout::Limits,
|
_limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
|
layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
|
||||||
|
|
@ -53,30 +53,29 @@ mod circle {
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_renderer: &mut Renderer<B>,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> (Primitive, mouse::Interaction) {
|
) {
|
||||||
(
|
renderer.fill_quad(
|
||||||
Primitive::Quad {
|
renderer::Quad {
|
||||||
bounds: layout.bounds(),
|
bounds: layout.bounds(),
|
||||||
background: Background::Color(Color::BLACK),
|
|
||||||
border_radius: self.radius,
|
border_radius: self.radius,
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
mouse::Interaction::default(),
|
Color::BLACK,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Circle
|
impl<'a, Message, Renderer> Into<Element<'a, Message, Renderer>> for Circle
|
||||||
where
|
where
|
||||||
B: Backend,
|
Renderer: renderer::Renderer,
|
||||||
{
|
{
|
||||||
fn into(self) -> Element<'a, Message, Renderer<B>> {
|
fn into(self) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(self)
|
Element::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@ mod rainbow {
|
||||||
// Of course, you can choose to make the implementation renderer-agnostic,
|
// Of course, you can choose to make the implementation renderer-agnostic,
|
||||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||||
// implemented by `iced_wgpu` and other renderers.
|
// implemented by `iced_wgpu` and other renderers.
|
||||||
use iced_graphics::{
|
use iced_graphics::renderer::{self, Renderer};
|
||||||
triangle::{Mesh2D, Vertex2D},
|
use iced_graphics::{Backend, Primitive};
|
||||||
Backend, Defaults, Primitive, Renderer,
|
|
||||||
};
|
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
|
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||||
Vector, Widget,
|
Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -53,12 +52,15 @@ mod rainbow {
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_renderer: &mut Renderer<B>,
|
renderer: &mut Renderer<B>,
|
||||||
_defaults: &Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> (Primitive, mouse::Interaction) {
|
) {
|
||||||
|
use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
||||||
|
use iced_native::Renderer as _;
|
||||||
|
|
||||||
let b = layout.bounds();
|
let b = layout.bounds();
|
||||||
|
|
||||||
// R O Y G B I V
|
// R O Y G B I V
|
||||||
|
|
@ -88,10 +90,7 @@ mod rainbow {
|
||||||
let posn_bl = [0.0, b.height];
|
let posn_bl = [0.0, b.height];
|
||||||
let posn_l = [0.0, b.height / 2.0];
|
let posn_l = [0.0, b.height / 2.0];
|
||||||
|
|
||||||
(
|
let mesh = Primitive::Mesh2D {
|
||||||
Primitive::Translate {
|
|
||||||
translation: Vector::new(b.x, b.y),
|
|
||||||
content: Box::new(Primitive::Mesh2D {
|
|
||||||
size: b.size(),
|
size: b.size(),
|
||||||
buffers: Mesh2D {
|
buffers: Mesh2D {
|
||||||
vertices: vec![
|
vertices: vec![
|
||||||
|
|
@ -143,10 +142,11 @@ mod rainbow {
|
||||||
0, 8, 1, // L
|
0, 8, 1, // L
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
};
|
||||||
},
|
|
||||||
mouse::Interaction::default(),
|
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
|
||||||
)
|
renderer.draw_primitive(mesh);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use iced_glow::Renderer;
|
use iced_glow::Renderer;
|
||||||
use iced_glutin::slider;
|
use iced_glutin::widget::slider::{self, Slider};
|
||||||
use iced_glutin::{
|
use iced_glutin::widget::{Column, Row, Text};
|
||||||
Alignment, Color, Column, Command, Element, Length, Program, Row, Slider,
|
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
|
||||||
Text,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Controls {
|
pub struct Controls {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ pub fn main() {
|
||||||
let mut state = program::State::new(
|
let mut state = program::State::new(
|
||||||
controls,
|
controls,
|
||||||
viewport.logical_size(),
|
viewport.logical_size(),
|
||||||
conversion::cursor_position(cursor_position, viewport.scale_factor()),
|
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
&mut debug,
|
&mut debug,
|
||||||
);
|
);
|
||||||
|
|
@ -160,16 +159,19 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And then iced on top
|
// And then iced on top
|
||||||
let mouse_interaction = renderer.backend_mut().draw(
|
renderer.with_primitives(|backend, primitive| {
|
||||||
|
backend.present(
|
||||||
&gl,
|
&gl,
|
||||||
|
primitive,
|
||||||
&viewport,
|
&viewport,
|
||||||
state.primitive(),
|
|
||||||
&debug.overlay(),
|
&debug.overlay(),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Update the mouse cursor
|
// Update the mouse cursor
|
||||||
windowed_context.window().set_cursor_icon(
|
windowed_context.window().set_cursor_icon(
|
||||||
iced_winit::conversion::mouse_interaction(
|
iced_winit::conversion::mouse_interaction(
|
||||||
mouse_interaction,
|
state.mouse_interaction(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
use iced_wgpu::Renderer;
|
use iced_wgpu::Renderer;
|
||||||
use iced_winit::{
|
use iced_winit::widget::slider::{self, Slider};
|
||||||
slider, Alignment, Color, Column, Command, Element, Length, Program, Row,
|
use iced_winit::widget::{Column, Row, Text};
|
||||||
Slider, Text,
|
use iced_winit::{Alignment, Color, Command, Element, Length, Program};
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Controls {
|
pub struct Controls {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@ pub fn main() {
|
||||||
let mut state = program::State::new(
|
let mut state = program::State::new(
|
||||||
controls,
|
controls,
|
||||||
viewport.logical_size(),
|
viewport.logical_size(),
|
||||||
conversion::cursor_position(cursor_position, viewport.scale_factor()),
|
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
&mut debug,
|
&mut debug,
|
||||||
);
|
);
|
||||||
|
|
@ -196,15 +195,17 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And then iced on top
|
// And then iced on top
|
||||||
let mouse_interaction = renderer.backend_mut().draw(
|
renderer.with_primitives(|backend, primitive| {
|
||||||
|
backend.present(
|
||||||
&mut device,
|
&mut device,
|
||||||
&mut staging_belt,
|
&mut staging_belt,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
&view,
|
&view,
|
||||||
|
primitive,
|
||||||
&viewport,
|
&viewport,
|
||||||
state.primitive(),
|
|
||||||
&debug.overlay(),
|
&debug.overlay(),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Then we submit the work
|
// Then we submit the work
|
||||||
staging_belt.finish();
|
staging_belt.finish();
|
||||||
|
|
@ -214,7 +215,7 @@ pub fn main() {
|
||||||
// Update the mouse cursor
|
// Update the mouse cursor
|
||||||
window.set_cursor_icon(
|
window.set_cursor_icon(
|
||||||
iced_winit::conversion::mouse_interaction(
|
iced_winit::conversion::mouse_interaction(
|
||||||
mouse_interaction,
|
state.mouse_interaction(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,11 @@ impl Application for Example {
|
||||||
let title_bar = pane_grid::TitleBar::new(title)
|
let title_bar = pane_grid::TitleBar::new(title)
|
||||||
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
|
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.style(style::TitleBar { is_focused });
|
.style(if is_focused {
|
||||||
|
style::TitleBar::Focused
|
||||||
|
} else {
|
||||||
|
style::TitleBar::Active
|
||||||
|
});
|
||||||
|
|
||||||
pane_grid::Content::new(pane.content.view(
|
pane_grid::Content::new(pane.content.view(
|
||||||
id,
|
id,
|
||||||
|
|
@ -185,7 +189,11 @@ impl Application for Example {
|
||||||
pane.is_pinned,
|
pane.is_pinned,
|
||||||
))
|
))
|
||||||
.title_bar(title_bar)
|
.title_bar(title_bar)
|
||||||
.style(style::Pane { is_focused })
|
.style(if is_focused {
|
||||||
|
style::Pane::Focused
|
||||||
|
} else {
|
||||||
|
style::Pane::Active
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
|
@ -387,14 +395,16 @@ mod style {
|
||||||
0xC4 as f32 / 255.0,
|
0xC4 as f32 / 255.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
pub struct TitleBar {
|
pub enum TitleBar {
|
||||||
pub is_focused: bool,
|
Active,
|
||||||
|
Focused,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl container::StyleSheet for TitleBar {
|
impl container::StyleSheet for TitleBar {
|
||||||
fn style(&self) -> container::Style {
|
fn style(&self) -> container::Style {
|
||||||
let pane = Pane {
|
let pane = match self {
|
||||||
is_focused: self.is_focused,
|
Self::Active => Pane::Active,
|
||||||
|
Self::Focused => Pane::Focused,
|
||||||
}
|
}
|
||||||
.style();
|
.style();
|
||||||
|
|
||||||
|
|
@ -406,8 +416,9 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pane {
|
pub enum Pane {
|
||||||
pub is_focused: bool,
|
Active,
|
||||||
|
Focused,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl container::StyleSheet for Pane {
|
impl container::StyleSheet for Pane {
|
||||||
|
|
@ -415,10 +426,9 @@ mod style {
|
||||||
container::Style {
|
container::Style {
|
||||||
background: Some(Background::Color(SURFACE)),
|
background: Some(Background::Color(SURFACE)),
|
||||||
border_width: 2.0,
|
border_width: 2.0,
|
||||||
border_color: if self.is_focused {
|
border_color: match self {
|
||||||
Color::BLACK
|
Self::Active => Color::from_rgb(0.7, 0.7, 0.7),
|
||||||
} else {
|
Self::Focused => Color::BLACK,
|
||||||
Color::from_rgb(0.7, 0.7, 0.7)
|
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ impl Default for Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn container::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -25,7 +25,7 @@ impl From<Theme> for Box<dyn container::StyleSheet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn radio::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -34,7 +34,7 @@ impl From<Theme> for Box<dyn radio::StyleSheet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn container::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -185,7 +185,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn radio::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -194,7 +194,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn text_input::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn text_input::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -203,7 +203,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn button::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn button::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => light::Button.into(),
|
Theme::Light => light::Button.into(),
|
||||||
|
|
@ -212,7 +212,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -221,7 +221,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn slider::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn slider::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
@ -239,7 +239,7 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn checkbox::StyleSheet> {
|
impl<'a> From<Theme> for Box<dyn checkbox::StyleSheet + 'a> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
Theme::Light => Default::default(),
|
Theme::Light => Default::default(),
|
||||||
|
|
|
||||||
|
|
@ -363,8 +363,10 @@ impl Controls {
|
||||||
let filter_button = |state, label, filter, current_filter| {
|
let filter_button = |state, label, filter, current_filter| {
|
||||||
let label = Text::new(label).size(16);
|
let label = Text::new(label).size(16);
|
||||||
let button =
|
let button =
|
||||||
Button::new(state, label).style(style::Button::Filter {
|
Button::new(state, label).style(if filter == current_filter {
|
||||||
selected: filter == current_filter,
|
style::Button::FilterSelected
|
||||||
|
} else {
|
||||||
|
style::Button::FilterActive
|
||||||
});
|
});
|
||||||
|
|
||||||
button.on_press(Message::FilterChanged(filter)).padding(8)
|
button.on_press(Message::FilterChanged(filter)).padding(8)
|
||||||
|
|
@ -602,7 +604,8 @@ mod style {
|
||||||
use iced::{button, Background, Color, Vector};
|
use iced::{button, Background, Color, Vector};
|
||||||
|
|
||||||
pub enum Button {
|
pub enum Button {
|
||||||
Filter { selected: bool },
|
FilterActive,
|
||||||
|
FilterSelected,
|
||||||
Icon,
|
Icon,
|
||||||
Destructive,
|
Destructive,
|
||||||
}
|
}
|
||||||
|
|
@ -610,20 +613,15 @@ mod style {
|
||||||
impl button::StyleSheet for Button {
|
impl button::StyleSheet for Button {
|
||||||
fn active(&self) -> button::Style {
|
fn active(&self) -> button::Style {
|
||||||
match self {
|
match self {
|
||||||
Button::Filter { selected } => {
|
Button::FilterActive => button::Style::default(),
|
||||||
if *selected {
|
Button::FilterSelected => button::Style {
|
||||||
button::Style {
|
background: Some(Background::Color(Color::from_rgb(
|
||||||
background: Some(Background::Color(
|
0.2, 0.2, 0.7,
|
||||||
Color::from_rgb(0.2, 0.2, 0.7),
|
))),
|
||||||
)),
|
|
||||||
border_radius: 10.0,
|
border_radius: 10.0,
|
||||||
text_color: Color::WHITE,
|
text_color: Color::WHITE,
|
||||||
..button::Style::default()
|
..button::Style::default()
|
||||||
}
|
},
|
||||||
} else {
|
|
||||||
button::Style::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button::Icon => button::Style {
|
Button::Icon => button::Style {
|
||||||
text_color: Color::from_rgb(0.5, 0.5, 0.5),
|
text_color: Color::from_rgb(0.5, 0.5, 0.5),
|
||||||
..button::Style::default()
|
..button::Style::default()
|
||||||
|
|
@ -646,9 +644,7 @@ mod style {
|
||||||
button::Style {
|
button::Style {
|
||||||
text_color: match self {
|
text_color: match self {
|
||||||
Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
|
Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
|
||||||
Button::Filter { selected } if !selected => {
|
Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7),
|
||||||
Color::from_rgb(0.2, 0.2, 0.7)
|
|
||||||
}
|
|
||||||
_ => active.text_color,
|
_ => active.text_color,
|
||||||
},
|
},
|
||||||
shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
|
shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,8 @@ use crate::{Settings, Transformation, Viewport};
|
||||||
|
|
||||||
use iced_graphics::backend;
|
use iced_graphics::backend;
|
||||||
use iced_graphics::font;
|
use iced_graphics::font;
|
||||||
use iced_graphics::Layer;
|
use iced_graphics::{Layer, Primitive};
|
||||||
use iced_graphics::Primitive;
|
|
||||||
use iced_native::alignment;
|
use iced_native::alignment;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::{Font, Size};
|
use iced_native::{Font, Size};
|
||||||
|
|
||||||
/// A [`glow`] graphics backend for [`iced`].
|
/// A [`glow`] graphics backend for [`iced`].
|
||||||
|
|
@ -47,18 +45,18 @@ impl Backend {
|
||||||
///
|
///
|
||||||
/// The text provided as overlay will be rendered on top of the primitives.
|
/// The text provided as overlay will be rendered on top of the primitives.
|
||||||
/// This is useful for rendering debug information.
|
/// This is useful for rendering debug information.
|
||||||
pub fn draw<T: AsRef<str>>(
|
pub fn present<T: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
|
primitives: &[Primitive],
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
(primitive, mouse_interaction): &(Primitive, mouse::Interaction),
|
|
||||||
overlay_text: &[T],
|
overlay_text: &[T],
|
||||||
) -> mouse::Interaction {
|
) {
|
||||||
let viewport_size = viewport.physical_size();
|
let viewport_size = viewport.physical_size();
|
||||||
let scale_factor = viewport.scale_factor() as f32;
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
let projection = viewport.projection();
|
let projection = viewport.projection();
|
||||||
|
|
||||||
let mut layers = Layer::generate(primitive, viewport);
|
let mut layers = Layer::generate(primitives, viewport);
|
||||||
layers.push(Layer::overlay(overlay_text, viewport));
|
layers.push(Layer::overlay(overlay_text, viewport));
|
||||||
|
|
||||||
for layer in layers {
|
for layer in layers {
|
||||||
|
|
@ -70,8 +68,6 @@ impl Backend {
|
||||||
viewport_size.height,
|
viewport_size.height,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
*mouse_interaction
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(
|
fn flush(
|
||||||
|
|
@ -83,6 +79,11 @@ impl Backend {
|
||||||
target_height: u32,
|
target_height: u32,
|
||||||
) {
|
) {
|
||||||
let mut bounds = (layer.bounds * scale_factor).snap();
|
let mut bounds = (layer.bounds * scale_factor).snap();
|
||||||
|
|
||||||
|
if bounds.width < 1 || bounds.height < 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bounds.height = bounds.height.min(target_height);
|
bounds.height = bounds.height.min(target_height);
|
||||||
|
|
||||||
if !layer.quads.is_empty() {
|
if !layer.quads.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
//!
|
//!
|
||||||
//! [`glow`]: https://github.com/grovesNL/glow
|
//! [`glow`]: https://github.com/grovesNL/glow
|
||||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||||
//#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![deny(missing_debug_implementations)]
|
#![deny(missing_debug_implementations)]
|
||||||
#![deny(unused_results)]
|
#![deny(unused_results)]
|
||||||
#![forbid(rust_2018_idioms)]
|
#![forbid(rust_2018_idioms)]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
pub mod program;
|
mod program;
|
||||||
mod quad;
|
mod quad;
|
||||||
mod text;
|
mod text;
|
||||||
mod triangle;
|
mod triangle;
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,14 @@ pub mod qr_code;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use qr_code::QRCode;
|
pub use qr_code::QRCode;
|
||||||
|
|
||||||
pub use iced_native::{Image, Space};
|
pub use iced_native::widget::{Image, Space};
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>;
|
pub type Column<'a, Message> =
|
||||||
|
iced_native::widget::Column<'a, Message, Renderer>;
|
||||||
|
|
||||||
/// A container that distributes its contents horizontally.
|
/// A container that distributes its contents horizontally.
|
||||||
pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>;
|
pub type Row<'a, Message> = iced_native::widget::Row<'a, Message, Renderer>;
|
||||||
|
|
||||||
/// A paragraph of text.
|
/// A paragraph of text.
|
||||||
pub type Text = iced_native::Text<Renderer>;
|
pub type Text = iced_native::widget::Text<Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
||||||
pub use iced_graphics::button::{Style, StyleSheet};
|
pub use iced_graphics::button::{Style, StyleSheet};
|
||||||
pub use iced_native::button::State;
|
pub use iced_native::widget::button::State;
|
||||||
|
|
||||||
/// A widget that produces a message when clicked.
|
/// A widget that produces a message when clicked.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
|
||||||
pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>;
|
pub type Button<'a, Message> =
|
||||||
|
iced_native::widget::Button<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ pub use iced_graphics::checkbox::{Style, StyleSheet};
|
||||||
/// A box that can be checked.
|
/// A box that can be checked.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||||
pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>;
|
pub type Checkbox<'a, Message> =
|
||||||
|
iced_native::widget::Checkbox<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ pub use iced_graphics::container::{Style, StyleSheet};
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` container with a default
|
/// This is an alias of an `iced_native` container with a default
|
||||||
/// `Renderer`.
|
/// `Renderer`.
|
||||||
pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>;
|
pub type Container<'a, Message> =
|
||||||
|
iced_native::widget::Container<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,13 @@ pub use iced_graphics::pane_grid::{
|
||||||
/// [](https://gfycat.com/mixedflatjellyfish)
|
/// [](https://gfycat.com/mixedflatjellyfish)
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
|
||||||
pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
|
pub type PaneGrid<'a, Message> =
|
||||||
|
iced_native::widget::PaneGrid<'a, Message, Renderer>;
|
||||||
|
|
||||||
/// The content of a [`Pane`].
|
/// The content of a [`Pane`].
|
||||||
pub type Content<'a, Message> =
|
pub type Content<'a, Message> =
|
||||||
iced_native::pane_grid::Content<'a, Message, Renderer>;
|
iced_native::widget::pane_grid::Content<'a, Message, Renderer>;
|
||||||
|
|
||||||
/// The title bar of a [`Pane`].
|
/// The title bar of a [`Pane`].
|
||||||
pub type TitleBar<'a, Message> =
|
pub type TitleBar<'a, Message> =
|
||||||
iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
|
iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//! Display a dropdown list of selectable values.
|
//! Display a dropdown list of selectable values.
|
||||||
pub use iced_native::pick_list::State;
|
pub use iced_native::widget::pick_list::State;
|
||||||
|
|
||||||
pub use iced_graphics::overlay::menu::Style as Menu;
|
pub use iced_graphics::overlay::menu::Style as Menu;
|
||||||
pub use iced_graphics::pick_list::{Style, StyleSheet};
|
pub use iced_graphics::pick_list::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A widget allowing the selection of a single value from a list of options.
|
/// A widget allowing the selection of a single value from a list of options.
|
||||||
pub type PickList<'a, T, Message> =
|
pub type PickList<'a, T, Message> =
|
||||||
iced_native::PickList<'a, T, Message, crate::Renderer>;
|
iced_native::widget::PickList<'a, T, Message, crate::Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,5 @@
|
||||||
//!
|
//!
|
||||||
//! A [`ProgressBar`] has a range of possible values and a current value,
|
//! A [`ProgressBar`] has a range of possible values and a current value,
|
||||||
//! as well as a length, height and style.
|
//! as well as a length, height and style.
|
||||||
use crate::Renderer;
|
|
||||||
|
|
||||||
pub use iced_graphics::progress_bar::{Style, StyleSheet};
|
pub use iced_graphics::progress_bar::*;
|
||||||
|
|
||||||
/// A bar that displays progress.
|
|
||||||
///
|
|
||||||
/// This is an alias of an `iced_native` progress bar with an
|
|
||||||
/// `iced_wgpu::Renderer`.
|
|
||||||
pub type ProgressBar = iced_native::ProgressBar<Renderer>;
|
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ pub use iced_graphics::radio::{Style, StyleSheet};
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` radio button with an
|
/// This is an alias of an `iced_native` radio button with an
|
||||||
/// `iced_wgpu::Renderer`.
|
/// `iced_wgpu::Renderer`.
|
||||||
pub type Radio<Message> = iced_native::Radio<Message, Renderer>;
|
pub type Radio<'a, Message> = iced_native::widget::Radio<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,3 @@
|
||||||
//! Display a horizontal or vertical rule for dividing content.
|
//! Display a horizontal or vertical rule for dividing content.
|
||||||
|
|
||||||
use crate::Renderer;
|
pub use iced_graphics::rule::*;
|
||||||
|
|
||||||
pub use iced_graphics::rule::{FillMode, Style, StyleSheet};
|
|
||||||
|
|
||||||
/// Display a horizontal or vertical rule for dividing content.
|
|
||||||
///
|
|
||||||
/// This is an alias of an `iced_native` rule with an `iced_glow::Renderer`.
|
|
||||||
pub type Rule = iced_native::Rule<Renderer>;
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
||||||
pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet};
|
pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet};
|
||||||
pub use iced_native::scrollable::State;
|
pub use iced_native::widget::scrollable::State;
|
||||||
|
|
||||||
/// A widget that can vertically display an infinite amount of content
|
/// A widget that can vertically display an infinite amount of content
|
||||||
/// with a scrollbar.
|
/// with a scrollbar.
|
||||||
|
|
@ -10,4 +10,4 @@ pub use iced_native::scrollable::State;
|
||||||
/// This is an alias of an `iced_native` scrollable with a default
|
/// This is an alias of an `iced_native` scrollable with a default
|
||||||
/// `Renderer`.
|
/// `Renderer`.
|
||||||
pub type Scrollable<'a, Message> =
|
pub type Scrollable<'a, Message> =
|
||||||
iced_native::Scrollable<'a, Message, Renderer>;
|
iced_native::widget::Scrollable<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
//! Display an interactive selector of a single value from a range of values.
|
//! Display an interactive selector of a single value from a range of values.
|
||||||
//!
|
//!
|
||||||
//! A [`Slider`] has some local [`State`].
|
//! A [`Slider`] has some local [`State`].
|
||||||
use crate::Renderer;
|
|
||||||
|
|
||||||
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
|
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||||
pub use iced_native::slider::State;
|
pub use iced_native::widget::slider::{Slider, State};
|
||||||
|
|
||||||
/// An horizontal bar and a handle that selects a single value from a range of
|
|
||||||
/// values.
|
|
||||||
///
|
|
||||||
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
|
|
||||||
pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
||||||
pub use iced_graphics::text_input::{Style, StyleSheet};
|
pub use iced_graphics::text_input::{Style, StyleSheet};
|
||||||
pub use iced_native::text_input::State;
|
pub use iced_native::widget::text_input::State;
|
||||||
|
|
||||||
/// A field that can be filled with text.
|
/// A field that can be filled with text.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
|
||||||
pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>;
|
pub type TextInput<'a, Message> =
|
||||||
|
iced_native::widget::TextInput<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ pub use iced_graphics::toggler::{Style, StyleSheet};
|
||||||
/// A toggler that can be toggled.
|
/// A toggler that can be toggled.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||||
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
|
pub type Toggler<'a, Message> =
|
||||||
|
iced_native::widget::Toggler<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Display a widget over another.
|
//! Display a widget over another.
|
||||||
/// A widget allowing the selection of a single value from a list of options.
|
/// A widget allowing the selection of a single value from a list of options.
|
||||||
pub type Tooltip<'a, Message> =
|
pub type Tooltip<'a, Message> =
|
||||||
iced_native::Tooltip<'a, Message, crate::Renderer>;
|
iced_native::widget::Tooltip<'a, Message, crate::Renderer>;
|
||||||
|
|
||||||
pub use iced_native::tooltip::Position;
|
pub use iced_native::widget::tooltip::Position;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
|
||||||
use core::ffi::c_void;
|
use core::ffi::c_void;
|
||||||
use glow::HasContext;
|
use glow::HasContext;
|
||||||
use iced_graphics::{Antialiasing, Size};
|
use iced_graphics::{Antialiasing, Size};
|
||||||
use iced_native::mouse;
|
|
||||||
|
|
||||||
/// A window graphics backend for iced powered by `glow`.
|
/// A window graphics backend for iced powered by `glow`.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
|
|
@ -59,14 +58,13 @@ impl iced_graphics::window::GLCompositor for Compositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw<T: AsRef<str>>(
|
fn present<T: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
color: Color,
|
color: Color,
|
||||||
output: &<Self::Renderer as iced_native::Renderer>::Output,
|
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> mouse::Interaction {
|
) {
|
||||||
let gl = &self.gl;
|
let gl = &self.gl;
|
||||||
|
|
||||||
let [r, g, b, a] = color.into_linear();
|
let [r, g, b, a] = color.into_linear();
|
||||||
|
|
@ -76,6 +74,8 @@ impl iced_graphics::window::GLCompositor for Compositor {
|
||||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.backend_mut().draw(gl, viewport, output, overlay)
|
renderer.with_primitives(|backend, primitive| {
|
||||||
|
backend.present(gl, primitive, viewport, overlay);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Create interactive, native cross-platform applications.
|
//! Create interactive, native cross-platform applications.
|
||||||
use crate::{mouse, Error, Executor, Runtime};
|
use crate::mouse;
|
||||||
|
use crate::{Error, Executor, Runtime};
|
||||||
|
|
||||||
pub use iced_winit::Application;
|
pub use iced_winit::Application;
|
||||||
|
|
||||||
|
|
@ -179,10 +180,7 @@ async fn run_instance<A, E, C>(
|
||||||
&mut debug,
|
&mut debug,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut primitive =
|
|
||||||
user_interface.draw(&mut renderer, state.cursor_position());
|
|
||||||
let mut mouse_interaction = mouse::Interaction::default();
|
let mut mouse_interaction = mouse::Interaction::default();
|
||||||
|
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
|
|
||||||
|
|
@ -246,10 +244,18 @@ async fn run_instance<A, E, C>(
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.draw_started();
|
debug.draw_started();
|
||||||
primitive =
|
let new_mouse_interaction =
|
||||||
user_interface.draw(&mut renderer, state.cursor_position());
|
user_interface.draw(&mut renderer, state.cursor_position());
|
||||||
debug.draw_finished();
|
debug.draw_finished();
|
||||||
|
|
||||||
|
if new_mouse_interaction != mouse_interaction {
|
||||||
|
context.window().set_cursor_icon(
|
||||||
|
conversion::mouse_interaction(new_mouse_interaction),
|
||||||
|
);
|
||||||
|
|
||||||
|
mouse_interaction = new_mouse_interaction;
|
||||||
|
}
|
||||||
|
|
||||||
context.window().request_redraw();
|
context.window().request_redraw();
|
||||||
}
|
}
|
||||||
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
||||||
|
|
@ -291,10 +297,20 @@ async fn run_instance<A, E, C>(
|
||||||
debug.layout_finished();
|
debug.layout_finished();
|
||||||
|
|
||||||
debug.draw_started();
|
debug.draw_started();
|
||||||
primitive = user_interface
|
let new_mouse_interaction = user_interface
|
||||||
.draw(&mut renderer, state.cursor_position());
|
.draw(&mut renderer, state.cursor_position());
|
||||||
debug.draw_finished();
|
debug.draw_finished();
|
||||||
|
|
||||||
|
if new_mouse_interaction != mouse_interaction {
|
||||||
|
context.window().set_cursor_icon(
|
||||||
|
conversion::mouse_interaction(
|
||||||
|
new_mouse_interaction,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
mouse_interaction = new_mouse_interaction;
|
||||||
|
}
|
||||||
|
|
||||||
context.resize(glutin::dpi::PhysicalSize::new(
|
context.resize(glutin::dpi::PhysicalSize::new(
|
||||||
physical_size.width,
|
physical_size.width,
|
||||||
physical_size.height,
|
physical_size.height,
|
||||||
|
|
@ -305,11 +321,10 @@ async fn run_instance<A, E, C>(
|
||||||
viewport_version = current_viewport_version;
|
viewport_version = current_viewport_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_mouse_interaction = compositor.draw(
|
compositor.present(
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
state.viewport(),
|
state.viewport(),
|
||||||
state.background_color(),
|
state.background_color(),
|
||||||
&primitive,
|
|
||||||
&debug.overlay(),
|
&debug.overlay(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -317,14 +332,6 @@ async fn run_instance<A, E, C>(
|
||||||
|
|
||||||
debug.render_finished();
|
debug.render_finished();
|
||||||
|
|
||||||
if new_mouse_interaction != mouse_interaction {
|
|
||||||
context.window().set_cursor_icon(
|
|
||||||
conversion::mouse_interaction(new_mouse_interaction),
|
|
||||||
);
|
|
||||||
|
|
||||||
mouse_interaction = new_mouse_interaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle animations!
|
// TODO: Handle animations!
|
||||||
// Maybe we can use `ControlFlow::WaitUntil` for this.
|
// Maybe we can use `ControlFlow::WaitUntil` for this.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl<'a> Layer<'a> {
|
||||||
/// Distributes the given [`Primitive`] and generates a list of layers based
|
/// Distributes the given [`Primitive`] and generates a list of layers based
|
||||||
/// on its contents.
|
/// on its contents.
|
||||||
pub fn generate(
|
pub fn generate(
|
||||||
primitive: &'a Primitive,
|
primitives: &'a [Primitive],
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
let first_layer =
|
let first_layer =
|
||||||
|
|
@ -82,12 +82,14 @@ impl<'a> Layer<'a> {
|
||||||
|
|
||||||
let mut layers = vec![first_layer];
|
let mut layers = vec![first_layer];
|
||||||
|
|
||||||
|
for primitive in primitives {
|
||||||
Self::process_primitive(
|
Self::process_primitive(
|
||||||
&mut layers,
|
&mut layers,
|
||||||
Vector::new(0.0, 0.0),
|
Vector::new(0.0, 0.0),
|
||||||
primitive,
|
primitive,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
layers
|
layers
|
||||||
}
|
}
|
||||||
|
|
@ -173,11 +175,7 @@ impl<'a> Layer<'a> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primitive::Clip {
|
Primitive::Clip { bounds, content } => {
|
||||||
bounds,
|
|
||||||
offset,
|
|
||||||
content,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
let layer = &mut layers[current_layer];
|
||||||
let translated_bounds = *bounds + translation;
|
let translated_bounds = *bounds + translation;
|
||||||
|
|
||||||
|
|
@ -190,8 +188,7 @@ impl<'a> Layer<'a> {
|
||||||
|
|
||||||
Self::process_primitive(
|
Self::process_primitive(
|
||||||
layers,
|
layers,
|
||||||
translation
|
translation,
|
||||||
- Vector::new(offset.x as f32, offset.y as f32),
|
|
||||||
content,
|
content,
|
||||||
layers.len() - 1,
|
layers.len() - 1,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,14 @@
|
||||||
mod antialiasing;
|
mod antialiasing;
|
||||||
mod error;
|
mod error;
|
||||||
mod primitive;
|
mod primitive;
|
||||||
mod renderer;
|
|
||||||
mod transformation;
|
mod transformation;
|
||||||
mod viewport;
|
mod viewport;
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod defaults;
|
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
|
pub mod renderer;
|
||||||
pub mod triangle;
|
pub mod triangle;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
@ -31,7 +30,6 @@ pub use widget::*;
|
||||||
|
|
||||||
pub use antialiasing::Antialiasing;
|
pub use antialiasing::Antialiasing;
|
||||||
pub use backend::Backend;
|
pub use backend::Backend;
|
||||||
pub use defaults::Defaults;
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use layer::Layer;
|
pub use layer::Layer;
|
||||||
pub use primitive::Primitive;
|
pub use primitive::Primitive;
|
||||||
|
|
|
||||||
|
|
@ -1,116 +1,3 @@
|
||||||
//! Build and show dropdown menus.
|
//! Build and show dropdown menus.
|
||||||
use crate::alignment;
|
|
||||||
use crate::backend::{self, Backend};
|
|
||||||
use crate::{Primitive, Renderer};
|
|
||||||
|
|
||||||
use iced_native::{mouse, overlay, Color, Font, Padding, Point, Rectangle};
|
|
||||||
|
|
||||||
pub use iced_style::menu::Style;
|
pub use iced_style::menu::Style;
|
||||||
|
|
||||||
impl<B> overlay::menu::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Style = Style;
|
|
||||||
|
|
||||||
fn decorate(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
_cursor_position: Point,
|
|
||||||
style: &Style,
|
|
||||||
(primitives, mouse_cursor): Self::Output,
|
|
||||||
) -> Self::Output {
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style.background,
|
|
||||||
border_color: style.border_color,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_radius: 0.0,
|
|
||||||
},
|
|
||||||
primitives,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mouse_cursor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw<T: ToString>(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
options: &[T],
|
|
||||||
hovered_option: Option<usize>,
|
|
||||||
padding: Padding,
|
|
||||||
text_size: u16,
|
|
||||||
font: Font,
|
|
||||||
style: &Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
use std::f32;
|
|
||||||
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
let option_height = (text_size + padding.vertical()) as usize;
|
|
||||||
|
|
||||||
let mut primitives = Vec::new();
|
|
||||||
|
|
||||||
let offset = viewport.y - bounds.y;
|
|
||||||
let start = (offset / option_height as f32) as usize;
|
|
||||||
let end =
|
|
||||||
((offset + viewport.height) / option_height as f32).ceil() as usize;
|
|
||||||
|
|
||||||
let visible_options = &options[start..end.min(options.len())];
|
|
||||||
|
|
||||||
for (i, option) in visible_options.iter().enumerate() {
|
|
||||||
let i = start + i;
|
|
||||||
let is_selected = hovered_option == Some(i);
|
|
||||||
|
|
||||||
let bounds = Rectangle {
|
|
||||||
x: bounds.x,
|
|
||||||
y: bounds.y + (option_height * i) as f32,
|
|
||||||
width: bounds.width,
|
|
||||||
height: f32::from(text_size + padding.vertical()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_selected {
|
|
||||||
primitives.push(Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style.selected_background,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_radius: 0.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
primitives.push(Primitive::Text {
|
|
||||||
content: option.to_string(),
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + padding.left as f32,
|
|
||||||
y: bounds.center_y(),
|
|
||||||
width: f32::INFINITY,
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
size: f32::from(text_size),
|
|
||||||
font,
|
|
||||||
color: if is_selected {
|
|
||||||
style.selected_text_color
|
|
||||||
} else {
|
|
||||||
style.text_color
|
|
||||||
},
|
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group { primitives },
|
|
||||||
if is_mouse_over {
|
|
||||||
mouse::Interaction::Pointer
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use iced_native::{
|
use iced_native::image;
|
||||||
image, svg, Background, Color, Font, Rectangle, Size, Vector,
|
use iced_native::svg;
|
||||||
};
|
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
||||||
|
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::triangle;
|
use crate::triangle;
|
||||||
|
|
@ -66,8 +66,6 @@ pub enum Primitive {
|
||||||
Clip {
|
Clip {
|
||||||
/// The bounds of the clip
|
/// The bounds of the clip
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
/// The offset transformation of the clip
|
|
||||||
offset: Vector<u32>,
|
|
||||||
/// The content of the clip
|
/// The content of the clip
|
||||||
content: Box<Primitive>,
|
content: Box<Primitive>,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,43 @@
|
||||||
use crate::{Backend, Defaults, Primitive};
|
//! Create a renderer from a [`Backend`].
|
||||||
use iced_native::layout::{self, Layout};
|
use crate::backend::{self, Backend};
|
||||||
use iced_native::mouse;
|
use crate::{Primitive, Vector};
|
||||||
use iced_native::{
|
use iced_native::layout;
|
||||||
Background, Color, Element, Point, Rectangle, Vector, Widget,
|
use iced_native::renderer;
|
||||||
};
|
use iced_native::text::{self, Text};
|
||||||
|
use iced_native::{Background, Element, Font, Point, Rectangle, Size};
|
||||||
|
|
||||||
|
pub use iced_native::renderer::Style;
|
||||||
|
|
||||||
/// A backend-agnostic renderer that supports all the built-in widgets.
|
/// A backend-agnostic renderer that supports all the built-in widgets.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Renderer<B: Backend> {
|
pub struct Renderer<B: Backend> {
|
||||||
backend: B,
|
backend: B,
|
||||||
|
primitives: Vec<Primitive>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Backend> Renderer<B> {
|
impl<B: Backend> Renderer<B> {
|
||||||
/// Creates a new [`Renderer`] from the given [`Backend`].
|
/// Creates a new [`Renderer`] from the given [`Backend`].
|
||||||
pub fn new(backend: B) -> Self {
|
pub fn new(backend: B) -> Self {
|
||||||
Self { backend }
|
Self {
|
||||||
|
backend,
|
||||||
|
primitives: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the [`Backend`] of the [`Renderer`].
|
/// Returns the [`Backend`] of the [`Renderer`].
|
||||||
pub fn backend(&self) -> &B {
|
pub fn backend(&self) -> &B {
|
||||||
&self.backend
|
&self.backend
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the [`Backend`] of the [`Renderer`].
|
/// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
|
||||||
pub fn backend_mut(&mut self) -> &mut B {
|
pub fn draw_primitive(&mut self, primitive: Primitive) {
|
||||||
&mut self.backend
|
self.primitives.push(primitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the given closure with the [`Backend`] and the recorded primitives
|
||||||
|
/// of the [`Renderer`].
|
||||||
|
pub fn with_primitives(&mut self, f: impl FnOnce(&mut B, &[Primitive])) {
|
||||||
|
f(&mut self.backend, &self.primitives);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,9 +45,6 @@ impl<B> iced_native::Renderer for Renderer<B>
|
||||||
where
|
where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
type Output = (Primitive, mouse::Interaction);
|
|
||||||
type Defaults = Defaults;
|
|
||||||
|
|
||||||
fn layout<'a, Message>(
|
fn layout<'a, Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
element: &Element<'a, Message, Self>,
|
element: &Element<'a, Message, Self>,
|
||||||
|
|
@ -47,75 +57,114 @@ where
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay(
|
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||||
|
let current_primitives = std::mem::take(&mut self.primitives);
|
||||||
|
|
||||||
|
f(self);
|
||||||
|
|
||||||
|
let layer_primitives =
|
||||||
|
std::mem::replace(&mut self.primitives, current_primitives);
|
||||||
|
|
||||||
|
self.primitives.push(Primitive::Clip {
|
||||||
|
bounds,
|
||||||
|
content: Box::new(Primitive::Group {
|
||||||
|
primitives: layer_primitives,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_translation(
|
||||||
&mut self,
|
&mut self,
|
||||||
(base_primitive, base_cursor): (Primitive, mouse::Interaction),
|
translation: Vector,
|
||||||
(overlay_primitives, overlay_cursor): (Primitive, mouse::Interaction),
|
f: impl FnOnce(&mut Self),
|
||||||
overlay_bounds: Rectangle,
|
) {
|
||||||
) -> (Primitive, mouse::Interaction) {
|
let current_primitives = std::mem::take(&mut self.primitives);
|
||||||
(
|
|
||||||
Primitive::Group {
|
f(self);
|
||||||
primitives: vec![
|
|
||||||
base_primitive,
|
let layer_primitives =
|
||||||
Primitive::Clip {
|
std::mem::replace(&mut self.primitives, current_primitives);
|
||||||
bounds: Rectangle {
|
|
||||||
width: overlay_bounds.width + 0.5,
|
self.primitives.push(Primitive::Translate {
|
||||||
height: overlay_bounds.height + 0.5,
|
translation,
|
||||||
..overlay_bounds
|
content: Box::new(Primitive::Group {
|
||||||
},
|
primitives: layer_primitives,
|
||||||
offset: Vector::new(0, 0),
|
}),
|
||||||
content: Box::new(overlay_primitives),
|
});
|
||||||
},
|
}
|
||||||
],
|
|
||||||
},
|
fn fill_quad(
|
||||||
if base_cursor > overlay_cursor {
|
&mut self,
|
||||||
base_cursor
|
quad: renderer::Quad,
|
||||||
} else {
|
background: impl Into<Background>,
|
||||||
overlay_cursor
|
) {
|
||||||
},
|
self.primitives.push(Primitive::Quad {
|
||||||
|
bounds: quad.bounds,
|
||||||
|
background: background.into(),
|
||||||
|
border_radius: quad.border_radius,
|
||||||
|
border_width: quad.border_width,
|
||||||
|
border_color: quad.border_color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.primitives.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> text::Renderer for Renderer<B>
|
||||||
|
where
|
||||||
|
B: Backend + backend::Text,
|
||||||
|
{
|
||||||
|
type Font = Font;
|
||||||
|
|
||||||
|
const ICON_FONT: Font = B::ICON_FONT;
|
||||||
|
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
|
||||||
|
const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON;
|
||||||
|
|
||||||
|
fn default_size(&self) -> u16 {
|
||||||
|
self.backend().default_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn measure(
|
||||||
|
&self,
|
||||||
|
content: &str,
|
||||||
|
size: u16,
|
||||||
|
font: Font,
|
||||||
|
bounds: Size,
|
||||||
|
) -> (f32, f32) {
|
||||||
|
self.backend()
|
||||||
|
.measure(content, f32::from(size), font, bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hit_test(
|
||||||
|
&self,
|
||||||
|
content: &str,
|
||||||
|
size: f32,
|
||||||
|
font: Font,
|
||||||
|
bounds: Size,
|
||||||
|
point: Point,
|
||||||
|
nearest_only: bool,
|
||||||
|
) -> Option<text::Hit> {
|
||||||
|
self.backend().hit_test(
|
||||||
|
content,
|
||||||
|
size,
|
||||||
|
font,
|
||||||
|
bounds,
|
||||||
|
point,
|
||||||
|
nearest_only,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> layout::Debugger for Renderer<B>
|
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
|
||||||
where
|
self.primitives.push(Primitive::Text {
|
||||||
B: Backend,
|
content: text.content.to_string(),
|
||||||
{
|
bounds: text.bounds,
|
||||||
fn explain<Message>(
|
size: text.size,
|
||||||
&mut self,
|
color: text.color,
|
||||||
defaults: &Defaults,
|
font: text.font,
|
||||||
widget: &dyn Widget<Message, Self>,
|
horizontal_alignment: text.horizontal_alignment,
|
||||||
layout: Layout<'_>,
|
vertical_alignment: text.vertical_alignment,
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
color: Color,
|
|
||||||
) -> Self::Output {
|
|
||||||
let (primitive, cursor) =
|
|
||||||
widget.draw(self, defaults, layout, cursor_position, viewport);
|
|
||||||
|
|
||||||
let mut primitives = Vec::new();
|
|
||||||
|
|
||||||
explain_layout(layout, color, &mut primitives);
|
|
||||||
primitives.push(primitive);
|
|
||||||
|
|
||||||
(Primitive::Group { primitives }, cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn explain_layout(
|
|
||||||
layout: Layout<'_>,
|
|
||||||
color: Color,
|
|
||||||
primitives: &mut Vec<Primitive>,
|
|
||||||
) {
|
|
||||||
primitives.push(Primitive::Quad {
|
|
||||||
bounds: layout.bounds(),
|
|
||||||
background: Background::Color(Color::TRANSPARENT),
|
|
||||||
border_radius: 0.0,
|
|
||||||
border_width: 1.0,
|
|
||||||
border_color: color,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for child in layout.children() {
|
|
||||||
explain_layout(child, color, primitives);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,111 +1,12 @@
|
||||||
//! Allow your users to perform actions by pressing a button.
|
//! Allow your users to perform actions by pressing a button.
|
||||||
//!
|
//!
|
||||||
//! A [`Button`] has some local [`State`].
|
//! A [`Button`] has some local [`State`].
|
||||||
use crate::defaults::{self, Defaults};
|
use crate::Renderer;
|
||||||
use crate::{Backend, Primitive, Renderer};
|
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::{
|
|
||||||
Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use iced_native::button::State;
|
pub use iced_native::widget::button::{State, Style, StyleSheet};
|
||||||
pub use iced_style::button::{Style, StyleSheet};
|
|
||||||
|
|
||||||
/// A widget that produces a message when clicked.
|
/// A widget that produces a message when clicked.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
|
||||||
pub type Button<'a, Message, Backend> =
|
pub type Button<'a, Message, Backend> =
|
||||||
iced_native::Button<'a, Message, Renderer<Backend>>;
|
iced_native::widget::Button<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> iced_native::button::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
const DEFAULT_PADDING: Padding = Padding::new(5);
|
|
||||||
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
_defaults: &Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
is_disabled: bool,
|
|
||||||
is_pressed: bool,
|
|
||||||
style: &Box<dyn StyleSheet>,
|
|
||||||
content: &Element<'_, Message, Self>,
|
|
||||||
content_layout: Layout<'_>,
|
|
||||||
) -> Self::Output {
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
|
|
||||||
let styling = if is_disabled {
|
|
||||||
style.disabled()
|
|
||||||
} else if is_mouse_over {
|
|
||||||
if is_pressed {
|
|
||||||
style.pressed()
|
|
||||||
} else {
|
|
||||||
style.hovered()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
style.active()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (content, _) = content.draw(
|
|
||||||
self,
|
|
||||||
&Defaults {
|
|
||||||
text: defaults::Text {
|
|
||||||
color: styling.text_color,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
content_layout,
|
|
||||||
cursor_position,
|
|
||||||
&bounds,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
if styling.background.is_some() || styling.border_width > 0.0 {
|
|
||||||
let background = Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: styling
|
|
||||||
.background
|
|
||||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
|
||||||
border_radius: styling.border_radius,
|
|
||||||
border_width: styling.border_width,
|
|
||||||
border_color: styling.border_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
if styling.shadow_offset == Vector::default() {
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![background, content],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Implement proper shadow support
|
|
||||||
let shadow = Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + styling.shadow_offset.x,
|
|
||||||
y: bounds.y + styling.shadow_offset.y,
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
background: Background::Color(
|
|
||||||
[0.0, 0.0, 0.0, 0.5].into(),
|
|
||||||
),
|
|
||||||
border_radius: styling.border_radius,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
};
|
|
||||||
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![shadow, background, content],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
},
|
|
||||||
if is_mouse_over && !is_disabled {
|
|
||||||
mouse::Interaction::Pointer
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
|
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
|
||||||
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
|
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
|
||||||
//! and more!
|
//! and more!
|
||||||
use crate::{Backend, Defaults, Primitive, Renderer};
|
use crate::renderer::{self, Renderer};
|
||||||
|
use crate::{Backend, Primitive};
|
||||||
|
|
||||||
use iced_native::layout;
|
use iced_native::layout;
|
||||||
use iced_native::mouse;
|
use iced_native::mouse;
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
|
|
@ -186,32 +188,42 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
_renderer: &mut Renderer<B>,
|
|
||||||
_defaults: &Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> (Primitive, mouse::Interaction) {
|
) -> mouse::Interaction {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let cursor = Cursor::from_window_position(cursor_position);
|
||||||
|
|
||||||
|
self.program.mouse_interaction(bounds, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer<B>,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
use iced_native::Renderer as _;
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let translation = Vector::new(bounds.x, bounds.y);
|
let translation = Vector::new(bounds.x, bounds.y);
|
||||||
let cursor = Cursor::from_window_position(cursor_position);
|
let cursor = Cursor::from_window_position(cursor_position);
|
||||||
|
|
||||||
(
|
renderer.with_translation(translation, |renderer| {
|
||||||
Primitive::Translate {
|
renderer.draw_primitive(Primitive::Group {
|
||||||
translation,
|
|
||||||
content: Box::new(Primitive::Group {
|
|
||||||
primitives: self
|
primitives: self
|
||||||
.program
|
.program
|
||||||
.draw(bounds, cursor)
|
.draw(bounds, cursor)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Geometry::into_primitive)
|
.map(Geometry::into_primitive)
|
||||||
.collect(),
|
.collect(),
|
||||||
}),
|
});
|
||||||
},
|
});
|
||||||
self.program.mouse_interaction(bounds, cursor),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,10 @@
|
||||||
//! Show toggle controls using checkboxes.
|
//! Show toggle controls using checkboxes.
|
||||||
use crate::alignment;
|
use crate::Renderer;
|
||||||
use crate::backend::{self, Backend};
|
|
||||||
use crate::{Primitive, Rectangle, Renderer};
|
|
||||||
|
|
||||||
use iced_native::checkbox;
|
|
||||||
use iced_native::mouse;
|
|
||||||
|
|
||||||
pub use iced_style::checkbox::{Style, StyleSheet};
|
pub use iced_style::checkbox::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A box that can be checked.
|
/// A box that can be checked.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||||
pub type Checkbox<Message, Backend> =
|
pub type Checkbox<'a, Message, Backend> =
|
||||||
iced_native::Checkbox<Message, Renderer<Backend>>;
|
iced_native::widget::Checkbox<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> checkbox::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: u16 = 20;
|
|
||||||
const DEFAULT_SPACING: u16 = 15;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
is_checked: bool,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
(label, _): Self::Output,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = if is_mouse_over {
|
|
||||||
style_sheet.hovered(is_checked)
|
|
||||||
} else {
|
|
||||||
style_sheet.active(is_checked)
|
|
||||||
};
|
|
||||||
|
|
||||||
let checkbox = Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style.background,
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_color: style.border_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: if is_checked {
|
|
||||||
let check = Primitive::Text {
|
|
||||||
content: B::CHECKMARK_ICON.to_string(),
|
|
||||||
font: B::ICON_FONT,
|
|
||||||
size: bounds.height * 0.7,
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.center_x(),
|
|
||||||
y: bounds.center_y(),
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
color: style.checkmark_color,
|
|
||||||
horizontal_alignment: alignment::Horizontal::Center,
|
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
|
||||||
};
|
|
||||||
|
|
||||||
vec![checkbox, check, label]
|
|
||||||
} else {
|
|
||||||
vec![checkbox, label]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
if is_mouse_over {
|
|
||||||
mouse::Interaction::Pointer
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,5 @@
|
||||||
use crate::{Backend, Primitive, Renderer};
|
use crate::Renderer;
|
||||||
use iced_native::column;
|
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::{Element, Layout, Point, Rectangle};
|
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
pub type Column<'a, Message, Backend> =
|
pub type Column<'a, Message, Backend> =
|
||||||
iced_native::Column<'a, Message, Renderer<Backend>>;
|
iced_native::widget::Column<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> column::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
content: &[Element<'_, Message, Self>],
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output {
|
|
||||||
let mut mouse_interaction = mouse::Interaction::default();
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: content
|
|
||||||
.iter()
|
|
||||||
.zip(layout.children())
|
|
||||||
.map(|(child, layout)| {
|
|
||||||
let (primitive, new_mouse_interaction) = child.draw(
|
|
||||||
self,
|
|
||||||
defaults,
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
if new_mouse_interaction > mouse_interaction {
|
|
||||||
mouse_interaction = new_mouse_interaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
mouse_interaction,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
//! Decorate content and apply alignment.
|
//! Decorate content and apply alignment.
|
||||||
use crate::container;
|
use crate::Renderer;
|
||||||
use crate::defaults::{self, Defaults};
|
|
||||||
use crate::{Backend, Primitive, Renderer};
|
|
||||||
use iced_native::{Background, Color, Element, Layout, Point, Rectangle};
|
|
||||||
|
|
||||||
pub use iced_style::container::{Style, StyleSheet};
|
pub use iced_style::container::{Style, StyleSheet};
|
||||||
|
|
||||||
|
|
@ -11,68 +8,4 @@ pub use iced_style::container::{Style, StyleSheet};
|
||||||
/// This is an alias of an `iced_native` container with a default
|
/// This is an alias of an `iced_native` container with a default
|
||||||
/// `Renderer`.
|
/// `Renderer`.
|
||||||
pub type Container<'a, Message, Backend> =
|
pub type Container<'a, Message, Backend> =
|
||||||
iced_native::Container<'a, Message, Renderer<Backend>>;
|
iced_native::widget::Container<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> iced_native::container::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn container::StyleSheet>;
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
content: &Element<'_, Message, Self>,
|
|
||||||
content_layout: Layout<'_>,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = style_sheet.style();
|
|
||||||
|
|
||||||
let defaults = Defaults {
|
|
||||||
text: defaults::Text {
|
|
||||||
color: style.text_color.unwrap_or(defaults.text.color),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (content, mouse_interaction) = content.draw(
|
|
||||||
self,
|
|
||||||
&defaults,
|
|
||||||
content_layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(background) = background(bounds, &style) {
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![background, content],
|
|
||||||
},
|
|
||||||
mouse_interaction,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(content, mouse_interaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn background(
|
|
||||||
bounds: Rectangle,
|
|
||||||
style: &container::Style,
|
|
||||||
) -> Option<Primitive> {
|
|
||||||
if style.background.is_some() || style.border_width > 0.0 {
|
|
||||||
Some(Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style
|
|
||||||
.background
|
|
||||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_color: style.border_color,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@
|
||||||
pub mod viewer;
|
pub mod viewer;
|
||||||
|
|
||||||
use crate::backend::{self, Backend};
|
use crate::backend::{self, Backend};
|
||||||
|
use crate::{Primitive, Rectangle, Renderer};
|
||||||
|
|
||||||
use crate::{Primitive, Renderer};
|
|
||||||
use iced_native::image;
|
use iced_native::image;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::Layout;
|
|
||||||
|
|
||||||
pub use iced_native::image::{Handle, Image, Viewer};
|
pub use iced_native::widget::image::{Image, Viewer};
|
||||||
|
pub use image::Handle;
|
||||||
|
|
||||||
impl<B> image::Renderer for Renderer<B>
|
impl<B> image::Renderer for Renderer<B>
|
||||||
where
|
where
|
||||||
|
|
@ -18,17 +17,7 @@ where
|
||||||
self.backend().dimensions(handle)
|
self.backend().dimensions(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
|
||||||
&mut self,
|
self.draw_primitive(Primitive::Image { handle, bounds })
|
||||||
handle: image::Handle,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
) -> Self::Output {
|
|
||||||
(
|
|
||||||
Primitive::Image {
|
|
||||||
handle,
|
|
||||||
bounds: layout.bounds(),
|
|
||||||
},
|
|
||||||
mouse::Interaction::default(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,2 @@
|
||||||
//! Zoom and pan on an image.
|
//! Zoom and pan on an image.
|
||||||
use crate::backend::{self, Backend};
|
pub use iced_native::widget::image::Viewer;
|
||||||
use crate::{Primitive, Renderer};
|
|
||||||
|
|
||||||
use iced_native::image;
|
|
||||||
use iced_native::image::viewer;
|
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::{Rectangle, Size, Vector};
|
|
||||||
|
|
||||||
impl<B> viewer::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Image,
|
|
||||||
{
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
state: &viewer::State,
|
|
||||||
bounds: Rectangle,
|
|
||||||
image_size: Size,
|
|
||||||
translation: Vector,
|
|
||||||
handle: image::Handle,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
) -> Self::Output {
|
|
||||||
(
|
|
||||||
{
|
|
||||||
Primitive::Clip {
|
|
||||||
bounds,
|
|
||||||
content: Box::new(Primitive::Translate {
|
|
||||||
translation,
|
|
||||||
content: Box::new(Primitive::Image {
|
|
||||||
handle,
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x,
|
|
||||||
y: bounds.y,
|
|
||||||
..Rectangle::with_size(image_size)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
offset: Vector::new(0, 0),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
if state.is_cursor_grabbed() {
|
|
||||||
mouse::Interaction::Grabbing
|
|
||||||
} else if is_mouse_over
|
|
||||||
&& (image_size.width > bounds.width
|
|
||||||
|| image_size.height > bounds.height)
|
|
||||||
{
|
|
||||||
mouse::Interaction::Grab
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::Idle
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,9 @@
|
||||||
//! drag and drop, and hotkey support.
|
//! drag and drop, and hotkey support.
|
||||||
//!
|
//!
|
||||||
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
|
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
|
||||||
use crate::defaults;
|
use crate::Renderer;
|
||||||
use crate::{Backend, Color, Primitive, Renderer};
|
|
||||||
use iced_native::container;
|
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::pane_grid;
|
|
||||||
use iced_native::{Element, Layout, Point, Rectangle, Vector};
|
|
||||||
|
|
||||||
pub use iced_native::pane_grid::{
|
pub use iced_native::widget::pane_grid::{
|
||||||
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
|
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
|
||||||
ResizeEvent, Split, State, TitleBar,
|
ResizeEvent, Split, State, TitleBar,
|
||||||
};
|
};
|
||||||
|
|
@ -28,277 +23,4 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
|
||||||
pub type PaneGrid<'a, Message, Backend> =
|
pub type PaneGrid<'a, Message, Backend> =
|
||||||
iced_native::PaneGrid<'a, Message, Renderer<Backend>>;
|
iced_native::widget::PaneGrid<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> pane_grid::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
content: &[(Pane, Content<'_, Message, Self>)],
|
|
||||||
dragging: Option<(Pane, Point)>,
|
|
||||||
resizing: Option<(Axis, Rectangle, bool)>,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
style_sheet: &<Self as pane_grid::Renderer>::Style,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output {
|
|
||||||
let pane_cursor_position = if dragging.is_some() {
|
|
||||||
// TODO: Remove once cursor availability is encoded in the type
|
|
||||||
// system
|
|
||||||
Point::new(-1.0, -1.0)
|
|
||||||
} else {
|
|
||||||
cursor_position
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mouse_interaction = mouse::Interaction::default();
|
|
||||||
let mut dragged_pane = None;
|
|
||||||
|
|
||||||
let mut panes: Vec<_> = content
|
|
||||||
.iter()
|
|
||||||
.zip(layout.children())
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, ((id, pane), layout))| {
|
|
||||||
let (primitive, new_mouse_interaction) = pane.draw(
|
|
||||||
self,
|
|
||||||
defaults,
|
|
||||||
layout,
|
|
||||||
pane_cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
if new_mouse_interaction > mouse_interaction {
|
|
||||||
mouse_interaction = new_mouse_interaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((dragging, origin)) = dragging {
|
|
||||||
if *id == dragging {
|
|
||||||
dragged_pane = Some((i, layout, origin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut primitives = if let Some((index, layout, origin)) = dragged_pane
|
|
||||||
{
|
|
||||||
let pane = panes.remove(index);
|
|
||||||
let bounds = layout.bounds();
|
|
||||||
|
|
||||||
// TODO: Fix once proper layering is implemented.
|
|
||||||
// This is a pretty hacky way to achieve layering.
|
|
||||||
let clip = Primitive::Clip {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: cursor_position.x - origin.x,
|
|
||||||
y: cursor_position.y - origin.y,
|
|
||||||
width: bounds.width + 0.5,
|
|
||||||
height: bounds.height + 0.5,
|
|
||||||
},
|
|
||||||
offset: Vector::new(0, 0),
|
|
||||||
content: Box::new(Primitive::Translate {
|
|
||||||
translation: Vector::new(
|
|
||||||
cursor_position.x - bounds.x - origin.x,
|
|
||||||
cursor_position.y - bounds.y - origin.y,
|
|
||||||
),
|
|
||||||
content: Box::new(pane),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
panes.push(clip);
|
|
||||||
|
|
||||||
panes
|
|
||||||
} else {
|
|
||||||
panes
|
|
||||||
};
|
|
||||||
|
|
||||||
let (primitives, mouse_interaction) =
|
|
||||||
if let Some((axis, split_region, is_picked)) = resizing {
|
|
||||||
let highlight = if is_picked {
|
|
||||||
style_sheet.picked_split()
|
|
||||||
} else {
|
|
||||||
style_sheet.hovered_split()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(highlight) = highlight {
|
|
||||||
primitives.push(Primitive::Quad {
|
|
||||||
bounds: match axis {
|
|
||||||
Axis::Horizontal => Rectangle {
|
|
||||||
x: split_region.x,
|
|
||||||
y: (split_region.y
|
|
||||||
+ (split_region.height - highlight.width)
|
|
||||||
/ 2.0)
|
|
||||||
.round(),
|
|
||||||
width: split_region.width,
|
|
||||||
height: highlight.width,
|
|
||||||
},
|
|
||||||
Axis::Vertical => Rectangle {
|
|
||||||
x: (split_region.x
|
|
||||||
+ (split_region.width - highlight.width)
|
|
||||||
/ 2.0)
|
|
||||||
.round(),
|
|
||||||
y: split_region.y,
|
|
||||||
width: highlight.width,
|
|
||||||
height: split_region.height,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
background: highlight.color.into(),
|
|
||||||
border_radius: 0.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
primitives,
|
|
||||||
match axis {
|
|
||||||
Axis::Horizontal => {
|
|
||||||
mouse::Interaction::ResizingVertically
|
|
||||||
}
|
|
||||||
Axis::Vertical => {
|
|
||||||
mouse::Interaction::ResizingHorizontally
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(primitives, mouse_interaction)
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group { primitives },
|
|
||||||
if dragging.is_some() {
|
|
||||||
mouse::Interaction::Grabbing
|
|
||||||
} else {
|
|
||||||
mouse_interaction
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_pane<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
style_sheet: &<Self as container::Renderer>::Style,
|
|
||||||
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
|
|
||||||
body: (&Element<'_, Message, Self>, Layout<'_>),
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = style_sheet.style();
|
|
||||||
let (body, body_layout) = body;
|
|
||||||
|
|
||||||
let (body_primitive, body_interaction) =
|
|
||||||
body.draw(self, defaults, body_layout, cursor_position, viewport);
|
|
||||||
|
|
||||||
let background = crate::widget::container::background(bounds, &style);
|
|
||||||
|
|
||||||
if let Some((title_bar, title_bar_layout)) = title_bar {
|
|
||||||
let show_controls = bounds.contains(cursor_position);
|
|
||||||
let is_over_pick_area =
|
|
||||||
title_bar.is_over_pick_area(title_bar_layout, cursor_position);
|
|
||||||
|
|
||||||
let (title_bar_primitive, title_bar_interaction) = title_bar.draw(
|
|
||||||
self,
|
|
||||||
defaults,
|
|
||||||
title_bar_layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
show_controls,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![
|
|
||||||
background.unwrap_or(Primitive::None),
|
|
||||||
title_bar_primitive,
|
|
||||||
body_primitive,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
if title_bar_interaction > body_interaction {
|
|
||||||
title_bar_interaction
|
|
||||||
} else if is_over_pick_area {
|
|
||||||
mouse::Interaction::Grab
|
|
||||||
} else {
|
|
||||||
body_interaction
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
if let Some(background) = background {
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![background, body_primitive],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body_primitive
|
|
||||||
},
|
|
||||||
body_interaction,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_title_bar<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
style_sheet: &<Self as container::Renderer>::Style,
|
|
||||||
content: (&Element<'_, Message, Self>, Layout<'_>),
|
|
||||||
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = style_sheet.style();
|
|
||||||
let (title_content, title_layout) = content;
|
|
||||||
|
|
||||||
let defaults = Self::Defaults {
|
|
||||||
text: defaults::Text {
|
|
||||||
color: style.text_color.unwrap_or(defaults.text.color),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let background = crate::widget::container::background(bounds, &style);
|
|
||||||
|
|
||||||
let (title_primitive, title_interaction) = title_content.draw(
|
|
||||||
self,
|
|
||||||
&defaults,
|
|
||||||
title_layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some((controls, controls_layout)) = controls {
|
|
||||||
let (controls_primitive, controls_interaction) = controls.draw(
|
|
||||||
self,
|
|
||||||
&defaults,
|
|
||||||
controls_layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![
|
|
||||||
background.unwrap_or(Primitive::None),
|
|
||||||
title_primitive,
|
|
||||||
controls_primitive,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
controls_interaction.max(title_interaction),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
if let Some(background) = background {
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![background, title_primitive],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
title_primitive
|
|
||||||
},
|
|
||||||
title_interaction,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,9 @@
|
||||||
//! Display a dropdown list of selectable values.
|
//! Display a dropdown list of selectable values.
|
||||||
use crate::alignment;
|
use crate::Renderer;
|
||||||
use crate::backend::{self, Backend};
|
|
||||||
use crate::{Primitive, Renderer};
|
|
||||||
|
|
||||||
use iced_native::{mouse, Font, Padding, Point, Rectangle};
|
pub use iced_native::widget::pick_list::State;
|
||||||
use iced_style::menu;
|
|
||||||
|
|
||||||
pub use iced_native::pick_list::State;
|
|
||||||
pub use iced_style::pick_list::{Style, StyleSheet};
|
pub use iced_style::pick_list::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A widget allowing the selection of a single value from a list of options.
|
/// A widget allowing the selection of a single value from a list of options.
|
||||||
pub type PickList<'a, T, Message, Backend> =
|
pub type PickList<'a, T, Message, Backend> =
|
||||||
iced_native::PickList<'a, T, Message, Renderer<Backend>>;
|
iced_native::widget::PickList<'a, T, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> iced_native::pick_list::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
const DEFAULT_PADDING: Padding = Padding::new(5);
|
|
||||||
|
|
||||||
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
|
|
||||||
style.menu()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
selected: Option<String>,
|
|
||||||
placeholder: Option<&str>,
|
|
||||||
padding: Padding,
|
|
||||||
text_size: u16,
|
|
||||||
font: Font,
|
|
||||||
style: &Box<dyn StyleSheet>,
|
|
||||||
) -> Self::Output {
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
let is_selected = selected.is_some();
|
|
||||||
|
|
||||||
let style = if is_mouse_over {
|
|
||||||
style.hovered()
|
|
||||||
} else {
|
|
||||||
style.active()
|
|
||||||
};
|
|
||||||
|
|
||||||
let background = Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style.background,
|
|
||||||
border_color: style.border_color,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
};
|
|
||||||
|
|
||||||
let arrow_down = Primitive::Text {
|
|
||||||
content: B::ARROW_DOWN_ICON.to_string(),
|
|
||||||
font: B::ICON_FONT,
|
|
||||||
size: bounds.height * style.icon_size,
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + bounds.width - f32::from(padding.horizontal()),
|
|
||||||
y: bounds.center_y(),
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
color: style.text_color,
|
|
||||||
horizontal_alignment: alignment::Horizontal::Right,
|
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: if let Some(label) =
|
|
||||||
selected.or_else(|| placeholder.map(str::to_string))
|
|
||||||
{
|
|
||||||
let label = Primitive::Text {
|
|
||||||
content: label,
|
|
||||||
size: f32::from(text_size),
|
|
||||||
font,
|
|
||||||
color: is_selected
|
|
||||||
.then(|| style.text_color)
|
|
||||||
.unwrap_or(style.placeholder_color),
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + f32::from(padding.left),
|
|
||||||
y: bounds.center_y(),
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
|
||||||
};
|
|
||||||
|
|
||||||
vec![background, label, arrow_down]
|
|
||||||
} else {
|
|
||||||
vec![background, arrow_down]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
if is_mouse_over {
|
|
||||||
mouse::Interaction::Pointer
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,73 +2,4 @@
|
||||||
//!
|
//!
|
||||||
//! A [`ProgressBar`] has a range of possible values and a current value,
|
//! A [`ProgressBar`] has a range of possible values and a current value,
|
||||||
//! as well as a length, height and style.
|
//! as well as a length, height and style.
|
||||||
use crate::{Backend, Primitive, Renderer};
|
pub use iced_native::widget::progress_bar::*;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::progress_bar;
|
|
||||||
use iced_native::{Color, Rectangle};
|
|
||||||
|
|
||||||
pub use iced_style::progress_bar::{Style, StyleSheet};
|
|
||||||
|
|
||||||
/// A bar that displays progress.
|
|
||||||
///
|
|
||||||
/// This is an alias of an `iced_native` progress bar with an
|
|
||||||
/// `iced_wgpu::Renderer`.
|
|
||||||
pub type ProgressBar<Backend> = iced_native::ProgressBar<Renderer<Backend>>;
|
|
||||||
|
|
||||||
impl<B> progress_bar::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
const DEFAULT_HEIGHT: u16 = 30;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
range: std::ops::RangeInclusive<f32>,
|
|
||||||
value: f32,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = style_sheet.style();
|
|
||||||
let (range_start, range_end) = range.into_inner();
|
|
||||||
|
|
||||||
let active_progress_width = if range_start >= range_end {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
bounds.width * (value - range_start) / (range_end - range_start)
|
|
||||||
};
|
|
||||||
|
|
||||||
let background = Primitive::Group {
|
|
||||||
primitives: vec![Primitive::Quad {
|
|
||||||
bounds: Rectangle { ..bounds },
|
|
||||||
background: style.background,
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
if active_progress_width > 0.0 {
|
|
||||||
let bar = Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
width: active_progress_width,
|
|
||||||
..bounds
|
|
||||||
},
|
|
||||||
background: style.bar,
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
};
|
|
||||||
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![background, bar],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
background
|
|
||||||
},
|
|
||||||
mouse::Interaction::default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
//! Encode and display information in a QR code.
|
//! Encode and display information in a QR code.
|
||||||
use crate::canvas;
|
use crate::canvas;
|
||||||
use crate::{Backend, Defaults, Primitive, Renderer, Vector};
|
use crate::renderer::{self, Renderer};
|
||||||
|
use crate::Backend;
|
||||||
|
|
||||||
|
use iced_native::layout;
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle,
|
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
||||||
Size, Widget,
|
Widget,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -80,12 +82,14 @@ where
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_renderer: &mut Renderer<B>,
|
renderer: &mut Renderer<B>,
|
||||||
_defaults: &Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> (Primitive, mouse::Interaction) {
|
) {
|
||||||
|
use iced_native::Renderer as _;
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let side_length = self.state.width + 2 * QUIET_ZONE;
|
let side_length = self.state.width + 2 * QUIET_ZONE;
|
||||||
|
|
||||||
|
|
@ -122,13 +126,11 @@ where
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
(
|
let translation = Vector::new(bounds.x, bounds.y);
|
||||||
Primitive::Translate {
|
|
||||||
translation: Vector::new(bounds.x, bounds.y),
|
renderer.with_translation(translation, |renderer| {
|
||||||
content: Box::new(geometry.into_primitive()),
|
renderer.draw_primitive(geometry.into_primitive());
|
||||||
},
|
});
|
||||||
mouse::Interaction::default(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
//! Create choices using radio buttons.
|
//! Create choices using radio buttons.
|
||||||
use crate::{Backend, Primitive, Renderer};
|
use crate::Renderer;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::radio;
|
|
||||||
use iced_native::{Background, Color, Rectangle};
|
|
||||||
|
|
||||||
pub use iced_style::radio::{Style, StyleSheet};
|
pub use iced_style::radio::{Style, StyleSheet};
|
||||||
|
|
||||||
|
|
@ -10,69 +7,5 @@ pub use iced_style::radio::{Style, StyleSheet};
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` radio button with an
|
/// This is an alias of an `iced_native` radio button with an
|
||||||
/// `iced_wgpu::Renderer`.
|
/// `iced_wgpu::Renderer`.
|
||||||
pub type Radio<Message, Backend> =
|
pub type Radio<'a, Message, Backend> =
|
||||||
iced_native::Radio<Message, Renderer<Backend>>;
|
iced_native::widget::Radio<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> radio::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: u16 = 28;
|
|
||||||
const DEFAULT_SPACING: u16 = 15;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
is_selected: bool,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
(label, _): Self::Output,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = if is_mouse_over {
|
|
||||||
style_sheet.hovered()
|
|
||||||
} else {
|
|
||||||
style_sheet.active()
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = bounds.width;
|
|
||||||
let dot_size = size / 2.0;
|
|
||||||
|
|
||||||
let radio = Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style.background,
|
|
||||||
border_radius: size / 2.0,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_color: style.border_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: if is_selected {
|
|
||||||
let radio_circle = Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + dot_size / 2.0,
|
|
||||||
y: bounds.y + dot_size / 2.0,
|
|
||||||
width: bounds.width - dot_size,
|
|
||||||
height: bounds.height - dot_size,
|
|
||||||
},
|
|
||||||
background: Background::Color(style.dot_color),
|
|
||||||
border_radius: dot_size / 2.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
};
|
|
||||||
|
|
||||||
vec![radio, radio_circle, label]
|
|
||||||
} else {
|
|
||||||
vec![radio, label]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
if is_mouse_over {
|
|
||||||
mouse::Interaction::Pointer
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,5 @@
|
||||||
use crate::{Backend, Primitive, Renderer};
|
use crate::Renderer;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::row;
|
|
||||||
use iced_native::{Element, Layout, Point, Rectangle};
|
|
||||||
|
|
||||||
/// A container that distributes its contents horizontally.
|
/// A container that distributes its contents horizontally.
|
||||||
pub type Row<'a, Message, Backend> =
|
pub type Row<'a, Message, Backend> =
|
||||||
iced_native::Row<'a, Message, Renderer<Backend>>;
|
iced_native::widget::Row<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> row::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
content: &[Element<'_, Message, Self>],
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output {
|
|
||||||
let mut mouse_interaction = mouse::Interaction::default();
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: content
|
|
||||||
.iter()
|
|
||||||
.zip(layout.children())
|
|
||||||
.map(|(child, layout)| {
|
|
||||||
let (primitive, new_mouse_interaction) = child.draw(
|
|
||||||
self,
|
|
||||||
defaults,
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
if new_mouse_interaction > mouse_interaction {
|
|
||||||
mouse_interaction = new_mouse_interaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
mouse_interaction,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,3 @@
|
||||||
//! Display a horizontal or vertical rule for dividing content.
|
//! Display a horizontal or vertical rule for dividing content.
|
||||||
|
|
||||||
use crate::{Backend, Primitive, Renderer};
|
pub use iced_native::widget::rule::*;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::rule;
|
|
||||||
use iced_native::{Background, Color, Rectangle};
|
|
||||||
|
|
||||||
pub use iced_style::rule::{FillMode, Style, StyleSheet};
|
|
||||||
|
|
||||||
/// Display a horizontal or vertical rule for dividing content.
|
|
||||||
///
|
|
||||||
/// This is an alias of an `iced_native` rule with an `iced_graphics::Renderer`.
|
|
||||||
pub type Rule<Backend> = iced_native::Rule<Renderer<Backend>>;
|
|
||||||
|
|
||||||
impl<B> rule::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
is_horizontal: bool,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = style_sheet.style();
|
|
||||||
|
|
||||||
let line = if is_horizontal {
|
|
||||||
let line_y = (bounds.y + (bounds.height / 2.0)
|
|
||||||
- (style.width as f32 / 2.0))
|
|
||||||
.round();
|
|
||||||
|
|
||||||
let (offset, line_width) = style.fill_mode.fill(bounds.width);
|
|
||||||
let line_x = bounds.x + offset;
|
|
||||||
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: line_x,
|
|
||||||
y: line_y,
|
|
||||||
width: line_width,
|
|
||||||
height: style.width as f32,
|
|
||||||
},
|
|
||||||
background: Background::Color(style.color),
|
|
||||||
border_radius: style.radius,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let line_x = (bounds.x + (bounds.width / 2.0)
|
|
||||||
- (style.width as f32 / 2.0))
|
|
||||||
.round();
|
|
||||||
|
|
||||||
let (offset, line_height) = style.fill_mode.fill(bounds.height);
|
|
||||||
let line_y = bounds.y + offset;
|
|
||||||
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: line_x,
|
|
||||||
y: line_y,
|
|
||||||
width: style.width as f32,
|
|
||||||
height: line_height,
|
|
||||||
},
|
|
||||||
background: Background::Color(style.color),
|
|
||||||
border_radius: style.radius,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(line, mouse::Interaction::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
//! Navigate an endless amount of content with a scrollbar.
|
//! Navigate an endless amount of content with a scrollbar.
|
||||||
use crate::{Backend, Primitive, Renderer};
|
use crate::Renderer;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::scrollable;
|
|
||||||
use iced_native::{Background, Color, Rectangle, Vector};
|
|
||||||
|
|
||||||
pub use iced_native::scrollable::State;
|
pub use iced_native::widget::scrollable::State;
|
||||||
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
|
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
|
||||||
|
|
||||||
/// A widget that can vertically display an infinite amount of content
|
/// A widget that can vertically display an infinite amount of content
|
||||||
|
|
@ -13,146 +10,4 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
|
||||||
/// This is an alias of an `iced_native` scrollable with a default
|
/// This is an alias of an `iced_native` scrollable with a default
|
||||||
/// `Renderer`.
|
/// `Renderer`.
|
||||||
pub type Scrollable<'a, Message, Backend> =
|
pub type Scrollable<'a, Message, Backend> =
|
||||||
iced_native::Scrollable<'a, Message, Renderer<Backend>>;
|
iced_native::widget::Scrollable<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> scrollable::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn iced_style::scrollable::StyleSheet>;
|
|
||||||
|
|
||||||
fn scrollbar(
|
|
||||||
&self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
content_bounds: Rectangle,
|
|
||||||
offset: u32,
|
|
||||||
scrollbar_width: u16,
|
|
||||||
scrollbar_margin: u16,
|
|
||||||
scroller_width: u16,
|
|
||||||
) -> Option<scrollable::Scrollbar> {
|
|
||||||
if content_bounds.height > bounds.height {
|
|
||||||
let outer_width =
|
|
||||||
scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
|
|
||||||
|
|
||||||
let outer_bounds = Rectangle {
|
|
||||||
x: bounds.x + bounds.width - outer_width as f32,
|
|
||||||
y: bounds.y,
|
|
||||||
width: outer_width as f32,
|
|
||||||
height: bounds.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
let scrollbar_bounds = Rectangle {
|
|
||||||
x: bounds.x + bounds.width
|
|
||||||
- f32::from(outer_width / 2 + scrollbar_width / 2),
|
|
||||||
y: bounds.y,
|
|
||||||
width: scrollbar_width as f32,
|
|
||||||
height: bounds.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
let ratio = bounds.height / content_bounds.height;
|
|
||||||
let scroller_height = bounds.height * ratio;
|
|
||||||
let y_offset = offset as f32 * ratio;
|
|
||||||
|
|
||||||
let scroller_bounds = Rectangle {
|
|
||||||
x: bounds.x + bounds.width
|
|
||||||
- f32::from(outer_width / 2 + scroller_width / 2),
|
|
||||||
y: scrollbar_bounds.y + y_offset,
|
|
||||||
width: scroller_width as f32,
|
|
||||||
height: scroller_height,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(scrollable::Scrollbar {
|
|
||||||
outer_bounds,
|
|
||||||
bounds: scrollbar_bounds,
|
|
||||||
margin: scrollbar_margin,
|
|
||||||
scroller: scrollable::Scroller {
|
|
||||||
bounds: scroller_bounds,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
state: &scrollable::State,
|
|
||||||
bounds: Rectangle,
|
|
||||||
_content_bounds: Rectangle,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
is_mouse_over_scrollbar: bool,
|
|
||||||
scrollbar: Option<scrollable::Scrollbar>,
|
|
||||||
offset: u32,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
(content, mouse_interaction): Self::Output,
|
|
||||||
) -> Self::Output {
|
|
||||||
(
|
|
||||||
if let Some(scrollbar) = scrollbar {
|
|
||||||
let clip = Primitive::Clip {
|
|
||||||
bounds,
|
|
||||||
offset: Vector::new(0, offset),
|
|
||||||
content: Box::new(content),
|
|
||||||
};
|
|
||||||
|
|
||||||
let style = if state.is_scroller_grabbed() {
|
|
||||||
style_sheet.dragging()
|
|
||||||
} else if is_mouse_over_scrollbar {
|
|
||||||
style_sheet.hovered()
|
|
||||||
} else {
|
|
||||||
style_sheet.active()
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_scrollbar_visible =
|
|
||||||
style.background.is_some() || style.border_width > 0.0;
|
|
||||||
|
|
||||||
let scroller = if is_mouse_over
|
|
||||||
|| state.is_scroller_grabbed()
|
|
||||||
|| is_scrollbar_visible
|
|
||||||
{
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: scrollbar.scroller.bounds,
|
|
||||||
background: Background::Color(style.scroller.color),
|
|
||||||
border_radius: style.scroller.border_radius,
|
|
||||||
border_width: style.scroller.border_width,
|
|
||||||
border_color: style.scroller.border_color,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Primitive::None
|
|
||||||
};
|
|
||||||
|
|
||||||
let scrollbar = if is_scrollbar_visible {
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: scrollbar.bounds,
|
|
||||||
background: style
|
|
||||||
.background
|
|
||||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_color: style.border_color,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Primitive::None
|
|
||||||
};
|
|
||||||
|
|
||||||
let scroll = Primitive::Clip {
|
|
||||||
bounds,
|
|
||||||
offset: Vector::new(0, 0),
|
|
||||||
content: Box::new(Primitive::Group {
|
|
||||||
primitives: vec![scrollbar, scroller],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![clip, scroll],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
},
|
|
||||||
if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
|
|
||||||
mouse::Interaction::Idle
|
|
||||||
} else {
|
|
||||||
mouse_interaction
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,123 +1,5 @@
|
||||||
//! Display an interactive selector of a single value from a range of values.
|
//! Display an interactive selector of a single value from a range of values.
|
||||||
//!
|
//!
|
||||||
//! A [`Slider`] has some local [`State`].
|
//! A [`Slider`] has some local [`State`].
|
||||||
use crate::{Backend, Primitive, Renderer};
|
pub use iced_native::widget::slider::{Slider, State};
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::slider;
|
|
||||||
use iced_native::{Background, Color, Point, Rectangle};
|
|
||||||
|
|
||||||
pub use iced_native::slider::State;
|
|
||||||
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||||
|
|
||||||
/// An horizontal bar and a handle that selects a single value from a range of
|
|
||||||
/// values.
|
|
||||||
///
|
|
||||||
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
|
|
||||||
pub type Slider<'a, T, Message, Backend> =
|
|
||||||
iced_native::Slider<'a, T, Message, Renderer<Backend>>;
|
|
||||||
|
|
||||||
impl<B> slider::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
const DEFAULT_HEIGHT: u16 = 22;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
range: std::ops::RangeInclusive<f32>,
|
|
||||||
value: f32,
|
|
||||||
is_dragging: bool,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
|
|
||||||
let style = if is_dragging {
|
|
||||||
style_sheet.dragging()
|
|
||||||
} else if is_mouse_over {
|
|
||||||
style_sheet.hovered()
|
|
||||||
} else {
|
|
||||||
style_sheet.active()
|
|
||||||
};
|
|
||||||
|
|
||||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
|
||||||
|
|
||||||
let (rail_top, rail_bottom) = (
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x,
|
|
||||||
y: rail_y,
|
|
||||||
width: bounds.width,
|
|
||||||
height: 2.0,
|
|
||||||
},
|
|
||||||
background: Background::Color(style.rail_colors.0),
|
|
||||||
border_radius: 0.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
},
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x,
|
|
||||||
y: rail_y + 2.0,
|
|
||||||
width: bounds.width,
|
|
||||||
height: 2.0,
|
|
||||||
},
|
|
||||||
background: Background::Color(style.rail_colors.1),
|
|
||||||
border_radius: 0.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let (handle_width, handle_height, handle_border_radius) = match style
|
|
||||||
.handle
|
|
||||||
.shape
|
|
||||||
{
|
|
||||||
HandleShape::Circle { radius } => {
|
|
||||||
(radius * 2.0, radius * 2.0, radius)
|
|
||||||
}
|
|
||||||
HandleShape::Rectangle {
|
|
||||||
width,
|
|
||||||
border_radius,
|
|
||||||
} => (f32::from(width), f32::from(bounds.height), border_radius),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (range_start, range_end) = range.into_inner();
|
|
||||||
|
|
||||||
let handle_offset = if range_start >= range_end {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
(bounds.width - handle_width) * (value - range_start)
|
|
||||||
/ (range_end - range_start)
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle = Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: bounds.x + handle_offset.round(),
|
|
||||||
y: rail_y - handle_height / 2.0,
|
|
||||||
width: handle_width,
|
|
||||||
height: handle_height,
|
|
||||||
},
|
|
||||||
background: Background::Color(style.handle.color),
|
|
||||||
border_radius: handle_border_radius,
|
|
||||||
border_width: style.handle.border_width,
|
|
||||||
border_color: style.handle.border_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![rail_top, rail_bottom, handle],
|
|
||||||
},
|
|
||||||
if is_dragging {
|
|
||||||
mouse::Interaction::Grabbing
|
|
||||||
} else if is_mouse_over {
|
|
||||||
mouse::Interaction::Grab
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1 @@
|
||||||
use crate::{Backend, Primitive, Renderer};
|
pub use iced_native::widget::Space;
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::space;
|
|
||||||
use iced_native::Rectangle;
|
|
||||||
|
|
||||||
pub use iced_native::Space;
|
|
||||||
|
|
||||||
impl<B> space::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
fn draw(&mut self, _bounds: Rectangle) -> Self::Output {
|
|
||||||
(Primitive::None, mouse::Interaction::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
//! Display vector graphics in your application.
|
//! Display vector graphics in your application.
|
||||||
use crate::backend::{self, Backend};
|
use crate::backend::{self, Backend};
|
||||||
use crate::{Primitive, Renderer};
|
use crate::{Primitive, Rectangle, Renderer};
|
||||||
use iced_native::{mouse, svg, Layout};
|
use iced_native::svg;
|
||||||
|
|
||||||
pub use iced_native::svg::{Handle, Svg};
|
pub use iced_native::widget::svg::Svg;
|
||||||
|
pub use svg::Handle;
|
||||||
|
|
||||||
impl<B> svg::Renderer for Renderer<B>
|
impl<B> svg::Renderer for Renderer<B>
|
||||||
where
|
where
|
||||||
|
|
@ -13,17 +14,7 @@ where
|
||||||
self.backend().viewport_dimensions(handle)
|
self.backend().viewport_dimensions(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
|
||||||
&mut self,
|
self.draw_primitive(Primitive::Svg { handle, bounds })
|
||||||
handle: svg::Handle,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
) -> Self::Output {
|
|
||||||
(
|
|
||||||
Primitive::Svg {
|
|
||||||
handle,
|
|
||||||
bounds: layout.bounds(),
|
|
||||||
},
|
|
||||||
mouse::Interaction::default(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,7 @@
|
||||||
//! Write some text for your users to read.
|
//! Write some text for your users to read.
|
||||||
use crate::backend::{self, Backend};
|
use crate::Renderer;
|
||||||
use crate::{Primitive, Renderer};
|
|
||||||
use iced_native::alignment;
|
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::text;
|
|
||||||
use iced_native::{Color, Font, Point, Rectangle, Size};
|
|
||||||
|
|
||||||
/// A paragraph of text.
|
/// A paragraph of text.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`.
|
||||||
pub type Text<Backend> = iced_native::Text<Renderer<Backend>>;
|
pub type Text<Backend> = iced_native::widget::Text<Renderer<Backend>>;
|
||||||
|
|
||||||
use std::f32;
|
|
||||||
|
|
||||||
impl<B> text::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Font = Font;
|
|
||||||
|
|
||||||
fn default_size(&self) -> u16 {
|
|
||||||
self.backend().default_size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn measure(
|
|
||||||
&self,
|
|
||||||
content: &str,
|
|
||||||
size: u16,
|
|
||||||
font: Font,
|
|
||||||
bounds: Size,
|
|
||||||
) -> (f32, f32) {
|
|
||||||
self.backend()
|
|
||||||
.measure(content, f32::from(size), font, bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hit_test(
|
|
||||||
&self,
|
|
||||||
content: &str,
|
|
||||||
size: f32,
|
|
||||||
font: Font,
|
|
||||||
bounds: Size,
|
|
||||||
point: Point,
|
|
||||||
nearest_only: bool,
|
|
||||||
) -> Option<text::Hit> {
|
|
||||||
self.backend().hit_test(
|
|
||||||
content,
|
|
||||||
size,
|
|
||||||
font,
|
|
||||||
bounds,
|
|
||||||
point,
|
|
||||||
nearest_only,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
content: &str,
|
|
||||||
size: u16,
|
|
||||||
font: Font,
|
|
||||||
color: Option<Color>,
|
|
||||||
horizontal_alignment: alignment::Horizontal,
|
|
||||||
vertical_alignment: alignment::Vertical,
|
|
||||||
) -> Self::Output {
|
|
||||||
let x = match horizontal_alignment {
|
|
||||||
alignment::Horizontal::Left => bounds.x,
|
|
||||||
alignment::Horizontal::Center => bounds.center_x(),
|
|
||||||
alignment::Horizontal::Right => bounds.x + bounds.width,
|
|
||||||
};
|
|
||||||
|
|
||||||
let y = match vertical_alignment {
|
|
||||||
alignment::Vertical::Top => bounds.y,
|
|
||||||
alignment::Vertical::Center => bounds.center_y(),
|
|
||||||
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Text {
|
|
||||||
content: content.to_string(),
|
|
||||||
size: f32::from(size),
|
|
||||||
bounds: Rectangle { x, y, ..bounds },
|
|
||||||
color: color.unwrap_or(defaults.text.color),
|
|
||||||
font,
|
|
||||||
horizontal_alignment,
|
|
||||||
vertical_alignment,
|
|
||||||
},
|
|
||||||
mouse::Interaction::default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,266 +1,13 @@
|
||||||
//! Display fields that can be filled with text.
|
//! Display fields that can be filled with text.
|
||||||
//!
|
//!
|
||||||
//! A [`TextInput`] has some local [`State`].
|
//! A [`TextInput`] has some local [`State`].
|
||||||
use crate::alignment;
|
use crate::Renderer;
|
||||||
use crate::backend::{self, Backend};
|
|
||||||
use crate::{
|
|
||||||
Background, Color, Font, Point, Primitive, Rectangle, Renderer, Size,
|
|
||||||
Vector,
|
|
||||||
};
|
|
||||||
|
|
||||||
use iced_native::mouse;
|
pub use iced_native::widget::text_input::State;
|
||||||
use iced_native::text_input::{self, cursor};
|
|
||||||
use std::f32;
|
|
||||||
|
|
||||||
pub use iced_native::text_input::State;
|
|
||||||
pub use iced_style::text_input::{Style, StyleSheet};
|
pub use iced_style::text_input::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A field that can be filled with text.
|
/// A field that can be filled with text.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
|
||||||
pub type TextInput<'a, Message, Backend> =
|
pub type TextInput<'a, Message, Backend> =
|
||||||
iced_native::TextInput<'a, Message, Renderer<Backend>>;
|
iced_native::widget::TextInput<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> text_input::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
|
|
||||||
let backend = self.backend();
|
|
||||||
|
|
||||||
let (width, _) =
|
|
||||||
backend.measure(value, f32::from(size), font, Size::INFINITY);
|
|
||||||
|
|
||||||
width
|
|
||||||
}
|
|
||||||
|
|
||||||
fn offset(
|
|
||||||
&self,
|
|
||||||
text_bounds: Rectangle,
|
|
||||||
font: Font,
|
|
||||||
size: u16,
|
|
||||||
value: &text_input::Value,
|
|
||||||
state: &text_input::State,
|
|
||||||
) -> f32 {
|
|
||||||
if state.is_focused() {
|
|
||||||
let cursor = state.cursor();
|
|
||||||
|
|
||||||
let focus_position = match cursor.state(value) {
|
|
||||||
cursor::State::Index(i) => i,
|
|
||||||
cursor::State::Selection { end, .. } => end,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (_, offset) = measure_cursor_and_scroll_offset(
|
|
||||||
self,
|
|
||||||
text_bounds,
|
|
||||||
value,
|
|
||||||
size,
|
|
||||||
focus_position,
|
|
||||||
font,
|
|
||||||
);
|
|
||||||
|
|
||||||
offset
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
text_bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
font: Font,
|
|
||||||
size: u16,
|
|
||||||
placeholder: &str,
|
|
||||||
value: &text_input::Value,
|
|
||||||
state: &text_input::State,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
|
|
||||||
let style = if state.is_focused() {
|
|
||||||
style_sheet.focused()
|
|
||||||
} else if is_mouse_over {
|
|
||||||
style_sheet.hovered()
|
|
||||||
} else {
|
|
||||||
style_sheet.active()
|
|
||||||
};
|
|
||||||
|
|
||||||
let input = Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background: style.background,
|
|
||||||
border_radius: style.border_radius,
|
|
||||||
border_width: style.border_width,
|
|
||||||
border_color: style.border_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
let text = value.to_string();
|
|
||||||
|
|
||||||
let text_value = Primitive::Text {
|
|
||||||
content: if text.is_empty() {
|
|
||||||
placeholder.to_string()
|
|
||||||
} else {
|
|
||||||
text.clone()
|
|
||||||
},
|
|
||||||
color: if text.is_empty() {
|
|
||||||
style_sheet.placeholder_color()
|
|
||||||
} else {
|
|
||||||
style_sheet.value_color()
|
|
||||||
},
|
|
||||||
font,
|
|
||||||
bounds: Rectangle {
|
|
||||||
y: text_bounds.center_y(),
|
|
||||||
width: f32::INFINITY,
|
|
||||||
..text_bounds
|
|
||||||
},
|
|
||||||
size: f32::from(size),
|
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (contents_primitive, offset) = if state.is_focused() {
|
|
||||||
let cursor = state.cursor();
|
|
||||||
|
|
||||||
let (cursor_primitive, offset) = match cursor.state(value) {
|
|
||||||
cursor::State::Index(position) => {
|
|
||||||
let (text_value_width, offset) =
|
|
||||||
measure_cursor_and_scroll_offset(
|
|
||||||
self,
|
|
||||||
text_bounds,
|
|
||||||
value,
|
|
||||||
size,
|
|
||||||
position,
|
|
||||||
font,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: text_bounds.x + text_value_width,
|
|
||||||
y: text_bounds.y,
|
|
||||||
width: 1.0,
|
|
||||||
height: text_bounds.height,
|
|
||||||
},
|
|
||||||
background: Background::Color(
|
|
||||||
style_sheet.value_color(),
|
|
||||||
),
|
|
||||||
border_radius: 0.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
},
|
|
||||||
offset,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
cursor::State::Selection { start, end } => {
|
|
||||||
let left = start.min(end);
|
|
||||||
let right = end.max(start);
|
|
||||||
|
|
||||||
let (left_position, left_offset) =
|
|
||||||
measure_cursor_and_scroll_offset(
|
|
||||||
self,
|
|
||||||
text_bounds,
|
|
||||||
value,
|
|
||||||
size,
|
|
||||||
left,
|
|
||||||
font,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (right_position, right_offset) =
|
|
||||||
measure_cursor_and_scroll_offset(
|
|
||||||
self,
|
|
||||||
text_bounds,
|
|
||||||
value,
|
|
||||||
size,
|
|
||||||
right,
|
|
||||||
font,
|
|
||||||
);
|
|
||||||
|
|
||||||
let width = right_position - left_position;
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds: Rectangle {
|
|
||||||
x: text_bounds.x + left_position,
|
|
||||||
y: text_bounds.y,
|
|
||||||
width,
|
|
||||||
height: text_bounds.height,
|
|
||||||
},
|
|
||||||
background: Background::Color(
|
|
||||||
style_sheet.selection_color(),
|
|
||||||
),
|
|
||||||
border_radius: 0.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
},
|
|
||||||
if end == right {
|
|
||||||
right_offset
|
|
||||||
} else {
|
|
||||||
left_offset
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![cursor_primitive, text_value],
|
|
||||||
},
|
|
||||||
Vector::new(offset as u32, 0),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(text_value, Vector::new(0, 0))
|
|
||||||
};
|
|
||||||
|
|
||||||
let text_width = self.measure_value(
|
|
||||||
if text.is_empty() { placeholder } else { &text },
|
|
||||||
size,
|
|
||||||
font,
|
|
||||||
);
|
|
||||||
|
|
||||||
let contents = if text_width > text_bounds.width {
|
|
||||||
Primitive::Clip {
|
|
||||||
bounds: text_bounds,
|
|
||||||
offset,
|
|
||||||
content: Box::new(contents_primitive),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
contents_primitive
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![input, contents],
|
|
||||||
},
|
|
||||||
if is_mouse_over {
|
|
||||||
mouse::Interaction::Text
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn measure_cursor_and_scroll_offset<B>(
|
|
||||||
renderer: &Renderer<B>,
|
|
||||||
text_bounds: Rectangle,
|
|
||||||
value: &text_input::Value,
|
|
||||||
size: u16,
|
|
||||||
cursor_index: usize,
|
|
||||||
font: Font,
|
|
||||||
) -> (f32, f32)
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
use iced_native::text_input::Renderer;
|
|
||||||
|
|
||||||
let text_before_cursor = value.until(cursor_index).to_string();
|
|
||||||
|
|
||||||
let text_value_width =
|
|
||||||
renderer.measure_value(&text_before_cursor, size, font);
|
|
||||||
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
|
|
||||||
|
|
||||||
(text_value_width, offset)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,10 @@
|
||||||
//! Show toggle controls using togglers.
|
//! Show toggle controls using togglers.
|
||||||
use crate::backend::{self, Backend};
|
use crate::Renderer;
|
||||||
use crate::{Primitive, Renderer};
|
|
||||||
use iced_native::mouse;
|
|
||||||
use iced_native::toggler;
|
|
||||||
use iced_native::Rectangle;
|
|
||||||
|
|
||||||
pub use iced_style::toggler::{Style, StyleSheet};
|
pub use iced_style::toggler::{Style, StyleSheet};
|
||||||
|
|
||||||
/// Makes sure that the border radius of the toggler looks good at every size.
|
|
||||||
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
|
|
||||||
|
|
||||||
/// The space ratio between the background Quad and the Toggler bounds, and
|
|
||||||
/// between the background Quad and foreground Quad.
|
|
||||||
const SPACE_RATIO: f32 = 0.05;
|
|
||||||
|
|
||||||
/// A toggler that can be toggled.
|
/// A toggler that can be toggled.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
|
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
|
||||||
pub type Toggler<Message, Backend> =
|
pub type Toggler<'a, Message, Backend> =
|
||||||
iced_native::Toggler<Message, Renderer<Backend>>;
|
iced_native::widget::Toggler<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
impl<B> toggler::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: u16 = 20;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
is_active: bool,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
label: Option<Self::Output>,
|
|
||||||
style_sheet: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
let style = if is_mouse_over {
|
|
||||||
style_sheet.hovered(is_active)
|
|
||||||
} else {
|
|
||||||
style_sheet.active(is_active)
|
|
||||||
};
|
|
||||||
|
|
||||||
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
|
|
||||||
let space = SPACE_RATIO * bounds.height as f32;
|
|
||||||
|
|
||||||
let toggler_background_bounds = Rectangle {
|
|
||||||
x: bounds.x + space,
|
|
||||||
y: bounds.y + space,
|
|
||||||
width: bounds.width - (2.0 * space),
|
|
||||||
height: bounds.height - (2.0 * space),
|
|
||||||
};
|
|
||||||
|
|
||||||
let toggler_background = Primitive::Quad {
|
|
||||||
bounds: toggler_background_bounds,
|
|
||||||
background: style.background.into(),
|
|
||||||
border_radius,
|
|
||||||
border_width: 1.0,
|
|
||||||
border_color: style.background_border.unwrap_or(style.background),
|
|
||||||
};
|
|
||||||
|
|
||||||
let toggler_foreground_bounds = Rectangle {
|
|
||||||
x: bounds.x
|
|
||||||
+ if is_active {
|
|
||||||
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
|
|
||||||
} else {
|
|
||||||
2.0 * space
|
|
||||||
},
|
|
||||||
y: bounds.y + (2.0 * space),
|
|
||||||
width: bounds.height - (4.0 * space),
|
|
||||||
height: bounds.height - (4.0 * space),
|
|
||||||
};
|
|
||||||
|
|
||||||
let toggler_foreground = Primitive::Quad {
|
|
||||||
bounds: toggler_foreground_bounds,
|
|
||||||
background: style.foreground.into(),
|
|
||||||
border_radius,
|
|
||||||
border_width: 1.0,
|
|
||||||
border_color: style.foreground_border.unwrap_or(style.foreground),
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: match label {
|
|
||||||
Some((l, _)) => {
|
|
||||||
vec![l, toggler_background, toggler_foreground]
|
|
||||||
}
|
|
||||||
None => vec![toggler_background, toggler_foreground],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
if is_mouse_over {
|
|
||||||
mouse::Interaction::Pointer
|
|
||||||
} else {
|
|
||||||
mouse::Interaction::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,168 +1,11 @@
|
||||||
//! Decorate content and apply alignment.
|
//! Decorate content and apply alignment.
|
||||||
use crate::backend::{self, Backend};
|
use crate::Renderer;
|
||||||
use crate::defaults::{self, Defaults};
|
|
||||||
use crate::{Primitive, Renderer, Vector};
|
|
||||||
|
|
||||||
use iced_native::container;
|
|
||||||
use iced_native::layout::{self, Layout};
|
|
||||||
use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
|
|
||||||
|
|
||||||
/// An element decorating some content.
|
/// An element decorating some content.
|
||||||
///
|
///
|
||||||
/// This is an alias of an `iced_native` tooltip with a default
|
/// This is an alias of an `iced_native` tooltip with a default
|
||||||
/// `Renderer`.
|
/// `Renderer`.
|
||||||
pub type Tooltip<'a, Message, Backend> =
|
pub type Tooltip<'a, Message, Backend> =
|
||||||
iced_native::Tooltip<'a, Message, Renderer<Backend>>;
|
iced_native::widget::Tooltip<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
pub use iced_native::tooltip::Position;
|
pub use iced_native::widget::tooltip::Position;
|
||||||
|
|
||||||
impl<B> iced_native::tooltip::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
const DEFAULT_PADDING: u16 = 5;
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Defaults,
|
|
||||||
cursor_position: Point,
|
|
||||||
content_layout: Layout<'_>,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
content: &Element<'_, Message, Self>,
|
|
||||||
tooltip: &Text<Self>,
|
|
||||||
position: Position,
|
|
||||||
style_sheet: &<Self as container::Renderer>::Style,
|
|
||||||
gap: u16,
|
|
||||||
padding: u16,
|
|
||||||
) -> Self::Output {
|
|
||||||
let (content, mouse_interaction) = content.draw(
|
|
||||||
self,
|
|
||||||
&defaults,
|
|
||||||
content_layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
let bounds = content_layout.bounds();
|
|
||||||
|
|
||||||
if bounds.contains(cursor_position) {
|
|
||||||
use iced_native::Widget;
|
|
||||||
|
|
||||||
let gap = f32::from(gap);
|
|
||||||
let style = style_sheet.style();
|
|
||||||
|
|
||||||
let defaults = Defaults {
|
|
||||||
text: defaults::Text {
|
|
||||||
color: style.text_color.unwrap_or(defaults.text.color),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let text_layout = Widget::<(), Self>::layout(
|
|
||||||
tooltip,
|
|
||||||
self,
|
|
||||||
&layout::Limits::new(Size::ZERO, viewport.size())
|
|
||||||
.pad(Padding::new(padding)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let padding = f32::from(padding);
|
|
||||||
let text_bounds = text_layout.bounds();
|
|
||||||
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
|
|
||||||
let y_center =
|
|
||||||
bounds.y + (bounds.height - text_bounds.height) / 2.0;
|
|
||||||
|
|
||||||
let mut tooltip_bounds = {
|
|
||||||
let offset = match position {
|
|
||||||
Position::Top => Vector::new(
|
|
||||||
x_center,
|
|
||||||
bounds.y - text_bounds.height - gap - padding,
|
|
||||||
),
|
|
||||||
Position::Bottom => Vector::new(
|
|
||||||
x_center,
|
|
||||||
bounds.y + bounds.height + gap + padding,
|
|
||||||
),
|
|
||||||
Position::Left => Vector::new(
|
|
||||||
bounds.x - text_bounds.width - gap - padding,
|
|
||||||
y_center,
|
|
||||||
),
|
|
||||||
Position::Right => Vector::new(
|
|
||||||
bounds.x + bounds.width + gap + padding,
|
|
||||||
y_center,
|
|
||||||
),
|
|
||||||
Position::FollowCursor => Vector::new(
|
|
||||||
cursor_position.x,
|
|
||||||
cursor_position.y - text_bounds.height,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
x: offset.x - padding,
|
|
||||||
y: offset.y - padding,
|
|
||||||
width: text_bounds.width + padding * 2.0,
|
|
||||||
height: text_bounds.height + padding * 2.0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if tooltip_bounds.x < viewport.x {
|
|
||||||
tooltip_bounds.x = viewport.x;
|
|
||||||
} else if viewport.x + viewport.width
|
|
||||||
< tooltip_bounds.x + tooltip_bounds.width
|
|
||||||
{
|
|
||||||
tooltip_bounds.x =
|
|
||||||
viewport.x + viewport.width - tooltip_bounds.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if tooltip_bounds.y < viewport.y {
|
|
||||||
tooltip_bounds.y = viewport.y;
|
|
||||||
} else if viewport.y + viewport.height
|
|
||||||
< tooltip_bounds.y + tooltip_bounds.height
|
|
||||||
{
|
|
||||||
tooltip_bounds.y =
|
|
||||||
viewport.y + viewport.height - tooltip_bounds.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tooltip, _) = Widget::<(), Self>::draw(
|
|
||||||
tooltip,
|
|
||||||
self,
|
|
||||||
&defaults,
|
|
||||||
Layout::with_offset(
|
|
||||||
Vector::new(
|
|
||||||
tooltip_bounds.x + padding,
|
|
||||||
tooltip_bounds.y + padding,
|
|
||||||
),
|
|
||||||
&text_layout,
|
|
||||||
),
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![
|
|
||||||
content,
|
|
||||||
Primitive::Clip {
|
|
||||||
bounds: *viewport,
|
|
||||||
offset: Vector::new(0, 0),
|
|
||||||
content: Box::new(
|
|
||||||
if let Some(background) =
|
|
||||||
crate::container::background(
|
|
||||||
tooltip_bounds,
|
|
||||||
&style,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: vec![background, tooltip],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tooltip
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mouse_interaction,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(content, mouse_interaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
use crate::{Color, Error, Viewport};
|
use crate::{Color, Error, Viewport};
|
||||||
|
|
||||||
use iced_native::mouse;
|
|
||||||
|
|
||||||
use raw_window_handle::HasRawWindowHandle;
|
use raw_window_handle::HasRawWindowHandle;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -30,9 +28,8 @@ pub trait Compositor: Sized {
|
||||||
window: &W,
|
window: &W,
|
||||||
) -> Self::Surface;
|
) -> Self::Surface;
|
||||||
|
|
||||||
/// Crates a new [`SwapChain`] for the given [`Surface`].
|
/// Configures a new [`Surface`] with the given dimensions.
|
||||||
///
|
///
|
||||||
/// [`SwapChain`]: Self::SwapChain
|
|
||||||
/// [`Surface`]: Self::Surface
|
/// [`Surface`]: Self::Surface
|
||||||
fn configure_surface(
|
fn configure_surface(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -41,18 +38,17 @@ pub trait Compositor: Sized {
|
||||||
height: u32,
|
height: u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Draws the output primitives to the next frame of the given [`SwapChain`].
|
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
||||||
///
|
///
|
||||||
/// [`SwapChain`]: Self::SwapChain
|
/// [`SwapChain`]: Self::SwapChain
|
||||||
fn draw<T: AsRef<str>>(
|
fn present<T: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
surface: &mut Self::Surface,
|
surface: &mut Self::Surface,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
output: &<Self::Renderer as iced_native::Renderer>::Output,
|
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Result<mouse::Interaction, SurfaceError>;
|
) -> Result<(), SurfaceError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of an unsuccessful call to [`Compositor::draw`].
|
/// Result of an unsuccessful call to [`Compositor::draw`].
|
||||||
|
|
@ -63,13 +59,13 @@ pub enum SurfaceError {
|
||||||
"A timeout was encountered while trying to acquire the next frame"
|
"A timeout was encountered while trying to acquire the next frame"
|
||||||
)]
|
)]
|
||||||
Timeout,
|
Timeout,
|
||||||
/// The underlying surface has changed, and therefore the swap chain must be updated.
|
/// The underlying surface has changed, and therefore the surface must be updated.
|
||||||
#[error(
|
#[error(
|
||||||
"The underlying surface has changed, and therefore the swap chain must be updated."
|
"The underlying surface has changed, and therefore the surface must be updated."
|
||||||
)]
|
)]
|
||||||
Outdated,
|
Outdated,
|
||||||
/// The swap chain has been lost and needs to be recreated.
|
/// The swap chain has been lost and needs to be recreated.
|
||||||
#[error("The swap chain has been lost and needs to be recreated")]
|
#[error("The surface has been lost and needs to be recreated")]
|
||||||
Lost,
|
Lost,
|
||||||
/// There is no more memory left to allocate a new frame.
|
/// There is no more memory left to allocate a new frame.
|
||||||
#[error("There is no more memory left to allocate a new frame")]
|
#[error("There is no more memory left to allocate a new frame")]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::{Color, Error, Size, Viewport};
|
use crate::{Color, Error, Size, Viewport};
|
||||||
use iced_native::mouse;
|
|
||||||
|
|
||||||
use core::ffi::c_void;
|
use core::ffi::c_void;
|
||||||
|
|
||||||
|
|
@ -49,15 +48,15 @@ pub trait GLCompositor: Sized {
|
||||||
/// Resizes the viewport of the [`GLCompositor`].
|
/// Resizes the viewport of the [`GLCompositor`].
|
||||||
fn resize_viewport(&mut self, physical_size: Size<u32>);
|
fn resize_viewport(&mut self, physical_size: Size<u32>);
|
||||||
|
|
||||||
/// Draws the provided output with the given [`Renderer`].
|
/// Presents the primitives of the [`Renderer`] to the next frame of the
|
||||||
|
/// [`GLCompositor`].
|
||||||
///
|
///
|
||||||
/// [`Renderer`]: crate::Renderer
|
/// [`Renderer`]: crate::Renderer
|
||||||
fn draw<T: AsRef<str>>(
|
fn present<T: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Self::Renderer,
|
renderer: &mut Self::Renderer,
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
output: &<Self::Renderer as iced_native::Renderer>::Output,
|
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> mouse::Interaction;
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,7 @@ path = "../core"
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
path = "../futures"
|
path = "../futures"
|
||||||
features = ["thread-pool"]
|
features = ["thread-pool"]
|
||||||
|
|
||||||
|
[dependencies.iced_style]
|
||||||
|
version = "0.3"
|
||||||
|
path = "../style"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
|
Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
|
||||||
};
|
};
|
||||||
|
|
@ -77,7 +79,7 @@ where
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # mod counter {
|
/// # mod counter {
|
||||||
/// # type Text = iced_native::Text<iced_native::renderer::Null>;
|
/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// # #[derive(Debug, Clone, Copy)]
|
/// # #[derive(Debug, Clone, Copy)]
|
||||||
/// # pub enum Message {}
|
/// # pub enum Message {}
|
||||||
|
|
@ -104,7 +106,8 @@ where
|
||||||
/// # pub enum Message {
|
/// # pub enum Message {
|
||||||
/// # Counter(usize, counter::Message)
|
/// # Counter(usize, counter::Message)
|
||||||
/// # }
|
/// # }
|
||||||
/// use iced_native::{Element, Row};
|
/// use iced_native::Element;
|
||||||
|
/// use iced_native::widget::Row;
|
||||||
/// use iced_wgpu::Renderer;
|
/// use iced_wgpu::Renderer;
|
||||||
///
|
///
|
||||||
/// impl ManyCounters {
|
/// impl ManyCounters {
|
||||||
|
|
@ -189,7 +192,7 @@ where
|
||||||
) -> Element<'a, Message, Renderer>
|
) -> Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
Renderer: 'a + layout::Debugger,
|
Renderer: 'a,
|
||||||
{
|
{
|
||||||
Element {
|
Element {
|
||||||
widget: Box::new(Explain::new(self, color.into())),
|
widget: Box::new(Explain::new(self, color.into())),
|
||||||
|
|
@ -241,13 +244,24 @@ where
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
self.widget
|
self.widget
|
||||||
.draw(renderer, defaults, layout, cursor_position, viewport)
|
.draw(renderer, style, layout, cursor_position, viewport)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`mouse::Interaction`] of the [`Element`].
|
||||||
|
pub fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.widget
|
||||||
|
.mouse_interaction(layout, cursor_position, viewport)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the _layout_ hash of the [`Element`].
|
/// Computes the _layout_ hash of the [`Element`].
|
||||||
|
|
@ -336,13 +350,23 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
self.widget
|
self.widget
|
||||||
.draw(renderer, defaults, layout, cursor_position, viewport)
|
.draw(renderer, style, layout, cursor_position, viewport)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.widget
|
||||||
|
.mouse_interaction(layout, cursor_position, viewport)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -378,7 +402,7 @@ where
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Explain<'a, Message, Renderer>
|
for Explain<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer + layout::Debugger,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.element.widget.width()
|
self.element.widget.width()
|
||||||
|
|
@ -418,19 +442,51 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.explain(
|
fn explain_layout<Renderer: crate::Renderer>(
|
||||||
defaults,
|
renderer: &mut Renderer,
|
||||||
self.element.widget.as_ref(),
|
color: Color,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
) {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: layout.bounds(),
|
||||||
|
border_color: color,
|
||||||
|
border_width: 1.0,
|
||||||
|
border_radius: 0.0,
|
||||||
|
},
|
||||||
|
Color::TRANSPARENT,
|
||||||
|
);
|
||||||
|
|
||||||
|
for child in layout.children() {
|
||||||
|
explain_layout(renderer, color, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.element.widget.draw(
|
||||||
|
renderer,
|
||||||
|
style,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport,
|
viewport,
|
||||||
self.color,
|
);
|
||||||
)
|
|
||||||
|
explain_layout(renderer, self.color, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.element
|
||||||
|
.widget
|
||||||
|
.mouse_interaction(layout, cursor_position, viewport)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
|
||||||
124
native/src/image.rs
Normal file
124
native/src/image.rs
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
//! Position your widgets properly.
|
//! Position your widgets properly.
|
||||||
mod debugger;
|
|
||||||
mod limits;
|
mod limits;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
||||||
pub mod flex;
|
pub mod flex;
|
||||||
|
|
||||||
pub use debugger::Debugger;
|
|
||||||
pub use limits::Limits;
|
pub use limits::Limits;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod image;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
|
|
@ -43,6 +44,8 @@ pub mod overlay;
|
||||||
pub mod program;
|
pub mod program;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
|
pub mod svg;
|
||||||
|
pub mod text;
|
||||||
pub mod touch;
|
pub mod touch;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
@ -84,4 +87,4 @@ pub use renderer::Renderer;
|
||||||
pub use runtime::Runtime;
|
pub use runtime::Runtime;
|
||||||
pub use subscription::Subscription;
|
pub use subscription::Subscription;
|
||||||
pub use user_interface::{Cache, UserInterface};
|
pub use user_interface::{Cache, UserInterface};
|
||||||
pub use widget::*;
|
pub use widget::Widget;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ pub use menu::Menu;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::{Clipboard, Hasher, Layout, Point, Size};
|
use crate::mouse;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size};
|
||||||
|
|
||||||
/// An interactive component that can be displayed on top of other widgets.
|
/// An interactive component that can be displayed on top of other widgets.
|
||||||
pub trait Overlay<Message, Renderer>
|
pub trait Overlay<Message, Renderer>
|
||||||
|
|
@ -32,10 +34,10 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Renderer::Output;
|
);
|
||||||
|
|
||||||
/// Computes the _layout_ hash of the [`Overlay`].
|
/// Computes the _layout_ hash of the [`Overlay`].
|
||||||
///
|
///
|
||||||
|
|
@ -73,4 +75,16 @@ where
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
|
||||||
|
///
|
||||||
|
/// By default, it returns [`mouse::Interaction::Idle`].
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
_layout: Layout<'_>,
|
||||||
|
_cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
mouse::Interaction::Idle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ pub use crate::Overlay;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
|
use crate::mouse;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size, Vector};
|
||||||
|
|
||||||
/// A generic [`Overlay`].
|
/// A generic [`Overlay`].
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
|
|
@ -67,16 +69,26 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`mouse::Interaction`] of the [`Element`].
|
||||||
|
pub fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.overlay
|
||||||
|
.mouse_interaction(layout, cursor_position, viewport)
|
||||||
|
}
|
||||||
|
|
||||||
/// Draws the [`Element`] and its children using the given [`Layout`].
|
/// Draws the [`Element`] and its children using the given [`Layout`].
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
self.overlay
|
self.overlay.draw(renderer, style, layout, cursor_position)
|
||||||
.draw(renderer, defaults, layout, cursor_position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the _layout_ hash of the [`Element`].
|
/// Computes the _layout_ hash of the [`Element`].
|
||||||
|
|
@ -139,15 +151,24 @@ where
|
||||||
event_status
|
event_status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.content
|
||||||
|
.mouse_interaction(layout, cursor_position, viewport)
|
||||||
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
self.content
|
self.content.draw(renderer, style, layout, cursor_position)
|
||||||
.draw(renderer, defaults, layout, cursor_position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher, position: Point) {
|
fn hash_layout(&self, state: &mut Hasher, position: Point) {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
//! Build and show dropdown menus.
|
//! Build and show dropdown menus.
|
||||||
use crate::container;
|
use crate::alignment;
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::scrollable;
|
use crate::renderer;
|
||||||
use crate::text;
|
use crate::text::{self, Text};
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
|
use crate::widget::scrollable::{self, Scrollable};
|
||||||
|
use crate::widget::Container;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
|
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
|
||||||
Rectangle, Scrollable, Size, Vector, Widget,
|
Rectangle, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use iced_style::menu::Style;
|
||||||
|
|
||||||
/// A list of selectable options.
|
/// A list of selectable options.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Menu<'a, T, Renderer: self::Renderer> {
|
pub struct Menu<'a, T, Renderer: text::Renderer> {
|
||||||
state: &'a mut State,
|
state: &'a mut State,
|
||||||
options: &'a [T],
|
options: &'a [T],
|
||||||
hovered_option: &'a mut Option<usize>,
|
hovered_option: &'a mut Option<usize>,
|
||||||
|
|
@ -23,13 +27,13 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Renderer> Menu<'a, T, Renderer>
|
impl<'a, T, Renderer> Menu<'a, T, Renderer>
|
||||||
where
|
where
|
||||||
T: ToString + Clone,
|
T: ToString + Clone,
|
||||||
Renderer: self::Renderer + 'a,
|
Renderer: text::Renderer + 'a,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
|
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
|
||||||
/// the message to produced when an option is selected.
|
/// the message to produced when an option is selected.
|
||||||
|
|
@ -77,10 +81,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Menu`].
|
/// Sets the style of the [`Menu`].
|
||||||
pub fn style(
|
pub fn style(mut self, style: impl Into<Style>) -> Self {
|
||||||
mut self,
|
|
||||||
style: impl Into<<Renderer as self::Renderer>::Style>,
|
|
||||||
) -> Self {
|
|
||||||
self.style = style.into();
|
self.style = style.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -116,14 +117,14 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Overlay<'a, Message, Renderer: self::Renderer> {
|
struct Overlay<'a, Message, Renderer: text::Renderer> {
|
||||||
container: Container<'a, Message, Renderer>,
|
container: Container<'a, Message, Renderer>,
|
||||||
width: u16,
|
width: u16,
|
||||||
target_height: f32,
|
target_height: f32,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer>
|
impl<'a, Message, Renderer: text::Renderer> Overlay<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
Renderer: 'a,
|
Renderer: 'a,
|
||||||
|
|
@ -168,7 +169,7 @@ where
|
||||||
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
|
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
|
||||||
for Overlay<'a, Message, Renderer>
|
for Overlay<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -233,45 +234,55 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.container
|
||||||
|
.mouse_interaction(layout, cursor_position, viewport)
|
||||||
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
let primitives = self.container.draw(
|
let bounds = layout.bounds();
|
||||||
renderer,
|
|
||||||
defaults,
|
renderer.fill_quad(
|
||||||
layout,
|
renderer::Quad {
|
||||||
cursor_position,
|
bounds,
|
||||||
&layout.bounds(),
|
border_color: self.style.border_color,
|
||||||
|
border_width: self.style.border_width,
|
||||||
|
border_radius: 0.0,
|
||||||
|
},
|
||||||
|
self.style.background,
|
||||||
);
|
);
|
||||||
|
|
||||||
renderer.decorate(
|
self.container
|
||||||
layout.bounds(),
|
.draw(renderer, style, layout, cursor_position, &bounds);
|
||||||
cursor_position,
|
|
||||||
&self.style,
|
|
||||||
primitives,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct List<'a, T, Renderer: self::Renderer> {
|
struct List<'a, T, Renderer: text::Renderer> {
|
||||||
options: &'a [T],
|
options: &'a [T],
|
||||||
hovered_option: &'a mut Option<usize>,
|
hovered_option: &'a mut Option<usize>,
|
||||||
last_selection: &'a mut Option<T>,
|
last_selection: &'a mut Option<T>,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Renderer: self::Renderer> Widget<Message, Renderer>
|
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
||||||
for List<'a, T, Renderer>
|
for List<'a, T, Renderer>
|
||||||
where
|
where
|
||||||
T: Clone + ToString,
|
T: Clone + ToString,
|
||||||
Renderer: self::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
Length::Fill
|
Length::Fill
|
||||||
|
|
@ -376,65 +387,84 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
let is_mouse_over = layout.bounds().contains(cursor_position);
|
||||||
|
|
||||||
|
if is_mouse_over {
|
||||||
|
mouse::Interaction::Pointer
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
_cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
self::Renderer::draw(
|
let bounds = layout.bounds();
|
||||||
renderer,
|
|
||||||
layout.bounds(),
|
let text_size = self.text_size.unwrap_or(renderer.default_size());
|
||||||
cursor_position,
|
let option_height = (text_size + self.padding.vertical()) as usize;
|
||||||
viewport,
|
|
||||||
self.options,
|
let offset = viewport.y - bounds.y;
|
||||||
*self.hovered_option,
|
let start = (offset / option_height as f32) as usize;
|
||||||
self.padding,
|
let end =
|
||||||
self.text_size.unwrap_or(renderer.default_size()),
|
((offset + viewport.height) / option_height as f32).ceil() as usize;
|
||||||
self.font,
|
|
||||||
&self.style,
|
let visible_options = &self.options[start..end.min(self.options.len())];
|
||||||
)
|
|
||||||
|
for (i, option) in visible_options.iter().enumerate() {
|
||||||
|
let i = start + i;
|
||||||
|
let is_selected = *self.hovered_option == Some(i);
|
||||||
|
|
||||||
|
let bounds = Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y + (option_height * i) as f32,
|
||||||
|
width: bounds.width,
|
||||||
|
height: f32::from(text_size + self.padding.vertical()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_selected {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_radius: 0.0,
|
||||||
|
},
|
||||||
|
self.style.selected_background,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// The renderer of a [`Menu`].
|
renderer.fill_text(Text {
|
||||||
///
|
content: &option.to_string(),
|
||||||
/// Your [renderer] will need to implement this trait before being
|
bounds: Rectangle {
|
||||||
/// able to use a [`Menu`] in your user interface.
|
x: bounds.x + self.padding.left as f32,
|
||||||
///
|
y: bounds.center_y(),
|
||||||
/// [renderer]: crate::renderer
|
width: f32::INFINITY,
|
||||||
pub trait Renderer:
|
..bounds
|
||||||
scrollable::Renderer + container::Renderer + text::Renderer
|
},
|
||||||
{
|
size: f32::from(text_size),
|
||||||
/// The [`Menu`] style supported by this renderer.
|
font: self.font,
|
||||||
type Style: Default + Clone;
|
color: if is_selected {
|
||||||
|
self.style.selected_text_color
|
||||||
/// Decorates a the list of options of a [`Menu`].
|
} else {
|
||||||
///
|
self.style.text_color
|
||||||
/// This method can be used to draw a background for the [`Menu`].
|
},
|
||||||
fn decorate(
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
&mut self,
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
bounds: Rectangle,
|
});
|
||||||
cursor_position: Point,
|
}
|
||||||
style: &<Self as Renderer>::Style,
|
}
|
||||||
primitive: Self::Output,
|
|
||||||
) -> Self::Output;
|
|
||||||
|
|
||||||
/// Draws the list of options of a [`Menu`].
|
|
||||||
fn draw<T: ToString>(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
options: &[T],
|
|
||||||
hovered_option: Option<usize>,
|
|
||||||
padding: Padding,
|
|
||||||
text_size: u16,
|
|
||||||
font: Self::Font,
|
|
||||||
style: &<Self as Renderer>::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
||||||
|
|
@ -442,7 +472,7 @@ impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
||||||
where
|
where
|
||||||
T: ToString + Clone,
|
T: ToString + Clone,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + text::Renderer,
|
||||||
{
|
{
|
||||||
fn into(self) -> Element<'a, Message, Renderer> {
|
fn into(self) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(self)
|
Element::new(self)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
use crate::mouse;
|
||||||
use crate::{
|
use crate::{
|
||||||
Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
|
Cache, Clipboard, Command, Debug, Event, Point, Program, Size,
|
||||||
UserInterface,
|
UserInterface,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -12,9 +13,9 @@ where
|
||||||
{
|
{
|
||||||
program: P,
|
program: P,
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
primitive: <P::Renderer as Renderer>::Output,
|
|
||||||
queued_events: Vec<Event>,
|
queued_events: Vec<Event>,
|
||||||
queued_messages: Vec<P::Message>,
|
queued_messages: Vec<P::Message>,
|
||||||
|
mouse_interaction: mouse::Interaction,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> State<P>
|
impl<P> State<P>
|
||||||
|
|
@ -26,11 +27,10 @@ where
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mut program: P,
|
mut program: P,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
cursor_position: Point,
|
|
||||||
renderer: &mut P::Renderer,
|
renderer: &mut P::Renderer,
|
||||||
debug: &mut Debug,
|
debug: &mut Debug,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut user_interface = build_user_interface(
|
let user_interface = build_user_interface(
|
||||||
&mut program,
|
&mut program,
|
||||||
Cache::default(),
|
Cache::default(),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -38,18 +38,14 @@ where
|
||||||
debug,
|
debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
debug.draw_started();
|
|
||||||
let primitive = user_interface.draw(renderer, cursor_position);
|
|
||||||
debug.draw_finished();
|
|
||||||
|
|
||||||
let cache = Some(user_interface.into_cache());
|
let cache = Some(user_interface.into_cache());
|
||||||
|
|
||||||
State {
|
State {
|
||||||
program,
|
program,
|
||||||
cache,
|
cache,
|
||||||
primitive,
|
|
||||||
queued_events: Vec::new(),
|
queued_events: Vec::new(),
|
||||||
queued_messages: Vec::new(),
|
queued_messages: Vec::new(),
|
||||||
|
mouse_interaction: mouse::Interaction::Idle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,11 +54,6 @@ where
|
||||||
&self.program
|
&self.program
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the current rendering primitive of the [`State`].
|
|
||||||
pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
|
|
||||||
&self.primitive
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues an event in the [`State`] for processing during an [`update`].
|
/// Queues an event in the [`State`] for processing during an [`update`].
|
||||||
///
|
///
|
||||||
/// [`update`]: Self::update
|
/// [`update`]: Self::update
|
||||||
|
|
@ -82,6 +73,11 @@ where
|
||||||
self.queued_events.is_empty() && self.queued_messages.is_empty()
|
self.queued_events.is_empty() && self.queued_messages.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`mouse::Interaction`] of the [`State`].
|
||||||
|
pub fn mouse_interaction(&self) -> mouse::Interaction {
|
||||||
|
self.mouse_interaction
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes all the queued events and messages, rebuilding and redrawing
|
/// Processes all the queued events and messages, rebuilding and redrawing
|
||||||
/// the widgets of the linked [`Program`] if necessary.
|
/// the widgets of the linked [`Program`] if necessary.
|
||||||
///
|
///
|
||||||
|
|
@ -120,7 +116,8 @@ where
|
||||||
|
|
||||||
if messages.is_empty() {
|
if messages.is_empty() {
|
||||||
debug.draw_started();
|
debug.draw_started();
|
||||||
self.primitive = user_interface.draw(renderer, cursor_position);
|
self.mouse_interaction =
|
||||||
|
user_interface.draw(renderer, cursor_position);
|
||||||
debug.draw_finished();
|
debug.draw_finished();
|
||||||
|
|
||||||
self.cache = Some(user_interface.into_cache());
|
self.cache = Some(user_interface.into_cache());
|
||||||
|
|
@ -151,7 +148,8 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
debug.draw_started();
|
debug.draw_started();
|
||||||
self.primitive = user_interface.draw(renderer, cursor_position);
|
self.mouse_interaction =
|
||||||
|
user_interface.draw(renderer, cursor_position);
|
||||||
debug.draw_finished();
|
debug.draw_finished();
|
||||||
|
|
||||||
self.cache = Some(user_interface.into_cache());
|
self.cache = Some(user_interface.into_cache());
|
||||||
|
|
|
||||||
|
|
@ -19,28 +19,17 @@
|
||||||
//! [`text::Renderer`]: crate::widget::text::Renderer
|
//! [`text::Renderer`]: crate::widget::text::Renderer
|
||||||
//! [`Checkbox`]: crate::widget::Checkbox
|
//! [`Checkbox`]: crate::widget::Checkbox
|
||||||
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
|
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod null;
|
mod null;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub use null::Null;
|
pub use null::Null;
|
||||||
|
|
||||||
use crate::{layout, Element, Rectangle};
|
use crate::layout;
|
||||||
|
use crate::{Background, Color, Element, Rectangle, Vector};
|
||||||
|
|
||||||
/// A component that can take the state of a user interface and produce an
|
/// A component that can take the state of a user interface and produce an
|
||||||
/// output for its users.
|
/// output for its users.
|
||||||
pub trait Renderer: Sized {
|
pub trait Renderer: Sized {
|
||||||
/// The type of output of the [`Renderer`].
|
|
||||||
///
|
|
||||||
/// If you are implementing a graphical renderer, your output will most
|
|
||||||
/// likely be a tree of visual primitives.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// The default styling attributes of the [`Renderer`].
|
|
||||||
///
|
|
||||||
/// This type can be leveraged to implement style inheritance.
|
|
||||||
type Defaults: Default;
|
|
||||||
|
|
||||||
/// Lays out the elements of a user interface.
|
/// Lays out the elements of a user interface.
|
||||||
///
|
///
|
||||||
/// You should override this if you need to perform any operations before or
|
/// You should override this if you need to perform any operations before or
|
||||||
|
|
@ -53,12 +42,52 @@ pub trait Renderer: Sized {
|
||||||
element.layout(self, limits)
|
element.layout(self, limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overlays the `overlay` output with the given bounds on top of the `base`
|
/// Draws the primitives recorded in the given closure in a new layer.
|
||||||
/// output.
|
///
|
||||||
fn overlay(
|
/// The layer will clip its contents to the provided `bounds`.
|
||||||
|
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
|
||||||
|
|
||||||
|
/// Applies a `translation` to the primitives recorded in the given closure.
|
||||||
|
fn with_translation(
|
||||||
&mut self,
|
&mut self,
|
||||||
base: Self::Output,
|
translation: Vector,
|
||||||
overlay: Self::Output,
|
f: impl FnOnce(&mut Self),
|
||||||
overlay_bounds: Rectangle,
|
);
|
||||||
) -> Self::Output;
|
|
||||||
|
/// Clears all of the recorded primitives in the [`Renderer`].
|
||||||
|
fn clear(&mut self);
|
||||||
|
|
||||||
|
/// Fills a [`Quad`] with the provided [`Background`].
|
||||||
|
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A polygon with four sides.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Quad {
|
||||||
|
/// The bounds of the [`Quad`].
|
||||||
|
pub bounds: Rectangle,
|
||||||
|
|
||||||
|
/// The border radius of the [`Quad`].
|
||||||
|
pub border_radius: f32,
|
||||||
|
|
||||||
|
/// The border width of the [`Quad`].
|
||||||
|
pub border_width: f32,
|
||||||
|
|
||||||
|
/// The border color of the [`Quad`].
|
||||||
|
pub border_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The styling attributes of a [`Renderer`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Style {
|
||||||
|
/// The text color
|
||||||
|
pub text_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Style {
|
||||||
|
fn default() -> Self {
|
||||||
|
Style {
|
||||||
|
text_color: Color::BLACK,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,6 @@
|
||||||
use crate::alignment;
|
use crate::renderer::{self, Renderer};
|
||||||
use crate::button;
|
use crate::text::{self, Text};
|
||||||
use crate::checkbox;
|
use crate::{Background, Font, Point, Rectangle, Size, Vector};
|
||||||
use crate::column;
|
|
||||||
use crate::container;
|
|
||||||
use crate::pane_grid;
|
|
||||||
use crate::progress_bar;
|
|
||||||
use crate::radio;
|
|
||||||
use crate::row;
|
|
||||||
use crate::scrollable;
|
|
||||||
use crate::slider;
|
|
||||||
use crate::text;
|
|
||||||
use crate::text_input;
|
|
||||||
use crate::toggler;
|
|
||||||
use crate::{
|
|
||||||
Color, Element, Font, Layout, Padding, Point, Rectangle, Renderer, Size,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A renderer that does nothing.
|
/// A renderer that does nothing.
|
||||||
///
|
///
|
||||||
|
|
@ -30,33 +16,21 @@ impl Null {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer for Null {
|
impl Renderer for Null {
|
||||||
type Output = ();
|
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
|
||||||
type Defaults = ();
|
|
||||||
|
|
||||||
fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) {
|
fn with_translation(
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl column::Renderer for Null {
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
_defaults: &Self::Defaults,
|
_translation: Vector,
|
||||||
_content: &[Element<'_, Message, Self>],
|
_f: impl FnOnce(&mut Self),
|
||||||
_layout: Layout<'_>,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl row::Renderer for Null {
|
fn clear(&mut self) {}
|
||||||
fn draw<Message>(
|
|
||||||
|
fn fill_quad(
|
||||||
&mut self,
|
&mut self,
|
||||||
_defaults: &Self::Defaults,
|
_quad: renderer::Quad,
|
||||||
_content: &[Element<'_, Message, Self>],
|
_background: impl Into<Background>,
|
||||||
_layout: Layout<'_>,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +38,10 @@ impl row::Renderer for Null {
|
||||||
impl text::Renderer for Null {
|
impl text::Renderer for Null {
|
||||||
type Font = Font;
|
type Font = Font;
|
||||||
|
|
||||||
|
const ICON_FONT: Font = Font::Default;
|
||||||
|
const CHECKMARK_ICON: char = '0';
|
||||||
|
const ARROW_DOWN_ICON: char = '0';
|
||||||
|
|
||||||
fn default_size(&self) -> u16 {
|
fn default_size(&self) -> u16 {
|
||||||
20
|
20
|
||||||
}
|
}
|
||||||
|
|
@ -90,240 +68,5 @@ impl text::Renderer for Null {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
|
||||||
&mut self,
|
|
||||||
_defaults: &Self::Defaults,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_content: &str,
|
|
||||||
_size: u16,
|
|
||||||
_font: Font,
|
|
||||||
_color: Option<Color>,
|
|
||||||
_horizontal_alignment: alignment::Horizontal,
|
|
||||||
_vertical_alignment: alignment::Vertical,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl scrollable::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
fn scrollbar(
|
|
||||||
&self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_content_bounds: Rectangle,
|
|
||||||
_offset: u32,
|
|
||||||
_scrollbar_width: u16,
|
|
||||||
_scrollbar_margin: u16,
|
|
||||||
_scroller_width: u16,
|
|
||||||
) -> Option<scrollable::Scrollbar> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
_scrollable: &scrollable::State,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_content_bounds: Rectangle,
|
|
||||||
_is_mouse_over: bool,
|
|
||||||
_is_mouse_over_scrollbar: bool,
|
|
||||||
_scrollbar: Option<scrollable::Scrollbar>,
|
|
||||||
_offset: u32,
|
|
||||||
_style: &Self::Style,
|
|
||||||
_content: Self::Output,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl text_input::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn offset(
|
|
||||||
&self,
|
|
||||||
_text_bounds: Rectangle,
|
|
||||||
_font: Font,
|
|
||||||
_size: u16,
|
|
||||||
_value: &text_input::Value,
|
|
||||||
_state: &text_input::State,
|
|
||||||
) -> f32 {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_text_bounds: Rectangle,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_font: Font,
|
|
||||||
_size: u16,
|
|
||||||
_placeholder: &str,
|
|
||||||
_value: &text_input::Value,
|
|
||||||
_state: &text_input::State,
|
|
||||||
_style: &Self::Style,
|
|
||||||
) -> Self::Output {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl button::Renderer for Null {
|
|
||||||
const DEFAULT_PADDING: Padding = Padding::ZERO;
|
|
||||||
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
_defaults: &Self::Defaults,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_is_disabled: bool,
|
|
||||||
_is_pressed: bool,
|
|
||||||
_style: &Self::Style,
|
|
||||||
_content: &Element<'_, Message, Self>,
|
|
||||||
_content_layout: Layout<'_>,
|
|
||||||
) -> Self::Output {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl radio::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: u16 = 20;
|
|
||||||
const DEFAULT_SPACING: u16 = 15;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_is_selected: bool,
|
|
||||||
_is_mouse_over: bool,
|
|
||||||
_label: Self::Output,
|
|
||||||
_style: &Self::Style,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl checkbox::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: u16 = 20;
|
|
||||||
const DEFAULT_SPACING: u16 = 15;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_is_checked: bool,
|
|
||||||
_is_mouse_over: bool,
|
|
||||||
_label: Self::Output,
|
|
||||||
_style: &Self::Style,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl slider::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
const DEFAULT_HEIGHT: u16 = 30;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_range: std::ops::RangeInclusive<f32>,
|
|
||||||
_value: f32,
|
|
||||||
_is_dragging: bool,
|
|
||||||
_style_sheet: &Self::Style,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl progress_bar::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
const DEFAULT_HEIGHT: u16 = 30;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_range: std::ops::RangeInclusive<f32>,
|
|
||||||
_value: f32,
|
|
||||||
_style: &Self::Style,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl container::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
_defaults: &Self::Defaults,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
_style: &Self::Style,
|
|
||||||
_content: &Element<'_, Message, Self>,
|
|
||||||
_content_layout: Layout<'_>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl pane_grid::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
_defaults: &Self::Defaults,
|
|
||||||
_content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
|
|
||||||
_dragging: Option<(pane_grid::Pane, Point)>,
|
|
||||||
_resizing: Option<(pane_grid::Axis, Rectangle, bool)>,
|
|
||||||
_layout: Layout<'_>,
|
|
||||||
_style: &<Self as pane_grid::Renderer>::Style,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_pane<Message>(
|
|
||||||
&mut self,
|
|
||||||
_defaults: &Self::Defaults,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_style: &<Self as container::Renderer>::Style,
|
|
||||||
_title_bar: Option<(
|
|
||||||
&pane_grid::TitleBar<'_, Message, Self>,
|
|
||||||
Layout<'_>,
|
|
||||||
)>,
|
|
||||||
_body: (&Element<'_, Message, Self>, Layout<'_>),
|
|
||||||
_cursor_position: Point,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_title_bar<Message>(
|
|
||||||
&mut self,
|
|
||||||
_defaults: &Self::Defaults,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_style: &<Self as container::Renderer>::Style,
|
|
||||||
_content: (&Element<'_, Message, Self>, Layout<'_>),
|
|
||||||
_controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
|
||||||
_cursor_position: Point,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl toggler::Renderer for Null {
|
|
||||||
type Style = ();
|
|
||||||
|
|
||||||
const DEFAULT_SIZE: u16 = 20;
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
_bounds: Rectangle,
|
|
||||||
_is_checked: bool,
|
|
||||||
_is_mouse_over: bool,
|
|
||||||
_label: Option<Self::Output>,
|
|
||||||
_style: &Self::Style,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
88
native/src/svg.rs
Normal file
88
native/src/svg.rs
Normal 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
114
native/src/text.rs
Normal 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>);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
|
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
|
||||||
|
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
|
|
@ -47,7 +49,7 @@ where
|
||||||
/// # pub use iced_native::renderer::Null as Renderer;
|
/// # pub use iced_native::renderer::Null as Renderer;
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # use iced_native::Column;
|
/// # use iced_native::widget::Column;
|
||||||
/// #
|
/// #
|
||||||
/// # pub struct Counter;
|
/// # pub struct Counter;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -141,7 +143,7 @@ where
|
||||||
/// # pub use iced_native::renderer::Null as Renderer;
|
/// # pub use iced_native::renderer::Null as Renderer;
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # use iced_native::Column;
|
/// # use iced_native::widget::Column;
|
||||||
/// #
|
/// #
|
||||||
/// # pub struct Counter;
|
/// # pub struct Counter;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -277,7 +279,7 @@ where
|
||||||
/// # pub use iced_native::renderer::Null as Renderer;
|
/// # pub use iced_native::renderer::Null as Renderer;
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # use iced_native::Column;
|
/// # use iced_native::widget::Column;
|
||||||
/// #
|
/// #
|
||||||
/// # pub struct Counter;
|
/// # pub struct Counter;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -333,10 +335,13 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
|
// TODO: Move to shell level (?)
|
||||||
|
renderer.clear();
|
||||||
|
|
||||||
let viewport = Rectangle::with_size(self.bounds);
|
let viewport = Rectangle::with_size(self.bounds);
|
||||||
|
|
||||||
let overlay = if let Some(mut overlay) =
|
if let Some(mut overlay) =
|
||||||
self.root.overlay(Layout::new(&self.base.layout))
|
self.root.overlay(Layout::new(&self.base.layout))
|
||||||
{
|
{
|
||||||
let layer = Self::overlay_layer(
|
let layer = Self::overlay_layer(
|
||||||
|
|
@ -346,51 +351,81 @@ where
|
||||||
renderer,
|
renderer,
|
||||||
);
|
);
|
||||||
|
|
||||||
let overlay_bounds = layer.layout.bounds();
|
|
||||||
|
|
||||||
let overlay_primitives = overlay.draw(
|
|
||||||
renderer,
|
|
||||||
&Renderer::Defaults::default(),
|
|
||||||
Layout::new(&layer.layout),
|
|
||||||
cursor_position,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.overlay = Some(layer);
|
self.overlay = Some(layer);
|
||||||
|
|
||||||
Some((overlay_primitives, overlay_bounds))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((overlay_primitives, overlay_bounds)) = overlay {
|
if let Some(layer) = &self.overlay {
|
||||||
let base_cursor = if overlay_bounds.contains(cursor_position) {
|
let base_cursor = if layer.layout.bounds().contains(cursor_position)
|
||||||
|
{
|
||||||
Point::new(-1.0, -1.0)
|
Point::new(-1.0, -1.0)
|
||||||
} else {
|
} else {
|
||||||
cursor_position
|
cursor_position
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_primitives = self.root.widget.draw(
|
self.root.widget.draw(
|
||||||
renderer,
|
renderer,
|
||||||
&Renderer::Defaults::default(),
|
&renderer::Style::default(),
|
||||||
Layout::new(&self.base.layout),
|
Layout::new(&self.base.layout),
|
||||||
base_cursor,
|
base_cursor,
|
||||||
&viewport,
|
&viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
renderer.overlay(
|
|
||||||
base_primitives,
|
|
||||||
overlay_primitives,
|
|
||||||
overlay_bounds,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
self.root.widget.draw(
|
self.root.widget.draw(
|
||||||
renderer,
|
renderer,
|
||||||
&Renderer::Defaults::default(),
|
&renderer::Style::default(),
|
||||||
Layout::new(&self.base.layout),
|
Layout::new(&self.base.layout),
|
||||||
cursor_position,
|
cursor_position,
|
||||||
&viewport,
|
&viewport,
|
||||||
)
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let base_interaction = self.root.widget.mouse_interaction(
|
||||||
|
Layout::new(&self.base.layout),
|
||||||
|
cursor_position,
|
||||||
|
&viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
let Self {
|
||||||
|
overlay,
|
||||||
|
root,
|
||||||
|
base,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
// TODO: Currently, we need to call Widget::overlay twice to
|
||||||
|
// implement the painter's algorithm properly.
|
||||||
|
//
|
||||||
|
// Once we have a proper persistent widget tree, we should be able to
|
||||||
|
// avoid this additional call.
|
||||||
|
overlay
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|layer| {
|
||||||
|
root.overlay(Layout::new(&base.layout)).map(|overlay| {
|
||||||
|
let overlay_interaction = overlay.mouse_interaction(
|
||||||
|
Layout::new(&layer.layout),
|
||||||
|
cursor_position,
|
||||||
|
&viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
let overlay_bounds = layer.layout.bounds();
|
||||||
|
|
||||||
|
renderer.with_layer(viewport, |renderer| {
|
||||||
|
overlay.draw(
|
||||||
|
renderer,
|
||||||
|
&renderer::Style::default(),
|
||||||
|
Layout::new(&layer.layout),
|
||||||
|
cursor_position,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if overlay_bounds.contains(cursor_position) {
|
||||||
|
overlay_interaction
|
||||||
|
} else {
|
||||||
|
base_interaction
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(base_interaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relayouts and returns a new [`UserInterface`] using the provided
|
/// Relayouts and returns a new [`UserInterface`] using the provided
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,6 @@
|
||||||
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
|
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
|
||||||
//! source of inspiration.
|
//! source of inspiration.
|
||||||
//!
|
//!
|
||||||
//! # Re-exports
|
|
||||||
//! For convenience, the contents of this module are available at the root
|
|
||||||
//! module. Therefore, you can directly type:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! use iced_native::{button, Button, Widget};
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! [renderer]: crate::renderer
|
//! [renderer]: crate::renderer
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod checkbox;
|
pub mod checkbox;
|
||||||
|
|
@ -80,7 +72,9 @@ pub use tooltip::Tooltip;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
|
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
|
||||||
|
|
||||||
/// A component that displays information and allows interaction.
|
/// A component that displays information and allows interaction.
|
||||||
|
|
@ -131,11 +125,11 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output;
|
);
|
||||||
|
|
||||||
/// Computes the _layout_ hash of the [`Widget`].
|
/// Computes the _layout_ hash of the [`Widget`].
|
||||||
///
|
///
|
||||||
|
|
@ -174,6 +168,18 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
|
||||||
|
///
|
||||||
|
/// By default, it returns [`mouse::Interaction::Idle`].
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
_layout: Layout<'_>,
|
||||||
|
_cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
mouse::Interaction::Idle
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the overlay of the [`Widget`], if there is any.
|
/// Returns the overlay of the [`Widget`], if there is any.
|
||||||
fn overlay(
|
fn overlay(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,24 @@ use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
|
||||||
Widget,
|
Point, Rectangle, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
pub use iced_style::button::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A generic widget that produces a message when pressed.
|
/// A generic widget that produces a message when pressed.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::{button, Text};
|
/// # use iced_native::widget::{button, Text};
|
||||||
/// #
|
/// #
|
||||||
/// # type Button<'a, Message> =
|
/// # type Button<'a, Message> =
|
||||||
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
|
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// enum Message {
|
/// enum Message {
|
||||||
|
|
@ -34,10 +38,10 @@ use std::hash::Hash;
|
||||||
/// be disabled:
|
/// be disabled:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::{button, Text};
|
/// # use iced_native::widget::{button, Text};
|
||||||
/// #
|
/// #
|
||||||
/// # type Button<'a, Message> =
|
/// # type Button<'a, Message> =
|
||||||
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
|
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// enum Message {
|
/// enum Message {
|
||||||
|
|
@ -53,7 +57,7 @@ use std::hash::Hash;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Button<'a, Message, Renderer: self::Renderer> {
|
pub struct Button<'a, Message, Renderer> {
|
||||||
state: &'a mut State,
|
state: &'a mut State,
|
||||||
content: Element<'a, Message, Renderer>,
|
content: Element<'a, Message, Renderer>,
|
||||||
on_press: Option<Message>,
|
on_press: Option<Message>,
|
||||||
|
|
@ -62,13 +66,13 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
|
||||||
min_width: u32,
|
min_width: u32,
|
||||||
min_height: u32,
|
min_height: u32,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
|
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Button`] with some local [`State`] and the given
|
/// Creates a new [`Button`] with some local [`State`] and the given
|
||||||
/// content.
|
/// content.
|
||||||
|
|
@ -84,8 +88,8 @@ where
|
||||||
height: Length::Shrink,
|
height: Length::Shrink,
|
||||||
min_width: 0,
|
min_width: 0,
|
||||||
min_height: 0,
|
min_height: 0,
|
||||||
padding: Renderer::DEFAULT_PADDING,
|
padding: Padding::new(5),
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,8 +131,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Button`].
|
/// Sets the style of the [`Button`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Button<'a, Message, Renderer>
|
for Button<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -241,24 +248,88 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
renderer.draw(
|
let is_mouse_over = layout.bounds().contains(cursor_position);
|
||||||
defaults,
|
let is_disabled = self.on_press.is_none();
|
||||||
layout.bounds(),
|
|
||||||
|
if is_mouse_over && !is_disabled {
|
||||||
|
mouse::Interaction::Pointer
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
let is_disabled = self.on_press.is_none();
|
||||||
|
|
||||||
|
let styling = if is_disabled {
|
||||||
|
self.style_sheet.disabled()
|
||||||
|
} else if is_mouse_over {
|
||||||
|
if self.state.is_pressed {
|
||||||
|
self.style_sheet.pressed()
|
||||||
|
} else {
|
||||||
|
self.style_sheet.hovered()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.style_sheet.active()
|
||||||
|
};
|
||||||
|
|
||||||
|
if styling.background.is_some() || styling.border_width > 0.0 {
|
||||||
|
if styling.shadow_offset != Vector::default() {
|
||||||
|
// TODO: Implement proper shadow support
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + styling.shadow_offset.x,
|
||||||
|
y: bounds.y + styling.shadow_offset.y,
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
border_radius: styling.border_radius,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
Background::Color([0.0, 0.0, 0.0, 0.5].into()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_radius: styling.border_radius,
|
||||||
|
border_width: styling.border_width,
|
||||||
|
border_color: styling.border_color,
|
||||||
|
},
|
||||||
|
styling
|
||||||
|
.background
|
||||||
|
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content.draw(
|
||||||
|
renderer,
|
||||||
|
&renderer::Style {
|
||||||
|
text_color: styling.text_color,
|
||||||
|
},
|
||||||
|
content_layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
self.on_press.is_none(),
|
&bounds,
|
||||||
self.state.is_pressed,
|
);
|
||||||
&self.style,
|
|
||||||
&self.content,
|
|
||||||
layout.children().next().unwrap(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -277,38 +348,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Button`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Button`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer + Sized {
|
|
||||||
/// The default padding of a [`Button`].
|
|
||||||
const DEFAULT_PADDING: Padding;
|
|
||||||
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Draws a [`Button`].
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
is_disabled: bool,
|
|
||||||
is_pressed: bool,
|
|
||||||
style: &Self::Style,
|
|
||||||
content: &Element<'_, Message, Self>,
|
|
||||||
content_layout: Layout<'_>,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a + Clone,
|
Message: 'a + Clone,
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
button: Button<'a, Message, Renderer>,
|
button: Button<'a, Message, Renderer>,
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,27 @@
|
||||||
//! Show toggle controls using checkboxes.
|
//! Show toggle controls using checkboxes.
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::alignment::{self, Alignment};
|
use crate::alignment;
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::row;
|
use crate::renderer;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
|
use crate::widget::{self, Row, Text};
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
|
Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
|
||||||
Text, Widget,
|
Rectangle, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use iced_style::checkbox::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A box that can be checked.
|
/// A box that can be checked.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>;
|
/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// pub enum Message {
|
/// pub enum Message {
|
||||||
/// CheckboxToggled(bool),
|
/// CheckboxToggled(bool),
|
||||||
|
|
@ -31,7 +34,7 @@ use crate::{
|
||||||
///
|
///
|
||||||
/// 
|
/// 
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
|
pub struct Checkbox<'a, Message, Renderer: text::Renderer> {
|
||||||
is_checked: bool,
|
is_checked: bool,
|
||||||
on_toggle: Box<dyn Fn(bool) -> Message>,
|
on_toggle: Box<dyn Fn(bool) -> Message>,
|
||||||
label: String,
|
label: String,
|
||||||
|
|
@ -41,12 +44,16 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
text_color: Option<Color>,
|
text_color: Option<Color>,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Renderer: self::Renderer + text::Renderer>
|
impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> {
|
||||||
Checkbox<Message, Renderer>
|
/// The default size of a [`Checkbox`].
|
||||||
{
|
const DEFAULT_SIZE: u16 = 20;
|
||||||
|
|
||||||
|
/// The default spacing of a [`Checkbox`].
|
||||||
|
const DEFAULT_SPACING: u16 = 15;
|
||||||
|
|
||||||
/// Creates a new [`Checkbox`].
|
/// Creates a new [`Checkbox`].
|
||||||
///
|
///
|
||||||
/// It expects:
|
/// It expects:
|
||||||
|
|
@ -64,12 +71,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
|
||||||
on_toggle: Box::new(f),
|
on_toggle: Box::new(f),
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
|
size: Self::DEFAULT_SIZE,
|
||||||
spacing: Renderer::DEFAULT_SPACING,
|
spacing: Self::DEFAULT_SPACING,
|
||||||
text_size: None,
|
text_size: None,
|
||||||
font: Renderer::Font::default(),
|
font: Renderer::Font::default(),
|
||||||
text_color: None,
|
text_color: None,
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,16 +119,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Checkbox`].
|
/// Sets the style of the [`Checkbox`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Checkbox<Message, Renderer>
|
for Checkbox<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -180,43 +190,84 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
|
if layout.bounds().contains(cursor_position) {
|
||||||
|
mouse::Interaction::Pointer
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
|
|
||||||
let checkbox_layout = children.next().unwrap();
|
{
|
||||||
let label_layout = children.next().unwrap();
|
let layout = children.next().unwrap();
|
||||||
let checkbox_bounds = checkbox_layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let label = text::Renderer::draw(
|
let style = if is_mouse_over {
|
||||||
|
self.style_sheet.hovered(self.is_checked)
|
||||||
|
} else {
|
||||||
|
self.style_sheet.active(self.is_checked)
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
border_width: style.border_width,
|
||||||
|
border_color: style.border_color,
|
||||||
|
},
|
||||||
|
style.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.is_checked {
|
||||||
|
renderer.fill_text(text::Text {
|
||||||
|
content: &Renderer::CHECKMARK_ICON.to_string(),
|
||||||
|
font: Renderer::ICON_FONT,
|
||||||
|
size: bounds.height * 0.7,
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.center_x(),
|
||||||
|
y: bounds.center_y(),
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
color: style.checkmark_color,
|
||||||
|
horizontal_alignment: alignment::Horizontal::Center,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let label_layout = children.next().unwrap();
|
||||||
|
|
||||||
|
widget::text::draw(
|
||||||
renderer,
|
renderer,
|
||||||
defaults,
|
style,
|
||||||
label_layout.bounds(),
|
label_layout,
|
||||||
&self.label,
|
&self.label,
|
||||||
self.text_size.unwrap_or(renderer.default_size()),
|
|
||||||
self.font,
|
self.font,
|
||||||
|
self.text_size,
|
||||||
self.text_color,
|
self.text_color,
|
||||||
alignment::Horizontal::Left,
|
alignment::Horizontal::Left,
|
||||||
alignment::Vertical::Center,
|
alignment::Vertical::Center,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
|
|
||||||
self::Renderer::draw(
|
|
||||||
renderer,
|
|
||||||
checkbox_bounds,
|
|
||||||
self.is_checked,
|
|
||||||
is_mouse_over,
|
|
||||||
label,
|
|
||||||
&self.style,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -227,47 +278,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Checkbox`].
|
impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Checkbox`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::Renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// The default size of a [`Checkbox`].
|
|
||||||
const DEFAULT_SIZE: u16;
|
|
||||||
|
|
||||||
/// The default spacing of a [`Checkbox`].
|
|
||||||
const DEFAULT_SPACING: u16;
|
|
||||||
|
|
||||||
/// Draws a [`Checkbox`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// * the bounds of the [`Checkbox`]
|
|
||||||
/// * whether the [`Checkbox`] is selected or not
|
|
||||||
/// * whether the mouse is over the [`Checkbox`] or not
|
|
||||||
/// * the drawn label of the [`Checkbox`]
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
is_checked: bool,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
label: Self::Output,
|
|
||||||
style: &Self::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
|
Renderer: 'a + text::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
checkbox: Checkbox<Message, Renderer>,
|
checkbox: Checkbox<'a, Message, Renderer>,
|
||||||
) -> Element<'a, Message, Renderer> {
|
) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(checkbox)
|
Element::new(checkbox)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ use std::hash::Hash;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{
|
use crate::{
|
||||||
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
|
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
|
||||||
Rectangle, Widget,
|
Rectangle, Widget,
|
||||||
|
|
@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Column<'a, Message, Renderer>
|
for Column<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -162,21 +164,37 @@ where
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
.fold(event::Status::Ignored, event::Status::merge)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
renderer.draw(
|
self.children
|
||||||
defaults,
|
.iter()
|
||||||
&self.children,
|
.zip(layout.children())
|
||||||
|
.map(|(child, layout)| {
|
||||||
|
child.widget.mouse_interaction(
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||||
|
child.draw(renderer, style, layout, cursor_position, viewport);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -208,33 +226,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Column`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Column`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer + Sized {
|
|
||||||
/// Draws a [`Column`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the children of the [`Column`]
|
|
||||||
/// - the [`Layout`] of the [`Column`] and its children
|
|
||||||
/// - the cursor position
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
content: &[Element<'_, Message, Self>],
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,23 @@ use std::hash::Hash;
|
||||||
use crate::alignment::{self, Alignment};
|
use crate::alignment::{self, Alignment};
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
|
||||||
Widget,
|
Point, Rectangle, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::u32;
|
use std::u32;
|
||||||
|
|
||||||
|
pub use iced_style::container::{Style, StyleSheet};
|
||||||
|
|
||||||
/// An element decorating some content.
|
/// An element decorating some content.
|
||||||
///
|
///
|
||||||
/// It is normally used for alignment purposes.
|
/// It is normally used for alignment purposes.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Container<'a, Message, Renderer: self::Renderer> {
|
pub struct Container<'a, Message, Renderer> {
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
|
|
@ -24,13 +28,13 @@ pub struct Container<'a, Message, Renderer: self::Renderer> {
|
||||||
max_height: u32,
|
max_height: u32,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
content: Element<'a, Message, Renderer>,
|
content: Element<'a, Message, Renderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
|
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates an empty [`Container`].
|
/// Creates an empty [`Container`].
|
||||||
pub fn new<T>(content: T) -> Self
|
pub fn new<T>(content: T) -> Self
|
||||||
|
|
@ -45,7 +49,7 @@ where
|
||||||
max_height: u32::MAX,
|
max_height: u32::MAX,
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,8 +109,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Container`].
|
/// Sets the style of the [`Container`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +121,7 @@ where
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Container<'a, Message, Renderer>
|
for Container<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -172,25 +179,44 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
renderer.draw(
|
self.content.widget.mouse_interaction(
|
||||||
defaults,
|
layout.children().next().unwrap(),
|
||||||
layout.bounds(),
|
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport,
|
viewport,
|
||||||
&self.style,
|
|
||||||
&self.content,
|
|
||||||
layout.children().next().unwrap(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
renderer_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let style = self.style_sheet.style();
|
||||||
|
|
||||||
|
draw_background(renderer, &style, layout.bounds());
|
||||||
|
|
||||||
|
self.content.draw(
|
||||||
|
renderer,
|
||||||
|
&renderer::Style {
|
||||||
|
text_color: style
|
||||||
|
.text_color
|
||||||
|
.unwrap_or(renderer_style.text_color),
|
||||||
|
},
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
struct Marker;
|
struct Marker;
|
||||||
std::any::TypeId::of::<Marker>().hash(state);
|
std::any::TypeId::of::<Marker>().hash(state);
|
||||||
|
|
@ -212,33 +238,33 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Container`].
|
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
|
||||||
///
|
pub fn draw_background<Renderer>(
|
||||||
/// Your [renderer] will need to implement this trait before being
|
renderer: &mut Renderer,
|
||||||
/// able to use a [`Container`] in your user interface.
|
style: &Style,
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Draws a [`Container`].
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor_position: Point,
|
) where
|
||||||
viewport: &Rectangle,
|
Renderer: crate::Renderer,
|
||||||
style: &Self::Style,
|
{
|
||||||
content: &Element<'_, Message, Self>,
|
if style.background.is_some() || style.border_width > 0.0 {
|
||||||
content_layout: Layout<'_>,
|
renderer.fill_quad(
|
||||||
) -> Self::Output;
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
border_width: style.border_width,
|
||||||
|
border_color: style.border_color,
|
||||||
|
},
|
||||||
|
style
|
||||||
|
.background
|
||||||
|
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,19 @@
|
||||||
pub mod viewer;
|
pub mod viewer;
|
||||||
pub use viewer::Viewer;
|
pub use viewer::Viewer;
|
||||||
|
|
||||||
|
use crate::image::{self, Handle};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
||||||
|
|
||||||
use std::{
|
use std::hash::Hash;
|
||||||
hash::{Hash, Hasher as _},
|
|
||||||
path::PathBuf,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A frame that displays an image while keeping aspect ratio.
|
/// A frame that displays an image while keeping aspect ratio.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::Image;
|
/// # use iced_native::widget::Image;
|
||||||
/// #
|
/// #
|
||||||
/// let image = Image::new("resources/ferris.png");
|
/// let image = Image::new("resources/ferris.png");
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -54,7 +52,7 @@ impl Image {
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for Image
|
impl<Message, Renderer> Widget<Message, Renderer> for Image
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: image::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -92,12 +90,12 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.draw(self.handle.clone(), layout)
|
renderer.draw(self.handle.clone(), layout.bounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -110,129 +108,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`Image`] handle.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Handle {
|
|
||||||
id: u64,
|
|
||||||
data: Arc<Data>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handle {
|
|
||||||
/// Creates an image [`Handle`] pointing to the image of the given path.
|
|
||||||
///
|
|
||||||
/// Makes an educated guess about the image format by examining the data in the file.
|
|
||||||
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
|
|
||||||
Self::from_data(Data::Path(path.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an image [`Handle`] containing the image pixels directly. This
|
|
||||||
/// function expects the input data to be provided as a `Vec<u8>` of BGRA
|
|
||||||
/// pixels.
|
|
||||||
///
|
|
||||||
/// This is useful if you have already decoded your image.
|
|
||||||
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
|
|
||||||
Self::from_data(Data::Pixels {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
pixels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an image [`Handle`] containing the image data directly.
|
|
||||||
///
|
|
||||||
/// Makes an educated guess about the image format by examining the given data.
|
|
||||||
///
|
|
||||||
/// This is useful if you already have your image loaded in-memory, maybe
|
|
||||||
/// because you downloaded or generated it procedurally.
|
|
||||||
pub fn from_memory(bytes: Vec<u8>) -> Handle {
|
|
||||||
Self::from_data(Data::Bytes(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_data(data: Data) -> Handle {
|
|
||||||
let mut hasher = Hasher::default();
|
|
||||||
data.hash(&mut hasher);
|
|
||||||
|
|
||||||
Handle {
|
|
||||||
id: hasher.finish(),
|
|
||||||
data: Arc::new(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the unique identifier of the [`Handle`].
|
|
||||||
pub fn id(&self) -> u64 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the image [`Data`].
|
|
||||||
pub fn data(&self) -> &Data {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for Handle
|
|
||||||
where
|
|
||||||
T: Into<PathBuf>,
|
|
||||||
{
|
|
||||||
fn from(path: T) -> Handle {
|
|
||||||
Handle::from_path(path.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Handle {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.id.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data of an [`Image`].
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub enum Data {
|
|
||||||
/// File data
|
|
||||||
Path(PathBuf),
|
|
||||||
|
|
||||||
/// In-memory data
|
|
||||||
Bytes(Vec<u8>),
|
|
||||||
|
|
||||||
/// Decoded image pixels in BGRA format.
|
|
||||||
Pixels {
|
|
||||||
/// The width of the image.
|
|
||||||
width: u32,
|
|
||||||
/// The height of the image.
|
|
||||||
height: u32,
|
|
||||||
/// The pixels.
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Data {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
|
||||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
|
||||||
Data::Pixels { width, height, .. } => {
|
|
||||||
write!(f, "Pixels({} * {})", width, height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The renderer of an [`Image`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being able to use
|
|
||||||
/// an [`Image`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// Returns the dimensions of an [`Image`] located on the given path.
|
|
||||||
fn dimensions(&self, handle: &Handle) -> (u32, u32);
|
|
||||||
|
|
||||||
/// Draws an [`Image`].
|
|
||||||
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
|
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: image::Renderer,
|
||||||
{
|
{
|
||||||
fn from(image: Image) -> Element<'a, Message, Renderer> {
|
fn from(image: Image) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(image)
|
Element::new(image)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::event::{self, Event};
|
||||||
use crate::image;
|
use crate::image;
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
||||||
Widget,
|
Widget,
|
||||||
|
|
@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {
|
||||||
/// will be respected.
|
/// will be respected.
|
||||||
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
|
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer + image::Renderer,
|
Renderer: image::Renderer,
|
||||||
{
|
{
|
||||||
let (width, height) = renderer.dimensions(&self.handle);
|
let (width, height) = renderer.dimensions(&self.handle);
|
||||||
|
|
||||||
|
|
@ -115,7 +116,7 @@ impl<'a> Viewer<'a> {
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer + image::Renderer,
|
Renderer: image::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -280,14 +281,32 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
_defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
|
if self.state.is_cursor_grabbed() {
|
||||||
|
mouse::Interaction::Grabbing
|
||||||
|
} else if is_mouse_over {
|
||||||
|
mouse::Interaction::Grab
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let image_size = self.image_size(renderer, bounds.size());
|
let image_size = self.image_size(renderer, bounds.size());
|
||||||
|
|
@ -301,17 +320,19 @@ where
|
||||||
image_top_left - self.state.offset(bounds, image_size)
|
image_top_left - self.state.offset(bounds, image_size)
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
renderer.with_layer(bounds, |renderer| {
|
||||||
|
renderer.with_translation(translation, |renderer| {
|
||||||
self::Renderer::draw(
|
image::Renderer::draw(
|
||||||
renderer,
|
renderer,
|
||||||
&self.state,
|
|
||||||
bounds,
|
|
||||||
image_size,
|
|
||||||
translation,
|
|
||||||
self.handle.clone(),
|
self.handle.clone(),
|
||||||
is_mouse_over,
|
Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
|
..Rectangle::with_size(image_size)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -373,38 +394,9 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of an [`Viewer`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Viewer`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer + Sized {
|
|
||||||
/// Draws the [`Viewer`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the [`State`] of the [`Viewer`]
|
|
||||||
/// - the bounds of the [`Viewer`] widget
|
|
||||||
/// - the [`Size`] of the scaled [`Viewer`] image
|
|
||||||
/// - the translation of the clipped image
|
|
||||||
/// - the [`Handle`] to the underlying image
|
|
||||||
/// - whether the mouse is over the [`Viewer`] or not
|
|
||||||
///
|
|
||||||
/// [`Handle`]: image::Handle
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
state: &State,
|
|
||||||
bounds: Rectangle,
|
|
||||||
image_size: Size,
|
|
||||||
translation: Vector,
|
|
||||||
handle: image::Handle,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
|
impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer + image::Renderer,
|
Renderer: 'a + image::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
|
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,19 @@ pub use split::Split;
|
||||||
pub use state::State;
|
pub use state::State;
|
||||||
pub use title_bar::TitleBar;
|
pub use title_bar::TitleBar;
|
||||||
|
|
||||||
use crate::container;
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::row;
|
use crate::renderer;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||||
Widget,
|
Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||||
|
|
||||||
/// A collection of panes distributed using either vertical or horizontal splits
|
/// A collection of panes distributed using either vertical or horizontal splits
|
||||||
/// to completely fill the space available.
|
/// to completely fill the space available.
|
||||||
///
|
///
|
||||||
|
|
@ -61,10 +62,10 @@ use crate::{
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::{pane_grid, Text};
|
/// # use iced_native::widget::{pane_grid, Text};
|
||||||
/// #
|
/// #
|
||||||
/// # type PaneGrid<'a, Message> =
|
/// # type PaneGrid<'a, Message> =
|
||||||
/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>;
|
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// enum PaneState {
|
/// enum PaneState {
|
||||||
/// SomePane,
|
/// SomePane,
|
||||||
|
|
@ -89,7 +90,7 @@ use crate::{
|
||||||
/// .on_resize(10, Message::PaneResized);
|
/// .on_resize(10, Message::PaneResized);
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
|
pub struct PaneGrid<'a, Message, Renderer> {
|
||||||
state: &'a mut state::Internal,
|
state: &'a mut state::Internal,
|
||||||
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
|
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
|
||||||
width: Length,
|
width: Length,
|
||||||
|
|
@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
|
||||||
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
||||||
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
||||||
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
|
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
|
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
|
||||||
///
|
///
|
||||||
|
|
@ -130,7 +131,7 @@ where
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_drag: None,
|
on_drag: None,
|
||||||
on_resize: None,
|
on_resize: None,
|
||||||
style: Default::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,18 +191,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`PaneGrid`].
|
/// Sets the style of the [`PaneGrid`].
|
||||||
pub fn style(
|
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
|
||||||
mut self,
|
self.style_sheet = style.into();
|
||||||
style: impl Into<<Renderer as self::Renderer>::Style>,
|
|
||||||
) -> Self {
|
|
||||||
self.style = style.into();
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
|
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn click_pane(
|
fn click_pane(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -318,7 +316,7 @@ pub struct ResizeEvent {
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for PaneGrid<'a, Message, Renderer>
|
for PaneGrid<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer + container::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -473,14 +471,43 @@ where
|
||||||
.fold(event_status, event::Status::merge)
|
.fold(event_status, event::Status::merge)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
|
if self.state.picked_pane().is_some() {
|
||||||
|
return mouse::Interaction::Grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_, axis)) = self.state.picked_split() {
|
||||||
|
return match axis {
|
||||||
|
Axis::Horizontal => mouse::Interaction::ResizingHorizontally,
|
||||||
|
Axis::Vertical => mouse::Interaction::ResizingVertically,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.elements
|
||||||
|
.iter()
|
||||||
|
.zip(layout.children())
|
||||||
|
.map(|((_pane, content), layout)| {
|
||||||
|
content.mouse_interaction(layout, cursor_position, viewport)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let picked_pane = self.state.picked_pane();
|
||||||
|
|
||||||
let picked_split = self
|
let picked_split = self
|
||||||
.state
|
.state
|
||||||
.picked_split()
|
.picked_split()
|
||||||
|
|
@ -529,17 +556,89 @@ where
|
||||||
None => None,
|
None => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
self::Renderer::draw(
|
let pane_cursor_position = if picked_pane.is_some() {
|
||||||
|
// TODO: Remove once cursor availability is encoded in the type
|
||||||
|
// system
|
||||||
|
Point::new(-1.0, -1.0)
|
||||||
|
} else {
|
||||||
|
cursor_position
|
||||||
|
};
|
||||||
|
|
||||||
|
for ((id, pane), layout) in self.elements.iter().zip(layout.children())
|
||||||
|
{
|
||||||
|
match picked_pane {
|
||||||
|
Some((dragging, origin)) if *id == dragging => {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
renderer.with_translation(
|
||||||
|
cursor_position
|
||||||
|
- Point::new(
|
||||||
|
bounds.x + origin.x,
|
||||||
|
bounds.y + origin.y,
|
||||||
|
),
|
||||||
|
|renderer| {
|
||||||
|
renderer.with_layer(bounds, |renderer| {
|
||||||
|
pane.draw(
|
||||||
renderer,
|
renderer,
|
||||||
defaults,
|
style,
|
||||||
&self.elements,
|
|
||||||
self.state.picked_pane(),
|
|
||||||
picked_split,
|
|
||||||
layout,
|
layout,
|
||||||
&self.style,
|
pane_cursor_position,
|
||||||
cursor_position,
|
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
pane.draw(
|
||||||
|
renderer,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
pane_cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((axis, split_region, is_picked)) = picked_split {
|
||||||
|
let highlight = if is_picked {
|
||||||
|
self.style_sheet.picked_split()
|
||||||
|
} else {
|
||||||
|
self.style_sheet.hovered_split()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(highlight) = highlight {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: match axis {
|
||||||
|
Axis::Horizontal => Rectangle {
|
||||||
|
x: split_region.x,
|
||||||
|
y: (split_region.y
|
||||||
|
+ (split_region.height - highlight.width)
|
||||||
|
/ 2.0)
|
||||||
|
.round(),
|
||||||
|
width: split_region.width,
|
||||||
|
height: highlight.width,
|
||||||
|
},
|
||||||
|
Axis::Vertical => Rectangle {
|
||||||
|
x: (split_region.x
|
||||||
|
+ (split_region.width - highlight.width)
|
||||||
|
/ 2.0)
|
||||||
|
.round(),
|
||||||
|
y: split_region.y,
|
||||||
|
width: highlight.width,
|
||||||
|
height: split_region.height,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border_radius: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
highlight.color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -569,78 +668,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`PaneGrid`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`PaneGrid`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer + container::Renderer + Sized {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Draws a [`PaneGrid`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the elements of the [`PaneGrid`]
|
|
||||||
/// - the [`Pane`] that is currently being dragged
|
|
||||||
/// - the [`Axis`] that is currently being resized
|
|
||||||
/// - the [`Layout`] of the [`PaneGrid`] and its elements
|
|
||||||
/// - the cursor position
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
content: &[(Pane, Content<'_, Message, Self>)],
|
|
||||||
dragging: Option<(Pane, Point)>,
|
|
||||||
resizing: Option<(Axis, Rectangle, bool)>,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
style: &<Self as self::Renderer>::Style,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output;
|
|
||||||
|
|
||||||
/// Draws a [`Pane`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the [`TitleBar`] of the [`Pane`], if any
|
|
||||||
/// - the [`Content`] of the [`Pane`]
|
|
||||||
/// - the [`Layout`] of the [`Pane`] and its elements
|
|
||||||
/// - the cursor position
|
|
||||||
fn draw_pane<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
style: &<Self as container::Renderer>::Style,
|
|
||||||
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
|
|
||||||
body: (&Element<'_, Message, Self>, Layout<'_>),
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output;
|
|
||||||
|
|
||||||
/// Draws a [`TitleBar`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the bounds, style of the [`TitleBar`]
|
|
||||||
/// - the style of the [`TitleBar`]
|
|
||||||
/// - the content of the [`TitleBar`] with its layout
|
|
||||||
/// - the controls of the [`TitleBar`] with their [`Layout`], if any
|
|
||||||
/// - the cursor position
|
|
||||||
fn draw_title_bar<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
style: &<Self as container::Renderer>::Style,
|
|
||||||
content: (&Element<'_, Message, Self>, Layout<'_>),
|
|
||||||
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer + row::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::pane_grid::Axis;
|
use crate::widget::pane_grid::Axis;
|
||||||
|
|
||||||
/// The arrangement of a [`PaneGrid`].
|
/// The arrangement of a [`PaneGrid`].
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,32 @@
|
||||||
use crate::container;
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::pane_grid::{self, TitleBar};
|
use crate::renderer;
|
||||||
|
use crate::widget::container;
|
||||||
|
use crate::widget::pane_grid::TitleBar;
|
||||||
use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
|
use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
|
||||||
|
|
||||||
/// The content of a [`Pane`].
|
/// The content of a [`Pane`].
|
||||||
///
|
///
|
||||||
/// [`Pane`]: crate::widget::pane_grid::Pane
|
/// [`Pane`]: crate::widget::pane_grid::Pane
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
|
pub struct Content<'a, Message, Renderer> {
|
||||||
title_bar: Option<TitleBar<'a, Message, Renderer>>,
|
title_bar: Option<TitleBar<'a, Message, Renderer>>,
|
||||||
body: Element<'a, Message, Renderer>,
|
body: Element<'a, Message, Renderer>,
|
||||||
style: <Renderer as container::Renderer>::Style,
|
style_sheet: Box<dyn container::StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: pane_grid::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a new [`Content`] with the provided body.
|
/// Creates a new [`Content`] with the provided body.
|
||||||
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title_bar: None,
|
title_bar: None,
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
style: Default::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,16 +42,16 @@ where
|
||||||
/// Sets the style of the [`Content`].
|
/// Sets the style of the [`Content`].
|
||||||
pub fn style(
|
pub fn style(
|
||||||
mut self,
|
mut self,
|
||||||
style: impl Into<<Renderer as container::Renderer>::Style>,
|
style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.style = style.into();
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: pane_grid::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
|
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
|
||||||
///
|
///
|
||||||
|
|
@ -57,35 +59,45 @@ where
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
{
|
||||||
|
let style = self.style_sheet.style();
|
||||||
|
|
||||||
|
container::draw_background(renderer, &style, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(title_bar) = &self.title_bar {
|
if let Some(title_bar) = &self.title_bar {
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
let title_bar_layout = children.next().unwrap();
|
let title_bar_layout = children.next().unwrap();
|
||||||
let body_layout = children.next().unwrap();
|
let body_layout = children.next().unwrap();
|
||||||
|
|
||||||
renderer.draw_pane(
|
let show_controls = bounds.contains(cursor_position);
|
||||||
defaults,
|
|
||||||
layout.bounds(),
|
title_bar.draw(
|
||||||
&self.style,
|
renderer,
|
||||||
Some((title_bar, title_bar_layout)),
|
style,
|
||||||
(&self.body, body_layout),
|
title_bar_layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
show_controls,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.body.draw(
|
||||||
|
renderer,
|
||||||
|
style,
|
||||||
|
body_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
renderer.draw_pane(
|
self.body
|
||||||
defaults,
|
.draw(renderer, style, layout, cursor_position, viewport);
|
||||||
layout.bounds(),
|
|
||||||
&self.style,
|
|
||||||
None,
|
|
||||||
(&self.body, layout),
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,6 +198,40 @@ where
|
||||||
event_status.merge(body_status)
|
event_status.merge(body_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
let (body_layout, title_bar_interaction) =
|
||||||
|
if let Some(title_bar) = &self.title_bar {
|
||||||
|
let mut children = layout.children();
|
||||||
|
let title_bar_layout = children.next().unwrap();
|
||||||
|
|
||||||
|
let is_over_pick_area = title_bar
|
||||||
|
.is_over_pick_area(title_bar_layout, cursor_position);
|
||||||
|
|
||||||
|
if is_over_pick_area {
|
||||||
|
return mouse::Interaction::Grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mouse_interaction = title_bar.mouse_interaction(
|
||||||
|
title_bar_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
(children.next().unwrap(), mouse_interaction)
|
||||||
|
} else {
|
||||||
|
(layout, mouse::Interaction::default())
|
||||||
|
};
|
||||||
|
|
||||||
|
self.body
|
||||||
|
.mouse_interaction(body_layout, cursor_position, viewport)
|
||||||
|
.max(title_bar_interaction)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
|
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
|
||||||
if let Some(title_bar) = &self.title_bar {
|
if let Some(title_bar) = &self.title_bar {
|
||||||
title_bar.hash_layout(state);
|
title_bar.hash_layout(state);
|
||||||
|
|
@ -215,7 +261,7 @@ where
|
||||||
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
|
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
T: Into<Element<'a, Message, Renderer>>,
|
T: Into<Element<'a, Message, Renderer>>,
|
||||||
Renderer: pane_grid::Renderer + container::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn from(element: T) -> Self {
|
fn from(element: T) -> Self {
|
||||||
Self::new(element)
|
Self::new(element)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
use crate::{
|
use crate::widget::pane_grid::{Axis, Pane, Split};
|
||||||
pane_grid::{Axis, Pane, Split},
|
use crate::{Rectangle, Size};
|
||||||
Rectangle, Size,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::widget::pane_grid::{
|
||||||
pane_grid::{Axis, Configuration, Direction, Node, Pane, Split},
|
Axis, Configuration, Direction, Node, Pane, Split,
|
||||||
Hasher, Point, Rectangle, Size,
|
|
||||||
};
|
};
|
||||||
|
use crate::{Hasher, Point, Rectangle, Size};
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::container;
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::pane_grid;
|
use crate::renderer;
|
||||||
|
use crate::widget::container;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
|
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
|
||||||
};
|
};
|
||||||
|
|
@ -11,17 +12,17 @@ use crate::{
|
||||||
///
|
///
|
||||||
/// [`Pane`]: crate::widget::pane_grid::Pane
|
/// [`Pane`]: crate::widget::pane_grid::Pane
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
|
pub struct TitleBar<'a, Message, Renderer> {
|
||||||
content: Element<'a, Message, Renderer>,
|
content: Element<'a, Message, Renderer>,
|
||||||
controls: Option<Element<'a, Message, Renderer>>,
|
controls: Option<Element<'a, Message, Renderer>>,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
always_show_controls: bool,
|
always_show_controls: bool,
|
||||||
style: <Renderer as container::Renderer>::Style,
|
style_sheet: Box<dyn container::StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
|
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: pane_grid::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a new [`TitleBar`] with the given content.
|
/// Creates a new [`TitleBar`] with the given content.
|
||||||
pub fn new<E>(content: E) -> Self
|
pub fn new<E>(content: E) -> Self
|
||||||
|
|
@ -33,7 +34,7 @@ where
|
||||||
controls: None,
|
controls: None,
|
||||||
padding: Padding::ZERO,
|
padding: Padding::ZERO,
|
||||||
always_show_controls: false,
|
always_show_controls: false,
|
||||||
style: Default::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,9 +56,9 @@ where
|
||||||
/// Sets the style of the [`TitleBar`].
|
/// Sets the style of the [`TitleBar`].
|
||||||
pub fn style(
|
pub fn style(
|
||||||
mut self,
|
mut self,
|
||||||
style: impl Into<<Renderer as container::Renderer>::Style>,
|
style: impl Into<Box<dyn container::StyleSheet + 'a>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.style = style.into();
|
self.style_sheet = style.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +78,7 @@ where
|
||||||
|
|
||||||
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
|
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: pane_grid::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
|
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
|
||||||
///
|
///
|
||||||
|
|
@ -85,39 +86,47 @@ where
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
inherited_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
show_controls: bool,
|
show_controls: bool,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let style = self.style_sheet.style();
|
||||||
|
let inherited_style = renderer::Style {
|
||||||
|
text_color: style.text_color.unwrap_or(inherited_style.text_color),
|
||||||
|
};
|
||||||
|
|
||||||
|
container::draw_background(renderer, &style, bounds);
|
||||||
|
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
let padded = children.next().unwrap();
|
let padded = children.next().unwrap();
|
||||||
|
|
||||||
let mut children = padded.children();
|
let mut children = padded.children();
|
||||||
let title_layout = children.next().unwrap();
|
let title_layout = children.next().unwrap();
|
||||||
|
|
||||||
let controls = if let Some(controls) = &self.controls {
|
self.content.draw(
|
||||||
|
renderer,
|
||||||
|
&inherited_style,
|
||||||
|
title_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(controls) = &self.controls {
|
||||||
let controls_layout = children.next().unwrap();
|
let controls_layout = children.next().unwrap();
|
||||||
|
|
||||||
if show_controls || self.always_show_controls {
|
if show_controls || self.always_show_controls {
|
||||||
Some((controls, controls_layout))
|
controls.draw(
|
||||||
} else {
|
renderer,
|
||||||
None
|
&inherited_style,
|
||||||
}
|
controls_layout,
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
renderer.draw_title_bar(
|
|
||||||
defaults,
|
|
||||||
layout.bounds(),
|
|
||||||
&self.style,
|
|
||||||
(&self.content, title_layout),
|
|
||||||
controls,
|
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the mouse cursor is over the pick area of the
|
/// Returns whether the mouse cursor is over the pick area of the
|
||||||
|
|
@ -244,6 +253,35 @@ where
|
||||||
control_status.merge(title_status)
|
control_status.merge(title_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
let mut children = layout.children();
|
||||||
|
let padded = children.next().unwrap();
|
||||||
|
|
||||||
|
let mut children = padded.children();
|
||||||
|
let title_layout = children.next().unwrap();
|
||||||
|
|
||||||
|
let title_interaction = self.content.mouse_interaction(
|
||||||
|
title_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(controls) = &self.controls {
|
||||||
|
let controls_layout = children.next().unwrap();
|
||||||
|
|
||||||
|
controls
|
||||||
|
.mouse_interaction(controls_layout, cursor_position, viewport)
|
||||||
|
.max(title_interaction)
|
||||||
|
} else {
|
||||||
|
title_interaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn overlay(
|
pub(crate) fn overlay(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
//! Display a dropdown list of selectable values.
|
//! Display a dropdown list of selectable values.
|
||||||
|
use crate::alignment;
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::keyboard;
|
use crate::keyboard;
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::overlay::menu::{self, Menu};
|
use crate::overlay::menu::{self, Menu};
|
||||||
use crate::scrollable;
|
use crate::renderer;
|
||||||
use crate::text;
|
use crate::text::{self, Text};
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
||||||
|
|
@ -14,9 +15,11 @@ use crate::{
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub use iced_style::pick_list::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A widget for selecting a single value from a list of options.
|
/// A widget for selecting a single value from a list of options.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct PickList<'a, T, Message, Renderer: self::Renderer>
|
pub struct PickList<'a, T, Message, Renderer: text::Renderer>
|
||||||
where
|
where
|
||||||
[T]: ToOwned<Owned = Vec<T>>,
|
[T]: ToOwned<Owned = Vec<T>>,
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +36,7 @@ where
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The local state of a [`PickList`].
|
/// The local state of a [`PickList`].
|
||||||
|
|
@ -58,12 +61,15 @@ impl<T> Default for State<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: 'a, Message, Renderer: self::Renderer>
|
impl<'a, T: 'a, Message, Renderer: text::Renderer>
|
||||||
PickList<'a, T, Message, Renderer>
|
PickList<'a, T, Message, Renderer>
|
||||||
where
|
where
|
||||||
T: ToString + Eq,
|
T: ToString + Eq,
|
||||||
[T]: ToOwned<Owned = Vec<T>>,
|
[T]: ToOwned<Owned = Vec<T>>,
|
||||||
{
|
{
|
||||||
|
/// The default padding of a [`PickList`].
|
||||||
|
pub const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||||
|
|
||||||
/// Creates a new [`PickList`] with the given [`State`], a list of options,
|
/// Creates a new [`PickList`] with the given [`State`], a list of options,
|
||||||
/// the current selected value, and the message to produce when an option is
|
/// the current selected value, and the message to produce when an option is
|
||||||
/// selected.
|
/// selected.
|
||||||
|
|
@ -93,9 +99,9 @@ where
|
||||||
selected,
|
selected,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
text_size: None,
|
text_size: None,
|
||||||
padding: Renderer::DEFAULT_PADDING,
|
padding: Self::DEFAULT_PADDING,
|
||||||
font: Default::default(),
|
font: Default::default(),
|
||||||
style: Default::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,9 +138,9 @@ where
|
||||||
/// Sets the style of the [`PickList`].
|
/// Sets the style of the [`PickList`].
|
||||||
pub fn style(
|
pub fn style(
|
||||||
mut self,
|
mut self,
|
||||||
style: impl Into<<Renderer as self::Renderer>::Style>,
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.style = style.into();
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +151,7 @@ where
|
||||||
T: Clone + ToString + Eq,
|
T: Clone + ToString + Eq,
|
||||||
[T]: ToOwned<Owned = Vec<T>>,
|
[T]: ToOwned<Owned = Vec<T>>,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
Renderer: self::Renderer + scrollable::Renderer + 'a,
|
Renderer: text::Renderer + 'a,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -320,25 +326,90 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
_defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
self::Renderer::draw(
|
let bounds = layout.bounds();
|
||||||
renderer,
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
layout.bounds(),
|
|
||||||
cursor_position,
|
if is_mouse_over {
|
||||||
self.selected.as_ref().map(ToString::to_string),
|
mouse::Interaction::Pointer
|
||||||
self.placeholder.as_ref().map(String::as_str),
|
} else {
|
||||||
self.padding,
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
let is_selected = self.selected.is_some();
|
||||||
|
|
||||||
|
let style = if is_mouse_over {
|
||||||
|
self.style_sheet.hovered()
|
||||||
|
} else {
|
||||||
|
self.style_sheet.active()
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_color: style.border_color,
|
||||||
|
border_width: style.border_width,
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
},
|
||||||
|
style.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_text(Text {
|
||||||
|
content: &Renderer::ARROW_DOWN_ICON.to_string(),
|
||||||
|
font: Renderer::ICON_FONT,
|
||||||
|
size: bounds.height * style.icon_size,
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + bounds.width
|
||||||
|
- f32::from(self.padding.horizontal()),
|
||||||
|
y: bounds.center_y(),
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
color: style.text_color,
|
||||||
|
horizontal_alignment: alignment::Horizontal::Right,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(label) = self
|
||||||
|
.selected
|
||||||
|
.as_ref()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.as_ref()
|
||||||
|
.or_else(|| self.placeholder.as_ref())
|
||||||
|
{
|
||||||
|
renderer.fill_text(Text {
|
||||||
|
content: label,
|
||||||
|
size: f32::from(
|
||||||
self.text_size.unwrap_or(renderer.default_size()),
|
self.text_size.unwrap_or(renderer.default_size()),
|
||||||
self.font,
|
),
|
||||||
&self.style,
|
font: self.font,
|
||||||
)
|
color: is_selected
|
||||||
|
.then(|| style.text_color)
|
||||||
|
.unwrap_or(style.placeholder_color),
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + f32::from(self.padding.left),
|
||||||
|
y: bounds.center_y(),
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay(
|
fn overlay(
|
||||||
|
|
@ -357,7 +428,7 @@ where
|
||||||
.width(bounds.width.round() as u16)
|
.width(bounds.width.round() as u16)
|
||||||
.padding(self.padding)
|
.padding(self.padding)
|
||||||
.font(self.font)
|
.font(self.font)
|
||||||
.style(Renderer::menu_style(&self.style));
|
.style(self.style_sheet.menu());
|
||||||
|
|
||||||
if let Some(text_size) = self.text_size {
|
if let Some(text_size) = self.text_size {
|
||||||
menu = menu.text_size(text_size);
|
menu = menu.text_size(text_size);
|
||||||
|
|
@ -370,44 +441,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`PickList`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`PickList`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: text::Renderer + menu::Renderer {
|
|
||||||
/// The default padding of a [`PickList`].
|
|
||||||
const DEFAULT_PADDING: Padding;
|
|
||||||
|
|
||||||
/// The [`PickList`] style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Returns the style of the [`Menu`] of the [`PickList`].
|
|
||||||
fn menu_style(
|
|
||||||
style: &<Self as Renderer>::Style,
|
|
||||||
) -> <Self as menu::Renderer>::Style;
|
|
||||||
|
|
||||||
/// Draws a [`PickList`].
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
selected: Option<String>,
|
|
||||||
placeholder: Option<&str>,
|
|
||||||
padding: Padding,
|
|
||||||
text_size: u16,
|
|
||||||
font: Self::Font,
|
|
||||||
style: &<Self as Renderer>::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
||||||
for PickList<'a, T, Message, Renderer>
|
for PickList<'a, T, Message, Renderer>
|
||||||
where
|
where
|
||||||
T: Clone + ToString + Eq,
|
T: Clone + ToString + Eq,
|
||||||
[T]: ToOwned<Owned = Vec<T>>,
|
[T]: ToOwned<Owned = Vec<T>>,
|
||||||
Renderer: self::Renderer + 'a,
|
Renderer: text::Renderer + 'a,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
fn into(self) -> Element<'a, Message, Renderer> {
|
fn into(self) -> Element<'a, Message, Renderer> {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
//! Provide progress feedback to your users.
|
//! Provide progress feedback to your users.
|
||||||
|
use crate::layout;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{
|
use crate::{
|
||||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{hash::Hash, ops::RangeInclusive};
|
use std::{hash::Hash, ops::RangeInclusive};
|
||||||
|
|
||||||
|
pub use iced_style::progress_bar::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A bar that displays progress.
|
/// A bar that displays progress.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::renderer::Null;
|
/// # use iced_native::widget::ProgressBar;
|
||||||
/// #
|
|
||||||
/// # pub type ProgressBar = iced_native::ProgressBar<Null>;
|
|
||||||
/// let value = 50.0;
|
/// let value = 50.0;
|
||||||
///
|
///
|
||||||
/// ProgressBar::new(0.0..=100.0, value);
|
/// ProgressBar::new(0.0..=100.0, value);
|
||||||
|
|
@ -19,15 +21,18 @@ use std::{hash::Hash, ops::RangeInclusive};
|
||||||
///
|
///
|
||||||
/// 
|
/// 
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct ProgressBar<Renderer: self::Renderer> {
|
pub struct ProgressBar<'a> {
|
||||||
range: RangeInclusive<f32>,
|
range: RangeInclusive<f32>,
|
||||||
value: f32,
|
value: f32,
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Option<Length>,
|
height: Option<Length>,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Renderer: self::Renderer> ProgressBar<Renderer> {
|
impl<'a> ProgressBar<'a> {
|
||||||
|
/// The default height of a [`ProgressBar`].
|
||||||
|
pub const DEFAULT_HEIGHT: u16 = 30;
|
||||||
|
|
||||||
/// Creates a new [`ProgressBar`].
|
/// Creates a new [`ProgressBar`].
|
||||||
///
|
///
|
||||||
/// It expects:
|
/// It expects:
|
||||||
|
|
@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
|
||||||
range,
|
range,
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
height: None,
|
height: None,
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,23 +61,25 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`ProgressBar`].
|
/// Sets the style of the [`ProgressBar`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(&self) -> Length {
|
fn height(&self) -> Length {
|
||||||
self.height
|
self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))
|
||||||
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -80,10 +87,9 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let limits = limits.width(self.width).height(
|
let limits = limits
|
||||||
self.height
|
.width(self.width)
|
||||||
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)),
|
.height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));
|
||||||
);
|
|
||||||
|
|
||||||
let size = limits.resolve(Size::ZERO);
|
let size = limits.resolve(Size::ZERO);
|
||||||
|
|
||||||
|
|
@ -93,17 +99,47 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.draw(
|
let bounds = layout.bounds();
|
||||||
layout.bounds(),
|
let (range_start, range_end) = self.range.clone().into_inner();
|
||||||
self.range.clone(),
|
|
||||||
self.value,
|
let active_progress_width = if range_start >= range_end {
|
||||||
&self.style,
|
0.0
|
||||||
)
|
} else {
|
||||||
|
bounds.width * (self.value - range_start)
|
||||||
|
/ (range_end - range_start)
|
||||||
|
};
|
||||||
|
|
||||||
|
let style = self.style_sheet.style();
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle { ..bounds },
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
style.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
if active_progress_width > 0.0 {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
width: active_progress_width,
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
style.bar,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -115,45 +151,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`ProgressBar`].
|
impl<'a, Message, Renderer> From<ProgressBar<'a>>
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`ProgressBar`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// The default height of a [`ProgressBar`].
|
|
||||||
const DEFAULT_HEIGHT: u16;
|
|
||||||
|
|
||||||
/// Draws a [`ProgressBar`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// * the bounds of the [`ProgressBar`]
|
|
||||||
/// * the range of values of the [`ProgressBar`]
|
|
||||||
/// * the current value of the [`ProgressBar`]
|
|
||||||
/// * maybe a specific background of the [`ProgressBar`]
|
|
||||||
/// * maybe a specific active color of the [`ProgressBar`]
|
|
||||||
fn draw(
|
|
||||||
&self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
range: RangeInclusive<f32>,
|
|
||||||
value: f32,
|
|
||||||
style: &Self::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {
|
||||||
progress_bar: ProgressBar<Renderer>,
|
|
||||||
) -> Element<'a, Message, Renderer> {
|
|
||||||
Element::new(progress_bar)
|
Element::new(progress_bar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,27 @@
|
||||||
//! Create choices using radio buttons.
|
//! Create choices using radio buttons.
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::alignment::{self, Alignment};
|
use crate::alignment;
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::row;
|
use crate::renderer;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
|
use crate::widget::{self, Row, Text};
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
|
Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
|
||||||
Text, Widget,
|
Rectangle, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use iced_style::radio::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A circular button representing a choice.
|
/// A circular button representing a choice.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # type Radio<Message> =
|
/// # type Radio<'a, Message> =
|
||||||
/// # iced_native::Radio<Message, iced_native::renderer::Null>;
|
/// # iced_native::widget::Radio<'a, Message, iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
/// pub enum Choice {
|
/// pub enum Choice {
|
||||||
|
|
@ -40,7 +43,7 @@ use crate::{
|
||||||
///
|
///
|
||||||
/// 
|
/// 
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
|
pub struct Radio<'a, Message, Renderer: text::Renderer> {
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
on_click: Message,
|
on_click: Message,
|
||||||
label: String,
|
label: String,
|
||||||
|
|
@ -50,14 +53,19 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
text_color: Option<Color>,
|
text_color: Option<Color>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Renderer: self::Renderer + text::Renderer>
|
impl<'a, Message, Renderer: text::Renderer> Radio<'a, Message, Renderer>
|
||||||
Radio<Message, Renderer>
|
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
{
|
{
|
||||||
|
/// The default size of a [`Radio`] button.
|
||||||
|
pub const DEFAULT_SIZE: u16 = 28;
|
||||||
|
|
||||||
|
/// The default spacing of a [`Radio`] button.
|
||||||
|
pub const DEFAULT_SPACING: u16 = 15;
|
||||||
|
|
||||||
/// Creates a new [`Radio`] button.
|
/// Creates a new [`Radio`] button.
|
||||||
///
|
///
|
||||||
/// It expects:
|
/// It expects:
|
||||||
|
|
@ -81,12 +89,12 @@ where
|
||||||
on_click: f(value),
|
on_click: f(value),
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
|
size: Self::DEFAULT_SIZE,
|
||||||
spacing: Renderer::DEFAULT_SPACING, //15
|
spacing: Self::DEFAULT_SPACING, //15
|
||||||
text_size: None,
|
text_size: None,
|
||||||
text_color: None,
|
text_color: None,
|
||||||
font: Default::default(),
|
font: Default::default(),
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,16 +135,20 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Radio`] button.
|
/// Sets the style of the [`Radio`] button.
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
|
for Radio<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -192,43 +204,88 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
|
if layout.bounds().contains(cursor_position) {
|
||||||
|
mouse::Interaction::Pointer
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
|
|
||||||
let radio_layout = children.next().unwrap();
|
{
|
||||||
let label_layout = children.next().unwrap();
|
let layout = children.next().unwrap();
|
||||||
let radio_bounds = radio_layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let label = text::Renderer::draw(
|
let size = bounds.width;
|
||||||
|
let dot_size = size / 2.0;
|
||||||
|
|
||||||
|
let style = if is_mouse_over {
|
||||||
|
self.style_sheet.hovered()
|
||||||
|
} else {
|
||||||
|
self.style_sheet.active()
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_radius: size / 2.0,
|
||||||
|
border_width: style.border_width,
|
||||||
|
border_color: style.border_color,
|
||||||
|
},
|
||||||
|
style.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.is_selected {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + dot_size / 2.0,
|
||||||
|
y: bounds.y + dot_size / 2.0,
|
||||||
|
width: bounds.width - dot_size,
|
||||||
|
height: bounds.height - dot_size,
|
||||||
|
},
|
||||||
|
border_radius: dot_size / 2.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
style.dot_color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let label_layout = children.next().unwrap();
|
||||||
|
|
||||||
|
widget::text::draw(
|
||||||
renderer,
|
renderer,
|
||||||
defaults,
|
style,
|
||||||
label_layout.bounds(),
|
label_layout,
|
||||||
&self.label,
|
&self.label,
|
||||||
self.text_size.unwrap_or(renderer.default_size()),
|
|
||||||
self.font,
|
self.font,
|
||||||
|
self.text_size,
|
||||||
self.text_color,
|
self.text_color,
|
||||||
alignment::Horizontal::Left,
|
alignment::Horizontal::Left,
|
||||||
alignment::Vertical::Center,
|
alignment::Vertical::Center,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
|
||||||
|
|
||||||
self::Renderer::draw(
|
|
||||||
renderer,
|
|
||||||
radio_bounds,
|
|
||||||
self.is_selected,
|
|
||||||
is_mouse_over,
|
|
||||||
label,
|
|
||||||
&self.style,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -239,46 +296,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Radio`] button.
|
impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Radio`] button in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// The default size of a [`Radio`] button.
|
|
||||||
const DEFAULT_SIZE: u16;
|
|
||||||
|
|
||||||
/// The default spacing of a [`Radio`] button.
|
|
||||||
const DEFAULT_SPACING: u16;
|
|
||||||
|
|
||||||
/// Draws a [`Radio`] button.
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// * the bounds of the [`Radio`]
|
|
||||||
/// * whether the [`Radio`] is selected or not
|
|
||||||
/// * whether the mouse is over the [`Radio`] or not
|
|
||||||
/// * the drawn label of the [`Radio`]
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
is_selected: bool,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
label: Self::Output,
|
|
||||||
style: &Self::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a + Clone,
|
Message: 'a + Clone,
|
||||||
Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
|
Renderer: 'a + text::Renderer,
|
||||||
{
|
{
|
||||||
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
|
fn from(
|
||||||
|
radio: Radio<'a, Message, Renderer>,
|
||||||
|
) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(radio)
|
Element::new(radio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
//! Distribute content horizontally.
|
//! Distribute content horizontally.
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::{
|
use crate::{
|
||||||
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
|
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
|
||||||
Rectangle, Widget,
|
Rectangle, Widget,
|
||||||
|
|
@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Row<'a, Message, Renderer>
|
for Row<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -161,21 +163,37 @@ where
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
.fold(event::Status::Ignored, event::Status::merge)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
renderer.draw(
|
self.children
|
||||||
defaults,
|
.iter()
|
||||||
&self.children,
|
.zip(layout.children())
|
||||||
|
.map(|(child, layout)| {
|
||||||
|
child.widget.mouse_interaction(
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||||
|
child.draw(renderer, style, layout, cursor_position, viewport);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -207,33 +225,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Row`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Row`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer + Sized {
|
|
||||||
/// Draws a [`Row`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the children of the [`Row`]
|
|
||||||
/// - the [`Layout`] of the [`Row`] and its children
|
|
||||||
/// - the cursor position
|
|
||||||
fn draw<Message>(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
children: &[Element<'_, Message, Self>],
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,31 @@
|
||||||
//! Display a horizontal or vertical rule for dividing content.
|
//! Display a horizontal or vertical rule for dividing content.
|
||||||
|
use crate::layout;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::{
|
||||||
|
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{
|
pub use iced_style::rule::{FillMode, Style, StyleSheet};
|
||||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Display a horizontal or vertical rule for dividing content.
|
/// Display a horizontal or vertical rule for dividing content.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Rule<Renderer: self::Renderer> {
|
pub struct Rule<'a> {
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
style: Renderer::Style,
|
|
||||||
is_horizontal: bool,
|
is_horizontal: bool,
|
||||||
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Renderer: self::Renderer> Rule<Renderer> {
|
impl<'a> Rule<'a> {
|
||||||
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
|
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
|
||||||
pub fn horizontal(spacing: u16) -> Self {
|
pub fn horizontal(spacing: u16) -> Self {
|
||||||
Rule {
|
Rule {
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
height: Length::from(Length::Units(spacing)),
|
height: Length::from(Length::Units(spacing)),
|
||||||
style: Renderer::Style::default(),
|
|
||||||
is_horizontal: true,
|
is_horizontal: true,
|
||||||
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
|
||||||
Rule {
|
Rule {
|
||||||
width: Length::from(Length::Units(spacing)),
|
width: Length::from(Length::Units(spacing)),
|
||||||
height: Length::Fill,
|
height: Length::Fill,
|
||||||
style: Renderer::Style::default(),
|
|
||||||
is_horizontal: false,
|
is_horizontal: false,
|
||||||
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Rule`].
|
/// Sets the style of the [`Rule`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -68,12 +74,53 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
|
let bounds = layout.bounds();
|
||||||
|
let style = self.style_sheet.style();
|
||||||
|
|
||||||
|
let bounds = if self.is_horizontal {
|
||||||
|
let line_y = (bounds.y + (bounds.height / 2.0)
|
||||||
|
- (style.width as f32 / 2.0))
|
||||||
|
.round();
|
||||||
|
|
||||||
|
let (offset, line_width) = style.fill_mode.fill(bounds.width);
|
||||||
|
let line_x = bounds.x + offset;
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: line_x,
|
||||||
|
y: line_y,
|
||||||
|
width: line_width,
|
||||||
|
height: style.width as f32,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let line_x = (bounds.x + (bounds.width / 2.0)
|
||||||
|
- (style.width as f32 / 2.0))
|
||||||
|
.round();
|
||||||
|
|
||||||
|
let (offset, line_height) = style.fill_mode.fill(bounds.height);
|
||||||
|
let line_y = bounds.y + offset;
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: line_x,
|
||||||
|
y: line_y,
|
||||||
|
width: style.width as f32,
|
||||||
|
height: line_height,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border_radius: style.radius,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
style.color,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -85,32 +132,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Rule`].
|
impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Draws a [`Rule`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// * the bounds of the [`Rule`]
|
|
||||||
/// * the style of the [`Rule`]
|
|
||||||
/// * whether the [`Rule`] is horizontal (true) or vertical (false)
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
style: &Self::Style,
|
|
||||||
is_horizontal: bool,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Rule<Renderer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
|
fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(rule)
|
Element::new(rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
//! Navigate an endless amount of content with a scrollbar.
|
//! Navigate an endless amount of content with a scrollbar.
|
||||||
use crate::column;
|
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
use crate::renderer;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
|
use crate::widget::Column;
|
||||||
use crate::{
|
use crate::{
|
||||||
Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding,
|
Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
|
||||||
Point, Rectangle, Size, Vector, Widget,
|
Padding, Point, Rectangle, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{f32, hash::Hash, u32};
|
use std::{f32, hash::Hash, u32};
|
||||||
|
|
||||||
|
pub use iced_style::scrollable::StyleSheet;
|
||||||
|
|
||||||
/// A widget that can vertically display an infinite amount of content with a
|
/// A widget that can vertically display an infinite amount of content with a
|
||||||
/// scrollbar.
|
/// scrollbar.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
|
pub struct Scrollable<'a, Message, Renderer> {
|
||||||
state: &'a mut State,
|
state: &'a mut State,
|
||||||
height: Length,
|
height: Length,
|
||||||
max_height: u32,
|
max_height: u32,
|
||||||
|
|
@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
|
||||||
scroller_width: u16,
|
scroller_width: u16,
|
||||||
content: Column<'a, Message, Renderer>,
|
content: Column<'a, Message, Renderer>,
|
||||||
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
|
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
|
||||||
/// Creates a new [`Scrollable`] with the given [`State`].
|
/// Creates a new [`Scrollable`] with the given [`State`].
|
||||||
pub fn new(state: &'a mut State) -> Self {
|
pub fn new(state: &'a mut State) -> Self {
|
||||||
Scrollable {
|
Scrollable {
|
||||||
|
|
@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
||||||
scroller_width: 10,
|
scroller_width: 10,
|
||||||
content: Column::new(),
|
content: Column::new(),
|
||||||
on_scroll: None,
|
on_scroll: None,
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,8 +123,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Scrollable`] .
|
/// Sets the style of the [`Scrollable`] .
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,12 +157,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scrollbar(
|
||||||
|
&self,
|
||||||
|
bounds: Rectangle,
|
||||||
|
content_bounds: Rectangle,
|
||||||
|
) -> Option<Scrollbar> {
|
||||||
|
let offset = self.state.offset(bounds, content_bounds);
|
||||||
|
|
||||||
|
if content_bounds.height > bounds.height {
|
||||||
|
let outer_width = self.scrollbar_width.max(self.scroller_width)
|
||||||
|
+ 2 * self.scrollbar_margin;
|
||||||
|
|
||||||
|
let outer_bounds = Rectangle {
|
||||||
|
x: bounds.x + bounds.width - outer_width as f32,
|
||||||
|
y: bounds.y,
|
||||||
|
width: outer_width as f32,
|
||||||
|
height: bounds.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scrollbar_bounds = Rectangle {
|
||||||
|
x: bounds.x + bounds.width
|
||||||
|
- f32::from(outer_width / 2 + self.scrollbar_width / 2),
|
||||||
|
y: bounds.y,
|
||||||
|
width: self.scrollbar_width as f32,
|
||||||
|
height: bounds.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ratio = bounds.height / content_bounds.height;
|
||||||
|
let scroller_height = bounds.height * ratio;
|
||||||
|
let y_offset = offset as f32 * ratio;
|
||||||
|
|
||||||
|
let scroller_bounds = Rectangle {
|
||||||
|
x: bounds.x + bounds.width
|
||||||
|
- f32::from(outer_width / 2 + self.scroller_width / 2),
|
||||||
|
y: scrollbar_bounds.y + y_offset,
|
||||||
|
width: self.scroller_width as f32,
|
||||||
|
height: scroller_height,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Scrollbar {
|
||||||
|
outer_bounds,
|
||||||
|
bounds: scrollbar_bounds,
|
||||||
|
margin: self.scrollbar_margin,
|
||||||
|
scroller: Scroller {
|
||||||
|
bounds: scroller_bounds,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Scrollable<'a, Message, Renderer>
|
for Scrollable<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
Widget::<Message, Renderer>::width(&self.content)
|
Widget::<Message, Renderer>::width(&self.content)
|
||||||
|
|
@ -202,15 +259,7 @@ where
|
||||||
let content = layout.children().next().unwrap();
|
let content = layout.children().next().unwrap();
|
||||||
let content_bounds = content.bounds();
|
let content_bounds = content.bounds();
|
||||||
|
|
||||||
let offset = self.state.offset(bounds, content_bounds);
|
let scrollbar = self.scrollbar(bounds, content_bounds);
|
||||||
let scrollbar = renderer.scrollbar(
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
offset,
|
|
||||||
self.scrollbar_width,
|
|
||||||
self.scrollbar_margin,
|
|
||||||
self.scroller_width,
|
|
||||||
);
|
|
||||||
let is_mouse_over_scrollbar = scrollbar
|
let is_mouse_over_scrollbar = scrollbar
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
||||||
|
|
@ -374,26 +423,16 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let content_layout = layout.children().next().unwrap();
|
let content_layout = layout.children().next().unwrap();
|
||||||
let content_bounds = content_layout.bounds();
|
let content_bounds = content_layout.bounds();
|
||||||
let offset = self.state.offset(bounds, content_bounds);
|
let scrollbar = self.scrollbar(bounds, content_bounds);
|
||||||
let scrollbar = renderer.scrollbar(
|
|
||||||
bounds,
|
|
||||||
content_bounds,
|
|
||||||
offset,
|
|
||||||
self.scrollbar_width,
|
|
||||||
self.scrollbar_margin,
|
|
||||||
self.scroller_width,
|
|
||||||
);
|
|
||||||
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
let is_mouse_over_scrollbar = scrollbar
|
let is_mouse_over_scrollbar = scrollbar
|
||||||
|
|
@ -401,16 +440,18 @@ where
|
||||||
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
let content = {
|
if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() {
|
||||||
|
mouse::Interaction::Idle
|
||||||
|
} else {
|
||||||
|
let offset = self.state.offset(bounds, content_bounds);
|
||||||
|
|
||||||
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
||||||
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
||||||
} else {
|
} else {
|
||||||
Point::new(cursor_position.x, -1.0)
|
Point::new(cursor_position.x, -1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.content.draw(
|
self.content.mouse_interaction(
|
||||||
renderer,
|
|
||||||
defaults,
|
|
||||||
content_layout,
|
content_layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
&Rectangle {
|
&Rectangle {
|
||||||
|
|
@ -418,20 +459,114 @@ where
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
let content_bounds = content_layout.bounds();
|
||||||
|
let offset = self.state.offset(bounds, content_bounds);
|
||||||
|
let scrollbar = self.scrollbar(bounds, content_bounds);
|
||||||
|
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
let is_mouse_over_scrollbar = scrollbar
|
||||||
|
.as_ref()
|
||||||
|
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
||||||
|
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
||||||
|
} else {
|
||||||
|
Point::new(cursor_position.x, -1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
self::Renderer::draw(
|
if let Some(scrollbar) = scrollbar {
|
||||||
|
renderer.with_layer(bounds, |renderer| {
|
||||||
|
renderer.with_translation(
|
||||||
|
Vector::new(0.0, -(offset as f32)),
|
||||||
|
|renderer| {
|
||||||
|
self.content.draw(
|
||||||
renderer,
|
renderer,
|
||||||
&self.state,
|
style,
|
||||||
bounds,
|
content_layout,
|
||||||
content_layout.bounds(),
|
cursor_position,
|
||||||
is_mouse_over,
|
&Rectangle {
|
||||||
is_mouse_over_scrollbar,
|
y: bounds.y + offset as f32,
|
||||||
scrollbar,
|
..bounds
|
||||||
offset,
|
},
|
||||||
&self.style,
|
);
|
||||||
content,
|
},
|
||||||
)
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let style = if self.state.is_scroller_grabbed() {
|
||||||
|
self.style_sheet.dragging()
|
||||||
|
} else if is_mouse_over_scrollbar {
|
||||||
|
self.style_sheet.hovered()
|
||||||
|
} else {
|
||||||
|
self.style_sheet.active()
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_scrollbar_visible =
|
||||||
|
style.background.is_some() || style.border_width > 0.0;
|
||||||
|
|
||||||
|
renderer.with_layer(
|
||||||
|
Rectangle {
|
||||||
|
width: bounds.width + 2.0,
|
||||||
|
height: bounds.height + 2.0,
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
|renderer| {
|
||||||
|
if is_scrollbar_visible {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: scrollbar.bounds,
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
border_width: style.border_width,
|
||||||
|
border_color: style.border_color,
|
||||||
|
},
|
||||||
|
style.background.unwrap_or(Background::Color(
|
||||||
|
Color::TRANSPARENT,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_mouse_over
|
||||||
|
|| self.state.is_scroller_grabbed()
|
||||||
|
|| is_scrollbar_visible
|
||||||
|
{
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: scrollbar.scroller.bounds,
|
||||||
|
border_radius: style.scroller.border_radius,
|
||||||
|
border_width: style.scroller.border_width,
|
||||||
|
border_color: style.scroller.border_color,
|
||||||
|
},
|
||||||
|
style.scroller.color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.content.draw(
|
||||||
|
renderer,
|
||||||
|
style,
|
||||||
|
content_layout,
|
||||||
|
cursor_position,
|
||||||
|
&Rectangle {
|
||||||
|
y: bounds.y + offset as f32,
|
||||||
|
..bounds
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -577,19 +712,19 @@ impl State {
|
||||||
|
|
||||||
/// The scrollbar of a [`Scrollable`].
|
/// The scrollbar of a [`Scrollable`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scrollbar {
|
struct Scrollbar {
|
||||||
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
|
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
|
||||||
/// [`Scroller`].
|
/// [`Scroller`].
|
||||||
pub outer_bounds: Rectangle,
|
outer_bounds: Rectangle,
|
||||||
|
|
||||||
/// The bounds of the [`Scrollbar`].
|
/// The bounds of the [`Scrollbar`].
|
||||||
pub bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
|
||||||
/// The margin within the [`Scrollbar`].
|
/// The margin within the [`Scrollbar`].
|
||||||
pub margin: u16,
|
margin: u16,
|
||||||
|
|
||||||
/// The bounds of the [`Scroller`].
|
/// The bounds of the [`Scroller`].
|
||||||
pub scroller: Scroller,
|
scroller: Scroller,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scrollbar {
|
impl Scrollbar {
|
||||||
|
|
@ -624,62 +759,15 @@ impl Scrollbar {
|
||||||
|
|
||||||
/// The handle of a [`Scrollbar`].
|
/// The handle of a [`Scrollbar`].
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Scroller {
|
struct Scroller {
|
||||||
/// The bounds of the [`Scroller`].
|
/// The bounds of the [`Scroller`].
|
||||||
pub bounds: Rectangle,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The renderer of a [`Scrollable`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Scrollable`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: column::Renderer + Sized {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Returns the [`Scrollbar`] given the bounds and content bounds of a
|
|
||||||
/// [`Scrollable`].
|
|
||||||
fn scrollbar(
|
|
||||||
&self,
|
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
content_bounds: Rectangle,
|
|
||||||
offset: u32,
|
|
||||||
scrollbar_width: u16,
|
|
||||||
scrollbar_margin: u16,
|
|
||||||
scroller_width: u16,
|
|
||||||
) -> Option<Scrollbar>;
|
|
||||||
|
|
||||||
/// Draws the [`Scrollable`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the [`State`] of the [`Scrollable`]
|
|
||||||
/// - the bounds of the [`Scrollable`] widget
|
|
||||||
/// - the bounds of the [`Scrollable`] content
|
|
||||||
/// - whether the mouse is over the [`Scrollable`] or not
|
|
||||||
/// - whether the mouse is over the [`Scrollbar`] or not
|
|
||||||
/// - a optional [`Scrollbar`] to be rendered
|
|
||||||
/// - the scrolling offset
|
|
||||||
/// - the drawn content
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
scrollable: &State,
|
|
||||||
bounds: Rectangle,
|
|
||||||
content_bounds: Rectangle,
|
|
||||||
is_mouse_over: bool,
|
|
||||||
is_mouse_over_scrollbar: bool,
|
|
||||||
scrollbar: Option<Scrollbar>,
|
|
||||||
offset: u32,
|
|
||||||
style: &Self::Style,
|
|
||||||
content: Self::Output,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,17 @@
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
|
use crate::renderer;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
Background, Clipboard, Color, Element, Hasher, Layout, Length, Point,
|
||||||
|
Rectangle, Size, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{hash::Hash, ops::RangeInclusive};
|
use std::hash::Hash;
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||||
|
|
||||||
/// An horizontal bar and a handle that selects a single value from a range of
|
/// An horizontal bar and a handle that selects a single value from a range of
|
||||||
/// values.
|
/// values.
|
||||||
|
|
@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::{slider, renderer::Null};
|
/// # use iced_native::widget::slider::{self, Slider};
|
||||||
/// #
|
/// #
|
||||||
/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
|
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// pub enum Message {
|
/// pub enum Message {
|
||||||
/// SliderChanged(f32),
|
/// SliderChanged(f32),
|
||||||
|
|
@ -37,7 +41,7 @@ use std::{hash::Hash, ops::RangeInclusive};
|
||||||
///
|
///
|
||||||
/// 
|
/// 
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
|
pub struct Slider<'a, T, Message> {
|
||||||
state: &'a mut State,
|
state: &'a mut State,
|
||||||
range: RangeInclusive<T>,
|
range: RangeInclusive<T>,
|
||||||
step: T,
|
step: T,
|
||||||
|
|
@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
|
||||||
on_release: Option<Message>,
|
on_release: Option<Message>,
|
||||||
width: Length,
|
width: Length,
|
||||||
height: u16,
|
height: u16,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
|
impl<'a, T, Message> Slider<'a, T, Message>
|
||||||
where
|
where
|
||||||
T: Copy + From<u8> + std::cmp::PartialOrd,
|
T: Copy + From<u8> + std::cmp::PartialOrd,
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer,
|
|
||||||
{
|
{
|
||||||
|
/// The default height of a [`Slider`].
|
||||||
|
pub const DEFAULT_HEIGHT: u16 = 22;
|
||||||
|
|
||||||
/// Creates a new [`Slider`].
|
/// Creates a new [`Slider`].
|
||||||
///
|
///
|
||||||
/// It expects:
|
/// It expects:
|
||||||
|
|
@ -93,8 +99,8 @@ where
|
||||||
on_change: Box::new(on_change),
|
on_change: Box::new(on_change),
|
||||||
on_release: None,
|
on_release: None,
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
height: Renderer::DEFAULT_HEIGHT,
|
height: Self::DEFAULT_HEIGHT,
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,8 +128,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`Slider`].
|
/// Sets the style of the [`Slider`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,11 +157,11 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Slider<'a, T, Message, Renderer>
|
for Slider<'a, T, Message>
|
||||||
where
|
where
|
||||||
T: Copy + Into<f64> + num_traits::FromPrimitive,
|
T: Copy + Into<f64> + num_traits::FromPrimitive,
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -246,22 +255,113 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
let start = *self.range.start();
|
let bounds = layout.bounds();
|
||||||
let end = *self.range.end();
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
renderer.draw(
|
let style = if self.state.is_dragging {
|
||||||
layout.bounds(),
|
self.style_sheet.dragging()
|
||||||
cursor_position,
|
} else if is_mouse_over {
|
||||||
start.into() as f32..=end.into() as f32,
|
self.style_sheet.hovered()
|
||||||
self.value.into() as f32,
|
} else {
|
||||||
self.state.is_dragging,
|
self.style_sheet.active()
|
||||||
&self.style,
|
};
|
||||||
)
|
|
||||||
|
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: rail_y,
|
||||||
|
width: bounds.width,
|
||||||
|
height: 2.0,
|
||||||
|
},
|
||||||
|
border_radius: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
style.rail_colors.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: rail_y + 2.0,
|
||||||
|
width: bounds.width,
|
||||||
|
height: 2.0,
|
||||||
|
},
|
||||||
|
border_radius: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
Background::Color(style.rail_colors.1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (handle_width, handle_height, handle_border_radius) = match style
|
||||||
|
.handle
|
||||||
|
.shape
|
||||||
|
{
|
||||||
|
HandleShape::Circle { radius } => {
|
||||||
|
(radius * 2.0, radius * 2.0, radius)
|
||||||
|
}
|
||||||
|
HandleShape::Rectangle {
|
||||||
|
width,
|
||||||
|
border_radius,
|
||||||
|
} => (f32::from(width), f32::from(bounds.height), border_radius),
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = self.value.into() as f32;
|
||||||
|
let (range_start, range_end) = {
|
||||||
|
let (start, end) = self.range.clone().into_inner();
|
||||||
|
|
||||||
|
(start.into() as f32, end.into() as f32)
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle_offset = if range_start >= range_end {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
(bounds.width - handle_width) * (value - range_start)
|
||||||
|
/ (range_end - range_start)
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + handle_offset.round(),
|
||||||
|
y: rail_y - handle_height / 2.0,
|
||||||
|
width: handle_width,
|
||||||
|
height: handle_height,
|
||||||
|
},
|
||||||
|
border_radius: handle_border_radius,
|
||||||
|
border_width: style.handle.border_width,
|
||||||
|
border_color: style.handle.border_color,
|
||||||
|
},
|
||||||
|
style.handle.color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
|
if self.state.is_dragging {
|
||||||
|
mouse::Interaction::Grabbing
|
||||||
|
} else if is_mouse_over {
|
||||||
|
mouse::Interaction::Grab
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -272,48 +372,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Slider`].
|
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`Slider`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// The default height of a [`Slider`].
|
|
||||||
const DEFAULT_HEIGHT: u16;
|
|
||||||
|
|
||||||
/// Draws a [`Slider`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// * the current cursor position
|
|
||||||
/// * the bounds of the [`Slider`]
|
|
||||||
/// * the local state of the [`Slider`]
|
|
||||||
/// * the range of values of the [`Slider`]
|
|
||||||
/// * the current value of the [`Slider`]
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
range: RangeInclusive<f32>,
|
|
||||||
value: f32,
|
|
||||||
is_dragging: bool,
|
|
||||||
style: &Self::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
|
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
|
||||||
Message: 'a + Clone,
|
Message: 'a + Clone,
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + crate::Renderer,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {
|
||||||
slider: Slider<'a, T, Message, Renderer>,
|
|
||||||
) -> Element<'a, Message, Renderer> {
|
|
||||||
Element::new(slider)
|
Element::new(slider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//! Distribute content vertically.
|
//! Distribute content vertically.
|
||||||
use std::hash::Hash;
|
use crate::layout;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
||||||
|
|
||||||
use crate::{
|
use std::hash::Hash;
|
||||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An amount of empty space.
|
/// An amount of empty space.
|
||||||
///
|
///
|
||||||
|
|
@ -39,7 +39,7 @@ impl Space {
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for Space
|
impl<Message, Renderer> Widget<Message, Renderer> for Space
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -61,13 +61,12 @@ where
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
_renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.draw(layout.bounds())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -78,17 +77,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of an amount of [`Space`].
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// Draws an amount of empty [`Space`].
|
|
||||||
///
|
|
||||||
/// You should most likely return an empty primitive here.
|
|
||||||
fn draw(&mut self, bounds: Rectangle) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
|
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: crate::Renderer,
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
{
|
{
|
||||||
fn from(space: Space) -> Element<'a, Message, Renderer> {
|
fn from(space: Space) -> Element<'a, Message, Renderer> {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
//! Display vector graphics in your application.
|
//! Display vector graphics in your application.
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::svg::{self, Handle};
|
||||||
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
||||||
|
|
||||||
use std::{
|
use std::hash::Hash;
|
||||||
hash::{Hash, Hasher as _},
|
use std::path::PathBuf;
|
||||||
path::PathBuf,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A vector graphics image.
|
/// A vector graphics image.
|
||||||
///
|
///
|
||||||
|
|
@ -52,7 +51,7 @@ impl Svg {
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for Svg
|
impl<Message, Renderer> Widget<Message, Renderer> for Svg
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: svg::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -90,12 +89,12 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
_defaults: &Renderer::Defaults,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.draw(self.handle.clone(), layout)
|
renderer.draw(self.handle.clone(), layout.bounds())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -107,94 +106,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`Svg`] handle.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Handle {
|
|
||||||
id: u64,
|
|
||||||
data: Arc<Data>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handle {
|
|
||||||
/// Creates an SVG [`Handle`] pointing to the vector image of the given
|
|
||||||
/// path.
|
|
||||||
pub fn from_path(path: impl Into<PathBuf>) -> Handle {
|
|
||||||
Self::from_data(Data::Path(path.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
|
|
||||||
/// or gzip compressed data.
|
|
||||||
///
|
|
||||||
/// This is useful if you already have your SVG data in-memory, maybe
|
|
||||||
/// because you downloaded or generated it procedurally.
|
|
||||||
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
|
|
||||||
Self::from_data(Data::Bytes(bytes.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_data(data: Data) -> Handle {
|
|
||||||
let mut hasher = Hasher::default();
|
|
||||||
data.hash(&mut hasher);
|
|
||||||
|
|
||||||
Handle {
|
|
||||||
id: hasher.finish(),
|
|
||||||
data: Arc::new(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the unique identifier of the [`Handle`].
|
|
||||||
pub fn id(&self) -> u64 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the SVG [`Data`].
|
|
||||||
pub fn data(&self) -> &Data {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Handle {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.id.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data of an [`Svg`].
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub enum Data {
|
|
||||||
/// File data
|
|
||||||
Path(PathBuf),
|
|
||||||
|
|
||||||
/// In-memory data
|
|
||||||
///
|
|
||||||
/// Can contain an SVG string or a gzip compressed data.
|
|
||||||
Bytes(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Data {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
|
||||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The renderer of an [`Svg`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being able to use
|
|
||||||
/// an [`Svg`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: crate::Renderer {
|
|
||||||
/// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
|
|
||||||
fn dimensions(&self, handle: &Handle) -> (u32, u32);
|
|
||||||
|
|
||||||
/// Draws an [`Svg`].
|
|
||||||
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
|
impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: svg::Renderer,
|
||||||
{
|
{
|
||||||
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
|
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(icon)
|
Element::new(icon)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
//! Write some text for your users to read.
|
//! Write some text for your users to read.
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
|
use crate::renderer;
|
||||||
|
use crate::text;
|
||||||
use crate::{
|
use crate::{
|
||||||
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use iced_core::text::Hit;
|
|
||||||
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
/// A paragraph of text.
|
/// A paragraph of text.
|
||||||
|
|
@ -14,7 +14,7 @@ use std::hash::Hash;
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # type Text = iced_native::Text<iced_native::renderer::Null>;
|
/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
|
||||||
/// #
|
/// #
|
||||||
/// Text::new("I <3 iced!")
|
/// Text::new("I <3 iced!")
|
||||||
/// .color([0.0, 0.0, 1.0])
|
/// .color([0.0, 0.0, 1.0])
|
||||||
|
|
@ -23,7 +23,7 @@ use std::hash::Hash;
|
||||||
///
|
///
|
||||||
/// 
|
/// 
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Text<Renderer: self::Renderer> {
|
pub struct Text<Renderer: text::Renderer> {
|
||||||
content: String,
|
content: String,
|
||||||
size: Option<u16>,
|
size: Option<u16>,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
|
|
@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Renderer: self::Renderer> Text<Renderer> {
|
impl<Renderer: text::Renderer> Text<Renderer> {
|
||||||
/// Create a new fragment of [`Text`] with the given contents.
|
/// Create a new fragment of [`Text`] with the given contents.
|
||||||
pub fn new<T: Into<String>>(label: T) -> Self {
|
pub fn new<T: Into<String>>(label: T) -> Self {
|
||||||
Text {
|
Text {
|
||||||
|
|
@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {
|
||||||
|
|
||||||
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
|
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -134,21 +134,22 @@ where
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
defaults: &Renderer::Defaults,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
renderer.draw(
|
draw(
|
||||||
defaults,
|
renderer,
|
||||||
layout.bounds(),
|
style,
|
||||||
|
layout,
|
||||||
&self.content,
|
&self.content,
|
||||||
self.size.unwrap_or(renderer.default_size()),
|
|
||||||
self.font,
|
self.font,
|
||||||
|
self.size,
|
||||||
self.color,
|
self.color,
|
||||||
self.horizontal_alignment,
|
self.horizontal_alignment,
|
||||||
self.vertical_alignment,
|
self.vertical_alignment,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -162,79 +163,65 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`Text`] fragment.
|
/// Draws text using the same logic as the [`Text`] widget.
|
||||||
///
|
///
|
||||||
/// Your [renderer] will need to implement this trait before being
|
/// Specifically:
|
||||||
/// able to use [`Text`] in your user interface.
|
|
||||||
///
|
///
|
||||||
/// [renderer]: crate::Renderer
|
/// * If no `size` is provided, the default text size of the `Renderer` will be
|
||||||
pub trait Renderer: crate::Renderer {
|
/// used.
|
||||||
/// The font type used for [`Text`].
|
/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
|
||||||
type Font: Default + Copy;
|
/// used.
|
||||||
|
/// * The alignment attributes do not affect the position of the bounds of the
|
||||||
/// Returns the default size of [`Text`].
|
/// [`Layout`].
|
||||||
fn default_size(&self) -> u16;
|
pub fn draw<Renderer>(
|
||||||
|
renderer: &mut Renderer,
|
||||||
/// Measures the [`Text`] in the given bounds and returns the minimum
|
style: &renderer::Style,
|
||||||
/// boundaries that can fit the contents.
|
layout: Layout<'_>,
|
||||||
fn measure(
|
|
||||||
&self,
|
|
||||||
content: &str,
|
content: &str,
|
||||||
size: u16,
|
font: Renderer::Font,
|
||||||
font: Self::Font,
|
size: Option<u16>,
|
||||||
bounds: Size,
|
|
||||||
) -> (f32, f32);
|
|
||||||
|
|
||||||
/// Tests whether the provided point is within the boundaries of [`Text`]
|
|
||||||
/// laid out with the given parameters, returning information about
|
|
||||||
/// the nearest character.
|
|
||||||
///
|
|
||||||
/// If `nearest_only` is true, the hit test does not consider whether the
|
|
||||||
/// the point is interior to any glyph bounds, returning only the character
|
|
||||||
/// with the nearest centeroid.
|
|
||||||
fn hit_test(
|
|
||||||
&self,
|
|
||||||
contents: &str,
|
|
||||||
size: f32,
|
|
||||||
font: Self::Font,
|
|
||||||
bounds: Size,
|
|
||||||
point: Point,
|
|
||||||
nearest_only: bool,
|
|
||||||
) -> Option<Hit>;
|
|
||||||
|
|
||||||
/// Draws a [`Text`] fragment.
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// * the bounds of the [`Text`]
|
|
||||||
/// * the contents of the [`Text`]
|
|
||||||
/// * the size of the [`Text`]
|
|
||||||
/// * the color of the [`Text`]
|
|
||||||
/// * the [`HorizontalAlignment`] of the [`Text`]
|
|
||||||
/// * the [`VerticalAlignment`] of the [`Text`]
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
defaults: &Self::Defaults,
|
|
||||||
bounds: Rectangle,
|
|
||||||
content: &str,
|
|
||||||
size: u16,
|
|
||||||
font: Self::Font,
|
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
) -> Self::Output;
|
) where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
{
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
let x = match horizontal_alignment {
|
||||||
|
alignment::Horizontal::Left => bounds.x,
|
||||||
|
alignment::Horizontal::Center => bounds.center_x(),
|
||||||
|
alignment::Horizontal::Right => bounds.x + bounds.width,
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = match vertical_alignment {
|
||||||
|
alignment::Vertical::Top => bounds.y,
|
||||||
|
alignment::Vertical::Center => bounds.center_y(),
|
||||||
|
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_text(crate::text::Text {
|
||||||
|
content,
|
||||||
|
size: f32::from(size.unwrap_or(renderer.default_size())),
|
||||||
|
bounds: Rectangle { x, y, ..bounds },
|
||||||
|
color: color.unwrap_or(style.text_color),
|
||||||
|
font,
|
||||||
|
horizontal_alignment,
|
||||||
|
vertical_alignment,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Text<Renderer>>
|
impl<'a, Message, Renderer> From<Text<Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer + 'a,
|
Renderer: text::Renderer + 'a,
|
||||||
{
|
{
|
||||||
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
|
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(text)
|
Element::new(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Renderer: self::Renderer> Clone for Text<Renderer> {
|
impl<Renderer: text::Renderer> Clone for Text<Renderer> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
content: self.content.clone(),
|
content: self.content.clone(),
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,31 @@ pub use value::Value;
|
||||||
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
|
|
||||||
|
use crate::alignment;
|
||||||
use crate::event::{self, Event};
|
use crate::event::{self, Event};
|
||||||
use crate::keyboard;
|
use crate::keyboard;
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse::{self, click};
|
use crate::mouse::{self, click};
|
||||||
use crate::text;
|
use crate::renderer;
|
||||||
|
use crate::text::{self, Text};
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
|
||||||
Size, Widget,
|
Rectangle, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::u32;
|
use std::u32;
|
||||||
|
|
||||||
|
pub use iced_style::text_input::{Style, StyleSheet};
|
||||||
|
|
||||||
/// A field that can be filled with text.
|
/// A field that can be filled with text.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use iced_native::{text_input, renderer::Null};
|
/// # use iced_native::renderer::Null;
|
||||||
|
/// # use iced_native::widget::text_input;
|
||||||
/// #
|
/// #
|
||||||
/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;
|
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
|
||||||
/// #[derive(Debug, Clone)]
|
/// #[derive(Debug, Clone)]
|
||||||
/// enum Message {
|
/// enum Message {
|
||||||
/// TextInputChanged(String),
|
/// TextInputChanged(String),
|
||||||
|
|
@ -49,7 +54,7 @@ use std::u32;
|
||||||
/// ```
|
/// ```
|
||||||
/// 
|
/// 
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct TextInput<'a, Message, Renderer: self::Renderer> {
|
pub struct TextInput<'a, Message, Renderer: text::Renderer> {
|
||||||
state: &'a mut State,
|
state: &'a mut State,
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
value: Value,
|
value: Value,
|
||||||
|
|
@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
|
||||||
size: Option<u16>,
|
size: Option<u16>,
|
||||||
on_change: Box<dyn Fn(String) -> Message>,
|
on_change: Box<dyn Fn(String) -> Message>,
|
||||||
on_submit: Option<Message>,
|
on_submit: Option<Message>,
|
||||||
style: Renderer::Style,
|
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
|
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
/// Creates a new [`TextInput`].
|
/// Creates a new [`TextInput`].
|
||||||
///
|
///
|
||||||
|
|
@ -97,7 +102,7 @@ where
|
||||||
size: None,
|
size: None,
|
||||||
on_change: Box::new(on_change),
|
on_change: Box::new(on_change),
|
||||||
on_submit: None,
|
on_submit: None,
|
||||||
style: Renderer::Style::default(),
|
style_sheet: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,8 +152,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the style of the [`TextInput`].
|
/// Sets the style of the [`TextInput`].
|
||||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
pub fn style(
|
||||||
self.style = style.into();
|
mut self,
|
||||||
|
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||||
|
) -> Self {
|
||||||
|
self.style_sheet = style_sheet.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +168,7 @@ where
|
||||||
|
|
||||||
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
|
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||||
/// [`Value`] if provided.
|
/// [`Value`] if provided.
|
||||||
|
|
@ -170,38 +178,166 @@ where
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
value: Option<&Value>,
|
value: Option<&Value>,
|
||||||
) -> Renderer::Output {
|
) {
|
||||||
let value = value.unwrap_or(&self.value);
|
let value = value.unwrap_or(&self.value);
|
||||||
|
let secure_value = self.is_secure.then(|| value.secure());
|
||||||
|
let value = secure_value.as_ref().unwrap_or(&value);
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let text_bounds = layout.children().next().unwrap().bounds();
|
let text_bounds = layout.children().next().unwrap().bounds();
|
||||||
|
|
||||||
if self.is_secure {
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
self::Renderer::draw(
|
|
||||||
renderer,
|
let style = if self.state.is_focused() {
|
||||||
bounds,
|
self.style_sheet.focused()
|
||||||
text_bounds,
|
} else if is_mouse_over {
|
||||||
cursor_position,
|
self.style_sheet.hovered()
|
||||||
self.font,
|
|
||||||
self.size.unwrap_or(renderer.default_size()),
|
|
||||||
&self.placeholder,
|
|
||||||
&value.secure(),
|
|
||||||
&self.state,
|
|
||||||
&self.style,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
self::Renderer::draw(
|
self.style_sheet.active()
|
||||||
renderer,
|
};
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
|
border_radius: style.border_radius,
|
||||||
|
border_width: style.border_width,
|
||||||
|
border_color: style.border_color,
|
||||||
|
},
|
||||||
|
style.background,
|
||||||
|
);
|
||||||
|
|
||||||
|
let text = value.to_string();
|
||||||
|
let size = self.size.unwrap_or(renderer.default_size());
|
||||||
|
|
||||||
|
let (cursor, offset) = if self.state.is_focused() {
|
||||||
|
match self.state.cursor.state(&value) {
|
||||||
|
cursor::State::Index(position) => {
|
||||||
|
let (text_value_width, offset) =
|
||||||
|
measure_cursor_and_scroll_offset(
|
||||||
|
renderer,
|
||||||
text_bounds,
|
text_bounds,
|
||||||
cursor_position,
|
&value,
|
||||||
|
size,
|
||||||
|
position,
|
||||||
self.font,
|
self.font,
|
||||||
self.size.unwrap_or(renderer.default_size()),
|
);
|
||||||
&self.placeholder,
|
|
||||||
value,
|
(
|
||||||
&self.state,
|
Some((
|
||||||
&self.style,
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: text_bounds.x + text_value_width,
|
||||||
|
y: text_bounds.y,
|
||||||
|
width: 1.0,
|
||||||
|
height: text_bounds.height,
|
||||||
|
},
|
||||||
|
border_radius: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
self.style_sheet.value_color(),
|
||||||
|
)),
|
||||||
|
offset,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
cursor::State::Selection { start, end } => {
|
||||||
|
let left = start.min(end);
|
||||||
|
let right = end.max(start);
|
||||||
|
|
||||||
|
let (left_position, left_offset) =
|
||||||
|
measure_cursor_and_scroll_offset(
|
||||||
|
renderer,
|
||||||
|
text_bounds,
|
||||||
|
&value,
|
||||||
|
size,
|
||||||
|
left,
|
||||||
|
self.font,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (right_position, right_offset) =
|
||||||
|
measure_cursor_and_scroll_offset(
|
||||||
|
renderer,
|
||||||
|
text_bounds,
|
||||||
|
&value,
|
||||||
|
size,
|
||||||
|
right,
|
||||||
|
self.font,
|
||||||
|
);
|
||||||
|
|
||||||
|
let width = right_position - left_position;
|
||||||
|
|
||||||
|
(
|
||||||
|
Some((
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: text_bounds.x + left_position,
|
||||||
|
y: text_bounds.y,
|
||||||
|
width,
|
||||||
|
height: text_bounds.height,
|
||||||
|
},
|
||||||
|
border_radius: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
self.style_sheet.selection_color(),
|
||||||
|
)),
|
||||||
|
if end == right {
|
||||||
|
right_offset
|
||||||
|
} else {
|
||||||
|
left_offset
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(None, 0.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_width = renderer.measure_width(
|
||||||
|
if text.is_empty() {
|
||||||
|
&self.placeholder
|
||||||
|
} else {
|
||||||
|
&text
|
||||||
|
},
|
||||||
|
size,
|
||||||
|
self.font,
|
||||||
|
);
|
||||||
|
|
||||||
|
let render = |renderer: &mut Renderer| {
|
||||||
|
if let Some((cursor, color)) = cursor {
|
||||||
|
renderer.fill_quad(cursor, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.fill_text(Text {
|
||||||
|
content: if text.is_empty() {
|
||||||
|
&self.placeholder
|
||||||
|
} else {
|
||||||
|
&text
|
||||||
|
},
|
||||||
|
color: if text.is_empty() {
|
||||||
|
self.style_sheet.placeholder_color()
|
||||||
|
} else {
|
||||||
|
self.style_sheet.value_color()
|
||||||
|
},
|
||||||
|
font: self.font,
|
||||||
|
bounds: Rectangle {
|
||||||
|
y: text_bounds.center_y(),
|
||||||
|
width: f32::INFINITY,
|
||||||
|
..text_bounds
|
||||||
|
},
|
||||||
|
size: f32::from(size),
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if text_width > text_bounds.width {
|
||||||
|
renderer.with_layer(text_bounds, |renderer| {
|
||||||
|
renderer.with_translation(Vector::new(-offset, 0.0), render)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
render(renderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,7 +345,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for TextInput<'a, Message, Renderer>
|
for TextInput<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
Renderer: self::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
|
|
@ -275,7 +411,8 @@ where
|
||||||
self.value.clone()
|
self.value.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.find_cursor_position(
|
find_cursor_position(
|
||||||
|
renderer,
|
||||||
text_layout.bounds(),
|
text_layout.bounds(),
|
||||||
self.font,
|
self.font,
|
||||||
self.size,
|
self.size,
|
||||||
|
|
@ -294,8 +431,8 @@ where
|
||||||
if self.is_secure {
|
if self.is_secure {
|
||||||
self.state.cursor.select_all(&self.value);
|
self.state.cursor.select_all(&self.value);
|
||||||
} else {
|
} else {
|
||||||
let position = renderer
|
let position = find_cursor_position(
|
||||||
.find_cursor_position(
|
renderer,
|
||||||
text_layout.bounds(),
|
text_layout.bounds(),
|
||||||
self.font,
|
self.font,
|
||||||
self.size,
|
self.size,
|
||||||
|
|
@ -341,8 +478,8 @@ where
|
||||||
self.value.clone()
|
self.value.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let position = renderer
|
let position = find_cursor_position(
|
||||||
.find_cursor_position(
|
renderer,
|
||||||
text_layout.bounds(),
|
text_layout.bounds(),
|
||||||
self.font,
|
self.font,
|
||||||
self.size,
|
self.size,
|
||||||
|
|
@ -621,14 +758,27 @@ where
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
|
||||||
_defaults: &Renderer::Defaults,
|
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> Renderer::Output {
|
) -> mouse::Interaction {
|
||||||
|
if layout.bounds().contains(cursor_position) {
|
||||||
|
mouse::Interaction::Text
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
self.draw(renderer, layout, cursor_position, None)
|
self.draw(renderer, layout, cursor_position, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,87 +794,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The renderer of a [`TextInput`].
|
|
||||||
///
|
|
||||||
/// Your [renderer] will need to implement this trait before being
|
|
||||||
/// able to use a [`TextInput`] in your user interface.
|
|
||||||
///
|
|
||||||
/// [renderer]: crate::renderer
|
|
||||||
pub trait Renderer: text::Renderer + Sized {
|
|
||||||
/// The style supported by this renderer.
|
|
||||||
type Style: Default;
|
|
||||||
|
|
||||||
/// Returns the width of the value of the [`TextInput`].
|
|
||||||
fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
|
|
||||||
|
|
||||||
/// Returns the current horizontal offset of the value of the
|
|
||||||
/// [`TextInput`].
|
|
||||||
///
|
|
||||||
/// This is the amount of horizontal scrolling applied when the [`Value`]
|
|
||||||
/// does not fit the [`TextInput`].
|
|
||||||
fn offset(
|
|
||||||
&self,
|
|
||||||
text_bounds: Rectangle,
|
|
||||||
font: Self::Font,
|
|
||||||
size: u16,
|
|
||||||
value: &Value,
|
|
||||||
state: &State,
|
|
||||||
) -> f32;
|
|
||||||
|
|
||||||
/// Draws a [`TextInput`].
|
|
||||||
///
|
|
||||||
/// It receives:
|
|
||||||
/// - the bounds of the [`TextInput`]
|
|
||||||
/// - the bounds of the text (i.e. the current value)
|
|
||||||
/// - the cursor position
|
|
||||||
/// - the placeholder to show when the value is empty
|
|
||||||
/// - the current [`Value`]
|
|
||||||
/// - the current [`State`]
|
|
||||||
fn draw(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
text_bounds: Rectangle,
|
|
||||||
cursor_position: Point,
|
|
||||||
font: Self::Font,
|
|
||||||
size: u16,
|
|
||||||
placeholder: &str,
|
|
||||||
value: &Value,
|
|
||||||
state: &State,
|
|
||||||
style: &Self::Style,
|
|
||||||
) -> Self::Output;
|
|
||||||
|
|
||||||
/// Computes the position of the text cursor at the given X coordinate of
|
|
||||||
/// a [`TextInput`].
|
|
||||||
fn find_cursor_position(
|
|
||||||
&self,
|
|
||||||
text_bounds: Rectangle,
|
|
||||||
font: Self::Font,
|
|
||||||
size: Option<u16>,
|
|
||||||
value: &Value,
|
|
||||||
state: &State,
|
|
||||||
x: f32,
|
|
||||||
) -> Option<usize> {
|
|
||||||
let size = size.unwrap_or(self.default_size());
|
|
||||||
|
|
||||||
let offset = self.offset(text_bounds, font, size, &value, &state);
|
|
||||||
|
|
||||||
self.hit_test(
|
|
||||||
&value.to_string(),
|
|
||||||
size.into(),
|
|
||||||
font,
|
|
||||||
Size::INFINITY,
|
|
||||||
Point::new(x + offset, text_bounds.height / 2.0),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.map(text::Hit::cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
|
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a + Clone,
|
Message: 'a + Clone,
|
||||||
Renderer: 'a + self::Renderer,
|
Renderer: 'a + text::Renderer,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
text_input: TextInput<'a, Message, Renderer>,
|
text_input: TextInput<'a, Message, Renderer>,
|
||||||
|
|
@ -815,3 +889,88 @@ mod platform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn offset<Renderer>(
|
||||||
|
renderer: &Renderer,
|
||||||
|
text_bounds: Rectangle,
|
||||||
|
font: Renderer::Font,
|
||||||
|
size: u16,
|
||||||
|
value: &Value,
|
||||||
|
state: &State,
|
||||||
|
) -> f32
|
||||||
|
where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
{
|
||||||
|
if state.is_focused() {
|
||||||
|
let cursor = state.cursor();
|
||||||
|
|
||||||
|
let focus_position = match cursor.state(value) {
|
||||||
|
cursor::State::Index(i) => i,
|
||||||
|
cursor::State::Selection { end, .. } => end,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, offset) = measure_cursor_and_scroll_offset(
|
||||||
|
renderer,
|
||||||
|
text_bounds,
|
||||||
|
value,
|
||||||
|
size,
|
||||||
|
focus_position,
|
||||||
|
font,
|
||||||
|
);
|
||||||
|
|
||||||
|
offset
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn measure_cursor_and_scroll_offset<Renderer>(
|
||||||
|
renderer: &Renderer,
|
||||||
|
text_bounds: Rectangle,
|
||||||
|
value: &Value,
|
||||||
|
size: u16,
|
||||||
|
cursor_index: usize,
|
||||||
|
font: Renderer::Font,
|
||||||
|
) -> (f32, f32)
|
||||||
|
where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
{
|
||||||
|
let text_before_cursor = value.until(cursor_index).to_string();
|
||||||
|
|
||||||
|
let text_value_width =
|
||||||
|
renderer.measure_width(&text_before_cursor, size, font);
|
||||||
|
|
||||||
|
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
|
||||||
|
|
||||||
|
(text_value_width, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the position of the text cursor at the given X coordinate of
|
||||||
|
/// a [`TextInput`].
|
||||||
|
fn find_cursor_position<Renderer>(
|
||||||
|
renderer: &Renderer,
|
||||||
|
text_bounds: Rectangle,
|
||||||
|
font: Renderer::Font,
|
||||||
|
size: Option<u16>,
|
||||||
|
value: &Value,
|
||||||
|
state: &State,
|
||||||
|
x: f32,
|
||||||
|
) -> Option<usize>
|
||||||
|
where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
{
|
||||||
|
let size = size.unwrap_or(renderer.default_size());
|
||||||
|
|
||||||
|
let offset = offset(renderer, text_bounds, font, size, &value, &state);
|
||||||
|
|
||||||
|
renderer
|
||||||
|
.hit_test(
|
||||||
|
&value.to_string(),
|
||||||
|
size.into(),
|
||||||
|
font,
|
||||||
|
Size::INFINITY,
|
||||||
|
Point::new(x + offset, text_bounds.height / 2.0),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map(text::Hit::cursor)
|
||||||
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue