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 keyboard;
|
||||
pub mod mouse;
|
||||
pub mod text;
|
||||
|
||||
mod background;
|
||||
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]
|
||||
iced = { path = "../.." }
|
||||
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,
|
||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||
// implemented by `iced_wgpu` and other renderers.
|
||||
use iced_graphics::{Backend, Defaults, Primitive, Renderer};
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::renderer;
|
||||
use iced_native::{
|
||||
layout, mouse, Background, Color, Element, Hasher, Layout, Length,
|
||||
Point, Rectangle, Size, Widget,
|
||||
Color, Element, Hasher, Length, Point, Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
pub struct Circle {
|
||||
|
|
@ -25,9 +25,9 @@ mod circle {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Message, B> Widget<Message, Renderer<B>> for Circle
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Circle
|
||||
where
|
||||
B: Backend,
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Shrink
|
||||
|
|
@ -39,7 +39,7 @@ mod circle {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer<B>,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
|
||||
|
|
@ -53,30 +53,29 @@ mod circle {
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_renderer: &mut Renderer<B>,
|
||||
_defaults: &Defaults,
|
||||
renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> (Primitive, mouse::Interaction) {
|
||||
(
|
||||
Primitive::Quad {
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
background: Background::Color(Color::BLACK),
|
||||
border_radius: self.radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
Color::BLACK,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Circle
|
||||
impl<'a, Message, Renderer> Into<Element<'a, Message, Renderer>> for Circle
|
||||
where
|
||||
B: Backend,
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn into(self) -> Element<'a, Message, Renderer<B>> {
|
||||
fn into(self) -> Element<'a, Message, Renderer> {
|
||||
Element::new(self)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ mod rainbow {
|
|||
// Of course, you can choose to make the implementation renderer-agnostic,
|
||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||
// implemented by `iced_wgpu` and other renderers.
|
||||
use iced_graphics::{
|
||||
triangle::{Mesh2D, Vertex2D},
|
||||
Backend, Defaults, Primitive, Renderer,
|
||||
};
|
||||
use iced_graphics::renderer::{self, Renderer};
|
||||
use iced_graphics::{Backend, Primitive};
|
||||
|
||||
use iced_native::{
|
||||
layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
Vector, Widget,
|
||||
};
|
||||
|
||||
|
|
@ -53,12 +52,15 @@ mod rainbow {
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_renderer: &mut Renderer<B>,
|
||||
_defaults: &Defaults,
|
||||
renderer: &mut Renderer<B>,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> (Primitive, mouse::Interaction) {
|
||||
) {
|
||||
use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
||||
use iced_native::Renderer as _;
|
||||
|
||||
let b = layout.bounds();
|
||||
|
||||
// R O Y G B I V
|
||||
|
|
@ -88,65 +90,63 @@ mod rainbow {
|
|||
let posn_bl = [0.0, b.height];
|
||||
let posn_l = [0.0, b.height / 2.0];
|
||||
|
||||
(
|
||||
Primitive::Translate {
|
||||
translation: Vector::new(b.x, b.y),
|
||||
content: Box::new(Primitive::Mesh2D {
|
||||
size: b.size(),
|
||||
buffers: Mesh2D {
|
||||
vertices: vec![
|
||||
Vertex2D {
|
||||
position: posn_center,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_tl,
|
||||
color: color_r,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_t,
|
||||
color: color_o,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_tr,
|
||||
color: color_y,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_r,
|
||||
color: color_g,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_br,
|
||||
color: color_gb,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_b,
|
||||
color: color_b,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_bl,
|
||||
color: color_i,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_l,
|
||||
color: color_v,
|
||||
},
|
||||
],
|
||||
indices: vec![
|
||||
0, 1, 2, // TL
|
||||
0, 2, 3, // T
|
||||
0, 3, 4, // TR
|
||||
0, 4, 5, // R
|
||||
0, 5, 6, // BR
|
||||
0, 6, 7, // B
|
||||
0, 7, 8, // BL
|
||||
0, 8, 1, // L
|
||||
],
|
||||
let mesh = Primitive::Mesh2D {
|
||||
size: b.size(),
|
||||
buffers: Mesh2D {
|
||||
vertices: vec![
|
||||
Vertex2D {
|
||||
position: posn_center,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
},
|
||||
}),
|
||||
Vertex2D {
|
||||
position: posn_tl,
|
||||
color: color_r,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_t,
|
||||
color: color_o,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_tr,
|
||||
color: color_y,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_r,
|
||||
color: color_g,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_br,
|
||||
color: color_gb,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_b,
|
||||
color: color_b,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_bl,
|
||||
color: color_i,
|
||||
},
|
||||
Vertex2D {
|
||||
position: posn_l,
|
||||
color: color_v,
|
||||
},
|
||||
],
|
||||
indices: vec![
|
||||
0, 1, 2, // TL
|
||||
0, 2, 3, // T
|
||||
0, 3, 4, // TR
|
||||
0, 4, 5, // R
|
||||
0, 5, 6, // BR
|
||||
0, 6, 7, // B
|
||||
0, 7, 8, // BL
|
||||
0, 8, 1, // L
|
||||
],
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
};
|
||||
|
||||
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
|
||||
renderer.draw_primitive(mesh);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use iced_glow::Renderer;
|
||||
use iced_glutin::slider;
|
||||
use iced_glutin::{
|
||||
Alignment, Color, Column, Command, Element, Length, Program, Row, Slider,
|
||||
Text,
|
||||
};
|
||||
use iced_glutin::widget::slider::{self, Slider};
|
||||
use iced_glutin::widget::{Column, Row, Text};
|
||||
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ pub fn main() {
|
|||
let mut state = program::State::new(
|
||||
controls,
|
||||
viewport.logical_size(),
|
||||
conversion::cursor_position(cursor_position, viewport.scale_factor()),
|
||||
&mut renderer,
|
||||
&mut debug,
|
||||
);
|
||||
|
|
@ -160,16 +159,19 @@ pub fn main() {
|
|||
}
|
||||
|
||||
// And then iced on top
|
||||
let mouse_interaction = renderer.backend_mut().draw(
|
||||
&gl,
|
||||
&viewport,
|
||||
state.primitive(),
|
||||
&debug.overlay(),
|
||||
);
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&gl,
|
||||
primitive,
|
||||
&viewport,
|
||||
&debug.overlay(),
|
||||
);
|
||||
});
|
||||
|
||||
// Update the mouse cursor
|
||||
windowed_context.window().set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
mouse_interaction,
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use iced_wgpu::Renderer;
|
||||
use iced_winit::{
|
||||
slider, Alignment, Color, Column, Command, Element, Length, Program, Row,
|
||||
Slider, Text,
|
||||
};
|
||||
use iced_winit::widget::slider::{self, Slider};
|
||||
use iced_winit::widget::{Column, Row, Text};
|
||||
use iced_winit::{Alignment, Color, Command, Element, Length, Program};
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ pub fn main() {
|
|||
let mut state = program::State::new(
|
||||
controls,
|
||||
viewport.logical_size(),
|
||||
conversion::cursor_position(cursor_position, viewport.scale_factor()),
|
||||
&mut renderer,
|
||||
&mut debug,
|
||||
);
|
||||
|
|
@ -196,15 +195,17 @@ pub fn main() {
|
|||
}
|
||||
|
||||
// And then iced on top
|
||||
let mouse_interaction = renderer.backend_mut().draw(
|
||||
&mut device,
|
||||
&mut staging_belt,
|
||||
&mut encoder,
|
||||
&view,
|
||||
&viewport,
|
||||
state.primitive(),
|
||||
&debug.overlay(),
|
||||
);
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&mut device,
|
||||
&mut staging_belt,
|
||||
&mut encoder,
|
||||
&view,
|
||||
primitive,
|
||||
&viewport,
|
||||
&debug.overlay(),
|
||||
);
|
||||
});
|
||||
|
||||
// Then we submit the work
|
||||
staging_belt.finish();
|
||||
|
|
@ -212,11 +213,11 @@ pub fn main() {
|
|||
frame.present();
|
||||
|
||||
// Update the mouse cursor
|
||||
window.set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
mouse_interaction,
|
||||
),
|
||||
);
|
||||
window.set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
|
||||
// And recall staging buffers
|
||||
local_pool
|
||||
|
|
|
|||
|
|
@ -177,7 +177,11 @@ impl Application for Example {
|
|||
let title_bar = pane_grid::TitleBar::new(title)
|
||||
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
|
||||
.padding(10)
|
||||
.style(style::TitleBar { is_focused });
|
||||
.style(if is_focused {
|
||||
style::TitleBar::Focused
|
||||
} else {
|
||||
style::TitleBar::Active
|
||||
});
|
||||
|
||||
pane_grid::Content::new(pane.content.view(
|
||||
id,
|
||||
|
|
@ -185,7 +189,11 @@ impl Application for Example {
|
|||
pane.is_pinned,
|
||||
))
|
||||
.title_bar(title_bar)
|
||||
.style(style::Pane { is_focused })
|
||||
.style(if is_focused {
|
||||
style::Pane::Focused
|
||||
} else {
|
||||
style::Pane::Active
|
||||
})
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
|
|
@ -387,14 +395,16 @@ mod style {
|
|||
0xC4 as f32 / 255.0,
|
||||
);
|
||||
|
||||
pub struct TitleBar {
|
||||
pub is_focused: bool,
|
||||
pub enum TitleBar {
|
||||
Active,
|
||||
Focused,
|
||||
}
|
||||
|
||||
impl container::StyleSheet for TitleBar {
|
||||
fn style(&self) -> container::Style {
|
||||
let pane = Pane {
|
||||
is_focused: self.is_focused,
|
||||
let pane = match self {
|
||||
Self::Active => Pane::Active,
|
||||
Self::Focused => Pane::Focused,
|
||||
}
|
||||
.style();
|
||||
|
||||
|
|
@ -406,8 +416,9 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Pane {
|
||||
pub is_focused: bool,
|
||||
pub enum Pane {
|
||||
Active,
|
||||
Focused,
|
||||
}
|
||||
|
||||
impl container::StyleSheet for Pane {
|
||||
|
|
@ -415,10 +426,9 @@ mod style {
|
|||
container::Style {
|
||||
background: Some(Background::Color(SURFACE)),
|
||||
border_width: 2.0,
|
||||
border_color: if self.is_focused {
|
||||
Color::BLACK
|
||||
} else {
|
||||
Color::from_rgb(0.7, 0.7, 0.7)
|
||||
border_color: match self {
|
||||
Self::Active => Color::from_rgb(0.7, 0.7, 0.7),
|
||||
Self::Focused => Color::BLACK,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Default for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn container::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -25,7 +25,7 @@ impl From<Theme> for Box<dyn container::StyleSheet> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn radio::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -34,7 +34,7 @@ impl From<Theme> for Box<dyn radio::StyleSheet> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn container::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn container::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -185,7 +185,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn radio::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn radio::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -194,7 +194,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn text_input::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn text_input::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -203,7 +203,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn button::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn button::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => light::Button.into(),
|
||||
|
|
@ -212,7 +212,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn scrollable::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -221,7 +221,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn slider::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn slider::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
@ -239,7 +239,7 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn checkbox::StyleSheet> {
|
||||
impl<'a> From<Theme> for Box<dyn checkbox::StyleSheet + 'a> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
|
|
|
|||
|
|
@ -363,8 +363,10 @@ impl Controls {
|
|||
let filter_button = |state, label, filter, current_filter| {
|
||||
let label = Text::new(label).size(16);
|
||||
let button =
|
||||
Button::new(state, label).style(style::Button::Filter {
|
||||
selected: filter == current_filter,
|
||||
Button::new(state, label).style(if filter == current_filter {
|
||||
style::Button::FilterSelected
|
||||
} else {
|
||||
style::Button::FilterActive
|
||||
});
|
||||
|
||||
button.on_press(Message::FilterChanged(filter)).padding(8)
|
||||
|
|
@ -602,7 +604,8 @@ mod style {
|
|||
use iced::{button, Background, Color, Vector};
|
||||
|
||||
pub enum Button {
|
||||
Filter { selected: bool },
|
||||
FilterActive,
|
||||
FilterSelected,
|
||||
Icon,
|
||||
Destructive,
|
||||
}
|
||||
|
|
@ -610,20 +613,15 @@ mod style {
|
|||
impl button::StyleSheet for Button {
|
||||
fn active(&self) -> button::Style {
|
||||
match self {
|
||||
Button::Filter { selected } => {
|
||||
if *selected {
|
||||
button::Style {
|
||||
background: Some(Background::Color(
|
||||
Color::from_rgb(0.2, 0.2, 0.7),
|
||||
)),
|
||||
border_radius: 10.0,
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
}
|
||||
} else {
|
||||
button::Style::default()
|
||||
}
|
||||
}
|
||||
Button::FilterActive => button::Style::default(),
|
||||
Button::FilterSelected => button::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(
|
||||
0.2, 0.2, 0.7,
|
||||
))),
|
||||
border_radius: 10.0,
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
},
|
||||
Button::Icon => button::Style {
|
||||
text_color: Color::from_rgb(0.5, 0.5, 0.5),
|
||||
..button::Style::default()
|
||||
|
|
@ -646,9 +644,7 @@ mod style {
|
|||
button::Style {
|
||||
text_color: match self {
|
||||
Button::Icon => Color::from_rgb(0.2, 0.2, 0.7),
|
||||
Button::Filter { selected } if !selected => {
|
||||
Color::from_rgb(0.2, 0.2, 0.7)
|
||||
}
|
||||
Button::FilterActive => Color::from_rgb(0.2, 0.2, 0.7),
|
||||
_ => active.text_color,
|
||||
},
|
||||
shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ use crate::{Settings, Transformation, Viewport};
|
|||
|
||||
use iced_graphics::backend;
|
||||
use iced_graphics::font;
|
||||
use iced_graphics::Layer;
|
||||
use iced_graphics::Primitive;
|
||||
use iced_graphics::{Layer, Primitive};
|
||||
use iced_native::alignment;
|
||||
use iced_native::mouse;
|
||||
use iced_native::{Font, Size};
|
||||
|
||||
/// A [`glow`] graphics backend for [`iced`].
|
||||
|
|
@ -47,18 +45,18 @@ impl Backend {
|
|||
///
|
||||
/// The text provided as overlay will be rendered on top of the primitives.
|
||||
/// This is useful for rendering debug information.
|
||||
pub fn draw<T: AsRef<str>>(
|
||||
pub fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
(primitive, mouse_interaction): &(Primitive, mouse::Interaction),
|
||||
overlay_text: &[T],
|
||||
) -> mouse::Interaction {
|
||||
) {
|
||||
let viewport_size = viewport.physical_size();
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let projection = viewport.projection();
|
||||
|
||||
let mut layers = Layer::generate(primitive, viewport);
|
||||
let mut layers = Layer::generate(primitives, viewport);
|
||||
layers.push(Layer::overlay(overlay_text, viewport));
|
||||
|
||||
for layer in layers {
|
||||
|
|
@ -70,8 +68,6 @@ impl Backend {
|
|||
viewport_size.height,
|
||||
);
|
||||
}
|
||||
|
||||
*mouse_interaction
|
||||
}
|
||||
|
||||
fn flush(
|
||||
|
|
@ -83,6 +79,11 @@ impl Backend {
|
|||
target_height: u32,
|
||||
) {
|
||||
let mut bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
bounds.height = bounds.height.min(target_height);
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
//!
|
||||
//! [`glow`]: https://github.com/grovesNL/glow
|
||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
//#![deny(missing_docs)]
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
#![forbid(rust_2018_idioms)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
mod backend;
|
||||
pub mod program;
|
||||
mod program;
|
||||
mod quad;
|
||||
mod text;
|
||||
mod triangle;
|
||||
|
|
|
|||
|
|
@ -66,13 +66,14 @@ pub mod qr_code;
|
|||
#[doc(no_inline)]
|
||||
pub use qr_code::QRCode;
|
||||
|
||||
pub use iced_native::{Image, Space};
|
||||
pub use iced_native::widget::{Image, Space};
|
||||
|
||||
/// A container that distributes its contents vertically.
|
||||
pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>;
|
||||
pub type Column<'a, Message> =
|
||||
iced_native::widget::Column<'a, Message, Renderer>;
|
||||
|
||||
/// A container that distributes its contents horizontally.
|
||||
pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>;
|
||||
pub type Row<'a, Message> = iced_native::widget::Row<'a, Message, Renderer>;
|
||||
|
||||
/// A paragraph of text.
|
||||
pub type Text = iced_native::Text<Renderer>;
|
||||
pub type Text = iced_native::widget::Text<Renderer>;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
use crate::Renderer;
|
||||
|
||||
pub use iced_graphics::button::{Style, StyleSheet};
|
||||
pub use iced_native::button::State;
|
||||
pub use iced_native::widget::button::State;
|
||||
|
||||
/// A widget that produces a message when clicked.
|
||||
///
|
||||
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
|
||||
pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>;
|
||||
pub type Button<'a, Message> =
|
||||
iced_native::widget::Button<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ pub use iced_graphics::checkbox::{Style, StyleSheet};
|
|||
/// A box that can be checked.
|
||||
///
|
||||
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||
pub type Checkbox<Message> = iced_native::Checkbox<Message, Renderer>;
|
||||
pub type Checkbox<'a, Message> =
|
||||
iced_native::widget::Checkbox<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ pub use iced_graphics::container::{Style, StyleSheet};
|
|||
///
|
||||
/// This is an alias of an `iced_native` container with a default
|
||||
/// `Renderer`.
|
||||
pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>;
|
||||
pub type Container<'a, Message> =
|
||||
iced_native::widget::Container<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ pub use iced_graphics::pane_grid::{
|
|||
/// [](https://gfycat.com/mixedflatjellyfish)
|
||||
///
|
||||
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
|
||||
pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
|
||||
pub type PaneGrid<'a, Message> =
|
||||
iced_native::widget::PaneGrid<'a, Message, Renderer>;
|
||||
|
||||
/// The content of a [`Pane`].
|
||||
pub type Content<'a, Message> =
|
||||
iced_native::pane_grid::Content<'a, Message, Renderer>;
|
||||
iced_native::widget::pane_grid::Content<'a, Message, Renderer>;
|
||||
|
||||
/// The title bar of a [`Pane`].
|
||||
pub type TitleBar<'a, Message> =
|
||||
iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
|
||||
iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Display a dropdown list of selectable values.
|
||||
pub use iced_native::pick_list::State;
|
||||
pub use iced_native::widget::pick_list::State;
|
||||
|
||||
pub use iced_graphics::overlay::menu::Style as Menu;
|
||||
pub use iced_graphics::pick_list::{Style, StyleSheet};
|
||||
|
||||
/// A widget allowing the selection of a single value from a list of options.
|
||||
pub type PickList<'a, T, Message> =
|
||||
iced_native::PickList<'a, T, Message, crate::Renderer>;
|
||||
iced_native::widget::PickList<'a, T, Message, crate::Renderer>;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,5 @@
|
|||
//!
|
||||
//! A [`ProgressBar`] has a range of possible values and a current value,
|
||||
//! as well as a length, height and style.
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_graphics::progress_bar::{Style, StyleSheet};
|
||||
|
||||
/// A bar that displays progress.
|
||||
///
|
||||
/// This is an alias of an `iced_native` progress bar with an
|
||||
/// `iced_wgpu::Renderer`.
|
||||
pub type ProgressBar = iced_native::ProgressBar<Renderer>;
|
||||
pub use iced_graphics::progress_bar::*;
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ pub use iced_graphics::radio::{Style, StyleSheet};
|
|||
///
|
||||
/// This is an alias of an `iced_native` radio button with an
|
||||
/// `iced_wgpu::Renderer`.
|
||||
pub type Radio<Message> = iced_native::Radio<Message, Renderer>;
|
||||
pub type Radio<'a, Message> = iced_native::widget::Radio<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
//! Display a horizontal or vertical rule for dividing content.
|
||||
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_graphics::rule::{FillMode, Style, StyleSheet};
|
||||
|
||||
/// Display a horizontal or vertical rule for dividing content.
|
||||
///
|
||||
/// This is an alias of an `iced_native` rule with an `iced_glow::Renderer`.
|
||||
pub type Rule = iced_native::Rule<Renderer>;
|
||||
pub use iced_graphics::rule::*;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
use crate::Renderer;
|
||||
|
||||
pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet};
|
||||
pub use iced_native::scrollable::State;
|
||||
pub use iced_native::widget::scrollable::State;
|
||||
|
||||
/// A widget that can vertically display an infinite amount of content
|
||||
/// with a scrollbar.
|
||||
|
|
@ -10,4 +10,4 @@ pub use iced_native::scrollable::State;
|
|||
/// This is an alias of an `iced_native` scrollable with a default
|
||||
/// `Renderer`.
|
||||
pub type Scrollable<'a, Message> =
|
||||
iced_native::Scrollable<'a, Message, Renderer>;
|
||||
iced_native::widget::Scrollable<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
//! Display an interactive selector of a single value from a range of values.
|
||||
//!
|
||||
//! A [`Slider`] has some local [`State`].
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||
pub use iced_native::slider::State;
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
///
|
||||
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
|
||||
pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
|
||||
pub use iced_native::widget::slider::{Slider, State};
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
use crate::Renderer;
|
||||
|
||||
pub use iced_graphics::text_input::{Style, StyleSheet};
|
||||
pub use iced_native::text_input::State;
|
||||
pub use iced_native::widget::text_input::State;
|
||||
|
||||
/// A field that can be filled with text.
|
||||
///
|
||||
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
|
||||
pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>;
|
||||
pub type TextInput<'a, Message> =
|
||||
iced_native::widget::TextInput<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ pub use iced_graphics::toggler::{Style, StyleSheet};
|
|||
/// A toggler that can be toggled.
|
||||
///
|
||||
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
|
||||
pub type Toggler<'a, Message> =
|
||||
iced_native::widget::Toggler<'a, Message, Renderer>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Display a widget over another.
|
||||
/// A widget allowing the selection of a single value from a list of options.
|
||||
pub type Tooltip<'a, Message> =
|
||||
iced_native::Tooltip<'a, Message, crate::Renderer>;
|
||||
iced_native::widget::Tooltip<'a, Message, crate::Renderer>;
|
||||
|
||||
pub use iced_native::tooltip::Position;
|
||||
pub use iced_native::widget::tooltip::Position;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
|
|||
use core::ffi::c_void;
|
||||
use glow::HasContext;
|
||||
use iced_graphics::{Antialiasing, Size};
|
||||
use iced_native::mouse;
|
||||
|
||||
/// A window graphics backend for iced powered by `glow`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
|
@ -59,14 +58,13 @@ impl iced_graphics::window::GLCompositor for Compositor {
|
|||
}
|
||||
}
|
||||
|
||||
fn draw<T: AsRef<str>>(
|
||||
fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
viewport: &Viewport,
|
||||
color: Color,
|
||||
output: &<Self::Renderer as iced_native::Renderer>::Output,
|
||||
overlay: &[T],
|
||||
) -> mouse::Interaction {
|
||||
) {
|
||||
let gl = &self.gl;
|
||||
|
||||
let [r, g, b, a] = color.into_linear();
|
||||
|
|
@ -76,6 +74,8 @@ impl iced_graphics::window::GLCompositor for Compositor {
|
|||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
renderer.backend_mut().draw(gl, viewport, output, overlay)
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(gl, primitive, viewport, overlay);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Create interactive, native cross-platform applications.
|
||||
use crate::{mouse, Error, Executor, Runtime};
|
||||
use crate::mouse;
|
||||
use crate::{Error, Executor, Runtime};
|
||||
|
||||
pub use iced_winit::Application;
|
||||
|
||||
|
|
@ -179,10 +180,7 @@ async fn run_instance<A, E, C>(
|
|||
&mut debug,
|
||||
));
|
||||
|
||||
let mut primitive =
|
||||
user_interface.draw(&mut renderer, state.cursor_position());
|
||||
let mut mouse_interaction = mouse::Interaction::default();
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut messages = Vec::new();
|
||||
|
||||
|
|
@ -246,10 +244,18 @@ async fn run_instance<A, E, C>(
|
|||
}
|
||||
|
||||
debug.draw_started();
|
||||
primitive =
|
||||
let new_mouse_interaction =
|
||||
user_interface.draw(&mut renderer, state.cursor_position());
|
||||
debug.draw_finished();
|
||||
|
||||
if new_mouse_interaction != mouse_interaction {
|
||||
context.window().set_cursor_icon(
|
||||
conversion::mouse_interaction(new_mouse_interaction),
|
||||
);
|
||||
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
context.window().request_redraw();
|
||||
}
|
||||
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
||||
|
|
@ -291,10 +297,20 @@ async fn run_instance<A, E, C>(
|
|||
debug.layout_finished();
|
||||
|
||||
debug.draw_started();
|
||||
primitive = user_interface
|
||||
let new_mouse_interaction = user_interface
|
||||
.draw(&mut renderer, state.cursor_position());
|
||||
debug.draw_finished();
|
||||
|
||||
if new_mouse_interaction != mouse_interaction {
|
||||
context.window().set_cursor_icon(
|
||||
conversion::mouse_interaction(
|
||||
new_mouse_interaction,
|
||||
),
|
||||
);
|
||||
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
context.resize(glutin::dpi::PhysicalSize::new(
|
||||
physical_size.width,
|
||||
physical_size.height,
|
||||
|
|
@ -305,11 +321,10 @@ async fn run_instance<A, E, C>(
|
|||
viewport_version = current_viewport_version;
|
||||
}
|
||||
|
||||
let new_mouse_interaction = compositor.draw(
|
||||
compositor.present(
|
||||
&mut renderer,
|
||||
state.viewport(),
|
||||
state.background_color(),
|
||||
&primitive,
|
||||
&debug.overlay(),
|
||||
);
|
||||
|
||||
|
|
@ -317,14 +332,6 @@ async fn run_instance<A, E, C>(
|
|||
|
||||
debug.render_finished();
|
||||
|
||||
if new_mouse_interaction != mouse_interaction {
|
||||
context.window().set_cursor_icon(
|
||||
conversion::mouse_interaction(new_mouse_interaction),
|
||||
);
|
||||
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
// TODO: Handle animations!
|
||||
// Maybe we can use `ControlFlow::WaitUntil` for this.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/// on its contents.
|
||||
pub fn generate(
|
||||
primitive: &'a Primitive,
|
||||
primitives: &'a [Primitive],
|
||||
viewport: &Viewport,
|
||||
) -> Vec<Self> {
|
||||
let first_layer =
|
||||
|
|
@ -82,12 +82,14 @@ impl<'a> Layer<'a> {
|
|||
|
||||
let mut layers = vec![first_layer];
|
||||
|
||||
Self::process_primitive(
|
||||
&mut layers,
|
||||
Vector::new(0.0, 0.0),
|
||||
primitive,
|
||||
0,
|
||||
);
|
||||
for primitive in primitives {
|
||||
Self::process_primitive(
|
||||
&mut layers,
|
||||
Vector::new(0.0, 0.0),
|
||||
primitive,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
layers
|
||||
}
|
||||
|
|
@ -173,11 +175,7 @@ impl<'a> Layer<'a> {
|
|||
});
|
||||
}
|
||||
}
|
||||
Primitive::Clip {
|
||||
bounds,
|
||||
offset,
|
||||
content,
|
||||
} => {
|
||||
Primitive::Clip { bounds, content } => {
|
||||
let layer = &mut layers[current_layer];
|
||||
let translated_bounds = *bounds + translation;
|
||||
|
||||
|
|
@ -190,8 +188,7 @@ impl<'a> Layer<'a> {
|
|||
|
||||
Self::process_primitive(
|
||||
layers,
|
||||
translation
|
||||
- Vector::new(offset.x as f32, offset.y as f32),
|
||||
translation,
|
||||
content,
|
||||
layers.len() - 1,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,15 +13,14 @@
|
|||
mod antialiasing;
|
||||
mod error;
|
||||
mod primitive;
|
||||
mod renderer;
|
||||
mod transformation;
|
||||
mod viewport;
|
||||
|
||||
pub mod backend;
|
||||
pub mod defaults;
|
||||
pub mod font;
|
||||
pub mod layer;
|
||||
pub mod overlay;
|
||||
pub mod renderer;
|
||||
pub mod triangle;
|
||||
pub mod widget;
|
||||
pub mod window;
|
||||
|
|
@ -31,7 +30,6 @@ pub use widget::*;
|
|||
|
||||
pub use antialiasing::Antialiasing;
|
||||
pub use backend::Backend;
|
||||
pub use defaults::Defaults;
|
||||
pub use error::Error;
|
||||
pub use layer::Layer;
|
||||
pub use primitive::Primitive;
|
||||
|
|
|
|||
|
|
@ -1,116 +1,3 @@
|
|||
//! Build and show dropdown menus.
|
||||
use crate::alignment;
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Renderer};
|
||||
|
||||
use iced_native::{mouse, overlay, Color, Font, Padding, Point, Rectangle};
|
||||
|
||||
pub use iced_style::menu::Style;
|
||||
|
||||
impl<B> overlay::menu::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Style = Style;
|
||||
|
||||
fn decorate(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
_cursor_position: Point,
|
||||
style: &Style,
|
||||
(primitives, mouse_cursor): Self::Output,
|
||||
) -> Self::Output {
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Quad {
|
||||
bounds,
|
||||
background: style.background,
|
||||
border_color: style.border_color,
|
||||
border_width: style.border_width,
|
||||
border_radius: 0.0,
|
||||
},
|
||||
primitives,
|
||||
],
|
||||
},
|
||||
mouse_cursor,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw<T: ToString>(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
options: &[T],
|
||||
hovered_option: Option<usize>,
|
||||
padding: Padding,
|
||||
text_size: u16,
|
||||
font: Font,
|
||||
style: &Style,
|
||||
) -> Self::Output {
|
||||
use std::f32;
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let option_height = (text_size + padding.vertical()) as usize;
|
||||
|
||||
let mut primitives = Vec::new();
|
||||
|
||||
let offset = viewport.y - bounds.y;
|
||||
let start = (offset / option_height as f32) as usize;
|
||||
let end =
|
||||
((offset + viewport.height) / option_height as f32).ceil() as usize;
|
||||
|
||||
let visible_options = &options[start..end.min(options.len())];
|
||||
|
||||
for (i, option) in visible_options.iter().enumerate() {
|
||||
let i = start + i;
|
||||
let is_selected = hovered_option == Some(i);
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y + (option_height * i) as f32,
|
||||
width: bounds.width,
|
||||
height: f32::from(text_size + padding.vertical()),
|
||||
};
|
||||
|
||||
if is_selected {
|
||||
primitives.push(Primitive::Quad {
|
||||
bounds,
|
||||
background: style.selected_background,
|
||||
border_color: Color::TRANSPARENT,
|
||||
border_width: 0.0,
|
||||
border_radius: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
primitives.push(Primitive::Text {
|
||||
content: option.to_string(),
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + padding.left as f32,
|
||||
y: bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
..bounds
|
||||
},
|
||||
size: f32::from(text_size),
|
||||
font,
|
||||
color: if is_selected {
|
||||
style.selected_text_color
|
||||
} else {
|
||||
style.text_color
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
}
|
||||
|
||||
(
|
||||
Primitive::Group { primitives },
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use iced_native::{
|
||||
image, svg, Background, Color, Font, Rectangle, Size, Vector,
|
||||
};
|
||||
use iced_native::image;
|
||||
use iced_native::svg;
|
||||
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
||||
|
||||
use crate::alignment;
|
||||
use crate::triangle;
|
||||
|
|
@ -66,8 +66,6 @@ pub enum Primitive {
|
|||
Clip {
|
||||
/// The bounds of the clip
|
||||
bounds: Rectangle,
|
||||
/// The offset transformation of the clip
|
||||
offset: Vector<u32>,
|
||||
/// The content of the clip
|
||||
content: Box<Primitive>,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,30 +1,43 @@
|
|||
use crate::{Backend, Defaults, Primitive};
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::mouse;
|
||||
use iced_native::{
|
||||
Background, Color, Element, Point, Rectangle, Vector, Widget,
|
||||
};
|
||||
//! Create a renderer from a [`Backend`].
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Vector};
|
||||
use iced_native::layout;
|
||||
use iced_native::renderer;
|
||||
use iced_native::text::{self, Text};
|
||||
use iced_native::{Background, Element, Font, Point, Rectangle, Size};
|
||||
|
||||
pub use iced_native::renderer::Style;
|
||||
|
||||
/// A backend-agnostic renderer that supports all the built-in widgets.
|
||||
#[derive(Debug)]
|
||||
pub struct Renderer<B: Backend> {
|
||||
backend: B,
|
||||
primitives: Vec<Primitive>,
|
||||
}
|
||||
|
||||
impl<B: Backend> Renderer<B> {
|
||||
/// Creates a new [`Renderer`] from the given [`Backend`].
|
||||
pub fn new(backend: B) -> Self {
|
||||
Self { backend }
|
||||
Self {
|
||||
backend,
|
||||
primitives: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`Backend`] of the [`Renderer`].
|
||||
/// Returns the [`Backend`] of the [`Renderer`].
|
||||
pub fn backend(&self) -> &B {
|
||||
&self.backend
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the [`Backend`] of the [`Renderer`].
|
||||
pub fn backend_mut(&mut self) -> &mut B {
|
||||
&mut self.backend
|
||||
/// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
|
||||
pub fn draw_primitive(&mut self, primitive: Primitive) {
|
||||
self.primitives.push(primitive);
|
||||
}
|
||||
|
||||
/// Runs the given closure with the [`Backend`] and the recorded primitives
|
||||
/// of the [`Renderer`].
|
||||
pub fn with_primitives(&mut self, f: impl FnOnce(&mut B, &[Primitive])) {
|
||||
f(&mut self.backend, &self.primitives);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -32,9 +45,6 @@ impl<B> iced_native::Renderer for Renderer<B>
|
|||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Output = (Primitive, mouse::Interaction);
|
||||
type Defaults = Defaults;
|
||||
|
||||
fn layout<'a, Message>(
|
||||
&mut self,
|
||||
element: &Element<'a, Message, Self>,
|
||||
|
|
@ -47,75 +57,114 @@ where
|
|||
layout
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||
let current_primitives = std::mem::take(&mut self.primitives);
|
||||
|
||||
f(self);
|
||||
|
||||
let layer_primitives =
|
||||
std::mem::replace(&mut self.primitives, current_primitives);
|
||||
|
||||
self.primitives.push(Primitive::Clip {
|
||||
bounds,
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: layer_primitives,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
fn with_translation(
|
||||
&mut self,
|
||||
(base_primitive, base_cursor): (Primitive, mouse::Interaction),
|
||||
(overlay_primitives, overlay_cursor): (Primitive, mouse::Interaction),
|
||||
overlay_bounds: Rectangle,
|
||||
) -> (Primitive, mouse::Interaction) {
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![
|
||||
base_primitive,
|
||||
Primitive::Clip {
|
||||
bounds: Rectangle {
|
||||
width: overlay_bounds.width + 0.5,
|
||||
height: overlay_bounds.height + 0.5,
|
||||
..overlay_bounds
|
||||
},
|
||||
offset: Vector::new(0, 0),
|
||||
content: Box::new(overlay_primitives),
|
||||
},
|
||||
],
|
||||
},
|
||||
if base_cursor > overlay_cursor {
|
||||
base_cursor
|
||||
} else {
|
||||
overlay_cursor
|
||||
},
|
||||
translation: Vector,
|
||||
f: impl FnOnce(&mut Self),
|
||||
) {
|
||||
let current_primitives = std::mem::take(&mut self.primitives);
|
||||
|
||||
f(self);
|
||||
|
||||
let layer_primitives =
|
||||
std::mem::replace(&mut self.primitives, current_primitives);
|
||||
|
||||
self.primitives.push(Primitive::Translate {
|
||||
translation,
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: layer_primitives,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
fn fill_quad(
|
||||
&mut self,
|
||||
quad: renderer::Quad,
|
||||
background: impl Into<Background>,
|
||||
) {
|
||||
self.primitives.push(Primitive::Quad {
|
||||
bounds: quad.bounds,
|
||||
background: background.into(),
|
||||
border_radius: quad.border_radius,
|
||||
border_width: quad.border_width,
|
||||
border_color: quad.border_color,
|
||||
});
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.primitives.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> text::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Font = Font;
|
||||
|
||||
const ICON_FONT: Font = B::ICON_FONT;
|
||||
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
|
||||
const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON;
|
||||
|
||||
fn default_size(&self) -> u16 {
|
||||
self.backend().default_size()
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32) {
|
||||
self.backend()
|
||||
.measure(content, f32::from(size), font, bounds)
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
self.backend().hit_test(
|
||||
content,
|
||||
size,
|
||||
font,
|
||||
bounds,
|
||||
point,
|
||||
nearest_only,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> layout::Debugger for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn explain<Message>(
|
||||
&mut self,
|
||||
defaults: &Defaults,
|
||||
widget: &dyn Widget<Message, Self>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
color: Color,
|
||||
) -> Self::Output {
|
||||
let (primitive, cursor) =
|
||||
widget.draw(self, defaults, layout, cursor_position, viewport);
|
||||
|
||||
let mut primitives = Vec::new();
|
||||
|
||||
explain_layout(layout, color, &mut primitives);
|
||||
primitives.push(primitive);
|
||||
|
||||
(Primitive::Group { primitives }, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_layout(
|
||||
layout: Layout<'_>,
|
||||
color: Color,
|
||||
primitives: &mut Vec<Primitive>,
|
||||
) {
|
||||
primitives.push(Primitive::Quad {
|
||||
bounds: layout.bounds(),
|
||||
background: Background::Color(Color::TRANSPARENT),
|
||||
border_radius: 0.0,
|
||||
border_width: 1.0,
|
||||
border_color: color,
|
||||
});
|
||||
|
||||
for child in layout.children() {
|
||||
explain_layout(child, color, primitives);
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content.to_string(),
|
||||
bounds: text.bounds,
|
||||
size: text.size,
|
||||
color: text.color,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +1,12 @@
|
|||
//! Allow your users to perform actions by pressing a button.
|
||||
//!
|
||||
//! A [`Button`] has some local [`State`].
|
||||
use crate::defaults::{self, Defaults};
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::{
|
||||
Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
|
||||
};
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_native::button::State;
|
||||
pub use iced_style::button::{Style, StyleSheet};
|
||||
pub use iced_native::widget::button::{State, Style, StyleSheet};
|
||||
|
||||
/// A widget that produces a message when clicked.
|
||||
///
|
||||
/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`.
|
||||
pub type Button<'a, Message, Backend> =
|
||||
iced_native::Button<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> iced_native::button::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
_defaults: &Defaults,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
is_disabled: bool,
|
||||
is_pressed: bool,
|
||||
style: &Box<dyn StyleSheet>,
|
||||
content: &Element<'_, Message, Self>,
|
||||
content_layout: Layout<'_>,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let styling = if is_disabled {
|
||||
style.disabled()
|
||||
} else if is_mouse_over {
|
||||
if is_pressed {
|
||||
style.pressed()
|
||||
} else {
|
||||
style.hovered()
|
||||
}
|
||||
} else {
|
||||
style.active()
|
||||
};
|
||||
|
||||
let (content, _) = content.draw(
|
||||
self,
|
||||
&Defaults {
|
||||
text: defaults::Text {
|
||||
color: styling.text_color,
|
||||
},
|
||||
},
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&bounds,
|
||||
);
|
||||
|
||||
(
|
||||
if styling.background.is_some() || styling.border_width > 0.0 {
|
||||
let background = Primitive::Quad {
|
||||
bounds,
|
||||
background: styling
|
||||
.background
|
||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||
border_radius: styling.border_radius,
|
||||
border_width: styling.border_width,
|
||||
border_color: styling.border_color,
|
||||
};
|
||||
|
||||
if styling.shadow_offset == Vector::default() {
|
||||
Primitive::Group {
|
||||
primitives: vec![background, content],
|
||||
}
|
||||
} else {
|
||||
// TODO: Implement proper shadow support
|
||||
let shadow = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + styling.shadow_offset.x,
|
||||
y: bounds.y + styling.shadow_offset.y,
|
||||
..bounds
|
||||
},
|
||||
background: Background::Color(
|
||||
[0.0, 0.0, 0.0, 0.5].into(),
|
||||
),
|
||||
border_radius: styling.border_radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
};
|
||||
|
||||
Primitive::Group {
|
||||
primitives: vec![shadow, background, content],
|
||||
}
|
||||
}
|
||||
} else {
|
||||
content
|
||||
},
|
||||
if is_mouse_over && !is_disabled {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
iced_native::widget::Button<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
|
||||
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
|
||||
//! and more!
|
||||
use crate::{Backend, Defaults, Primitive, Renderer};
|
||||
use crate::renderer::{self, Renderer};
|
||||
use crate::{Backend, Primitive};
|
||||
|
||||
use iced_native::layout;
|
||||
use iced_native::mouse;
|
||||
use iced_native::{
|
||||
|
|
@ -186,32 +188,42 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_renderer: &mut Renderer<B>,
|
||||
_defaults: &Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> (Primitive, mouse::Interaction) {
|
||||
) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let cursor = Cursor::from_window_position(cursor_position);
|
||||
|
||||
self.program.mouse_interaction(bounds, cursor)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer<B>,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
use iced_native::Renderer as _;
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let translation = Vector::new(bounds.x, bounds.y);
|
||||
let cursor = Cursor::from_window_position(cursor_position);
|
||||
|
||||
(
|
||||
Primitive::Translate {
|
||||
translation,
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: self
|
||||
.program
|
||||
.draw(bounds, cursor)
|
||||
.into_iter()
|
||||
.map(Geometry::into_primitive)
|
||||
.collect(),
|
||||
}),
|
||||
},
|
||||
self.program.mouse_interaction(bounds, cursor),
|
||||
)
|
||||
renderer.with_translation(translation, |renderer| {
|
||||
renderer.draw_primitive(Primitive::Group {
|
||||
primitives: self
|
||||
.program
|
||||
.draw(bounds, cursor)
|
||||
.into_iter()
|
||||
.map(Geometry::into_primitive)
|
||||
.collect(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
|
|||
|
|
@ -1,77 +1,10 @@
|
|||
//! Show toggle controls using checkboxes.
|
||||
use crate::alignment;
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Rectangle, Renderer};
|
||||
|
||||
use iced_native::checkbox;
|
||||
use iced_native::mouse;
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_style::checkbox::{Style, StyleSheet};
|
||||
|
||||
/// A box that can be checked.
|
||||
///
|
||||
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||
pub type Checkbox<Message, Backend> =
|
||||
iced_native::Checkbox<Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> checkbox::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
const DEFAULT_SIZE: u16 = 20;
|
||||
const DEFAULT_SPACING: u16 = 15;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
is_checked: bool,
|
||||
is_mouse_over: bool,
|
||||
(label, _): Self::Output,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let style = if is_mouse_over {
|
||||
style_sheet.hovered(is_checked)
|
||||
} else {
|
||||
style_sheet.active(is_checked)
|
||||
};
|
||||
|
||||
let checkbox = Primitive::Quad {
|
||||
bounds,
|
||||
background: style.background,
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: if is_checked {
|
||||
let check = Primitive::Text {
|
||||
content: B::CHECKMARK_ICON.to_string(),
|
||||
font: B::ICON_FONT,
|
||||
size: bounds.height * 0.7,
|
||||
bounds: Rectangle {
|
||||
x: bounds.center_x(),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
color: style.checkmark_color,
|
||||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
};
|
||||
|
||||
vec![checkbox, check, label]
|
||||
} else {
|
||||
vec![checkbox, label]
|
||||
},
|
||||
},
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
pub type Checkbox<'a, Message, Backend> =
|
||||
iced_native::widget::Checkbox<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,49 +1,5 @@
|
|||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::column;
|
||||
use iced_native::mouse;
|
||||
use iced_native::{Element, Layout, Point, Rectangle};
|
||||
use crate::Renderer;
|
||||
|
||||
/// A container that distributes its contents vertically.
|
||||
pub type Column<'a, Message, Backend> =
|
||||
iced_native::Column<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> column::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
content: &[Element<'_, Message, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output {
|
||||
let mut mouse_interaction = mouse::Interaction::default();
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: content
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
let (primitive, new_mouse_interaction) = child.draw(
|
||||
self,
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if new_mouse_interaction > mouse_interaction {
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
primitive
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
mouse_interaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
iced_native::widget::Column<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
//! Decorate content and apply alignment.
|
||||
use crate::container;
|
||||
use crate::defaults::{self, Defaults};
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::{Background, Color, Element, Layout, Point, Rectangle};
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_style::container::{Style, StyleSheet};
|
||||
|
||||
|
|
@ -11,68 +8,4 @@ pub use iced_style::container::{Style, StyleSheet};
|
|||
/// This is an alias of an `iced_native` container with a default
|
||||
/// `Renderer`.
|
||||
pub type Container<'a, Message, Backend> =
|
||||
iced_native::Container<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> iced_native::container::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn container::StyleSheet>;
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Defaults,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
style_sheet: &Self::Style,
|
||||
content: &Element<'_, Message, Self>,
|
||||
content_layout: Layout<'_>,
|
||||
) -> Self::Output {
|
||||
let style = style_sheet.style();
|
||||
|
||||
let defaults = Defaults {
|
||||
text: defaults::Text {
|
||||
color: style.text_color.unwrap_or(defaults.text.color),
|
||||
},
|
||||
};
|
||||
|
||||
let (content, mouse_interaction) = content.draw(
|
||||
self,
|
||||
&defaults,
|
||||
content_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if let Some(background) = background(bounds, &style) {
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![background, content],
|
||||
},
|
||||
mouse_interaction,
|
||||
)
|
||||
} else {
|
||||
(content, mouse_interaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn background(
|
||||
bounds: Rectangle,
|
||||
style: &container::Style,
|
||||
) -> Option<Primitive> {
|
||||
if style.background.is_some() || style.border_width > 0.0 {
|
||||
Some(Primitive::Quad {
|
||||
bounds,
|
||||
background: style
|
||||
.background
|
||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
iced_native::widget::Container<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
pub mod viewer;
|
||||
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Rectangle, Renderer};
|
||||
|
||||
use crate::{Primitive, Renderer};
|
||||
use iced_native::image;
|
||||
use iced_native::mouse;
|
||||
use iced_native::Layout;
|
||||
|
||||
pub use iced_native::image::{Handle, Image, Viewer};
|
||||
pub use iced_native::widget::image::{Image, Viewer};
|
||||
pub use image::Handle;
|
||||
|
||||
impl<B> image::Renderer for Renderer<B>
|
||||
where
|
||||
|
|
@ -18,17 +17,7 @@ where
|
|||
self.backend().dimensions(handle)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
handle: image::Handle,
|
||||
layout: Layout<'_>,
|
||||
) -> Self::Output {
|
||||
(
|
||||
Primitive::Image {
|
||||
handle,
|
||||
bounds: layout.bounds(),
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
|
||||
self.draw_primitive(Primitive::Image { handle, bounds })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,2 @@
|
|||
//! Zoom and pan on an image.
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Renderer};
|
||||
|
||||
use iced_native::image;
|
||||
use iced_native::image::viewer;
|
||||
use iced_native::mouse;
|
||||
use iced_native::{Rectangle, Size, Vector};
|
||||
|
||||
impl<B> viewer::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Image,
|
||||
{
|
||||
fn draw(
|
||||
&mut self,
|
||||
state: &viewer::State,
|
||||
bounds: Rectangle,
|
||||
image_size: Size,
|
||||
translation: Vector,
|
||||
handle: image::Handle,
|
||||
is_mouse_over: bool,
|
||||
) -> Self::Output {
|
||||
(
|
||||
{
|
||||
Primitive::Clip {
|
||||
bounds,
|
||||
content: Box::new(Primitive::Translate {
|
||||
translation,
|
||||
content: Box::new(Primitive::Image {
|
||||
handle,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
..Rectangle::with_size(image_size)
|
||||
},
|
||||
}),
|
||||
}),
|
||||
offset: Vector::new(0, 0),
|
||||
}
|
||||
},
|
||||
{
|
||||
if state.is_cursor_grabbed() {
|
||||
mouse::Interaction::Grabbing
|
||||
} else if is_mouse_over
|
||||
&& (image_size.width > bounds.width
|
||||
|| image_size.height > bounds.height)
|
||||
{
|
||||
mouse::Interaction::Grab
|
||||
} else {
|
||||
mouse::Interaction::Idle
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
pub use iced_native::widget::image::Viewer;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,9 @@
|
|||
//! drag and drop, and hotkey support.
|
||||
//!
|
||||
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
|
||||
use crate::defaults;
|
||||
use crate::{Backend, Color, Primitive, Renderer};
|
||||
use iced_native::container;
|
||||
use iced_native::mouse;
|
||||
use iced_native::pane_grid;
|
||||
use iced_native::{Element, Layout, Point, Rectangle, Vector};
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_native::pane_grid::{
|
||||
pub use iced_native::widget::pane_grid::{
|
||||
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
|
||||
ResizeEvent, Split, State, TitleBar,
|
||||
};
|
||||
|
|
@ -28,277 +23,4 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
|
|||
///
|
||||
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
|
||||
pub type PaneGrid<'a, Message, Backend> =
|
||||
iced_native::PaneGrid<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> pane_grid::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
content: &[(Pane, Content<'_, Message, Self>)],
|
||||
dragging: Option<(Pane, Point)>,
|
||||
resizing: Option<(Axis, Rectangle, bool)>,
|
||||
layout: Layout<'_>,
|
||||
style_sheet: &<Self as pane_grid::Renderer>::Style,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output {
|
||||
let pane_cursor_position = if dragging.is_some() {
|
||||
// TODO: Remove once cursor availability is encoded in the type
|
||||
// system
|
||||
Point::new(-1.0, -1.0)
|
||||
} else {
|
||||
cursor_position
|
||||
};
|
||||
|
||||
let mut mouse_interaction = mouse::Interaction::default();
|
||||
let mut dragged_pane = None;
|
||||
|
||||
let mut panes: Vec<_> = content
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.enumerate()
|
||||
.map(|(i, ((id, pane), layout))| {
|
||||
let (primitive, new_mouse_interaction) = pane.draw(
|
||||
self,
|
||||
defaults,
|
||||
layout,
|
||||
pane_cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if new_mouse_interaction > mouse_interaction {
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
if let Some((dragging, origin)) = dragging {
|
||||
if *id == dragging {
|
||||
dragged_pane = Some((i, layout, origin));
|
||||
}
|
||||
}
|
||||
|
||||
primitive
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut primitives = if let Some((index, layout, origin)) = dragged_pane
|
||||
{
|
||||
let pane = panes.remove(index);
|
||||
let bounds = layout.bounds();
|
||||
|
||||
// TODO: Fix once proper layering is implemented.
|
||||
// This is a pretty hacky way to achieve layering.
|
||||
let clip = Primitive::Clip {
|
||||
bounds: Rectangle {
|
||||
x: cursor_position.x - origin.x,
|
||||
y: cursor_position.y - origin.y,
|
||||
width: bounds.width + 0.5,
|
||||
height: bounds.height + 0.5,
|
||||
},
|
||||
offset: Vector::new(0, 0),
|
||||
content: Box::new(Primitive::Translate {
|
||||
translation: Vector::new(
|
||||
cursor_position.x - bounds.x - origin.x,
|
||||
cursor_position.y - bounds.y - origin.y,
|
||||
),
|
||||
content: Box::new(pane),
|
||||
}),
|
||||
};
|
||||
|
||||
panes.push(clip);
|
||||
|
||||
panes
|
||||
} else {
|
||||
panes
|
||||
};
|
||||
|
||||
let (primitives, mouse_interaction) =
|
||||
if let Some((axis, split_region, is_picked)) = resizing {
|
||||
let highlight = if is_picked {
|
||||
style_sheet.picked_split()
|
||||
} else {
|
||||
style_sheet.hovered_split()
|
||||
};
|
||||
|
||||
if let Some(highlight) = highlight {
|
||||
primitives.push(Primitive::Quad {
|
||||
bounds: match axis {
|
||||
Axis::Horizontal => Rectangle {
|
||||
x: split_region.x,
|
||||
y: (split_region.y
|
||||
+ (split_region.height - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
width: split_region.width,
|
||||
height: highlight.width,
|
||||
},
|
||||
Axis::Vertical => Rectangle {
|
||||
x: (split_region.x
|
||||
+ (split_region.width - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
y: split_region.y,
|
||||
width: highlight.width,
|
||||
height: split_region.height,
|
||||
},
|
||||
},
|
||||
background: highlight.color.into(),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
});
|
||||
}
|
||||
|
||||
(
|
||||
primitives,
|
||||
match axis {
|
||||
Axis::Horizontal => {
|
||||
mouse::Interaction::ResizingVertically
|
||||
}
|
||||
Axis::Vertical => {
|
||||
mouse::Interaction::ResizingHorizontally
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(primitives, mouse_interaction)
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group { primitives },
|
||||
if dragging.is_some() {
|
||||
mouse::Interaction::Grabbing
|
||||
} else {
|
||||
mouse_interaction
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn draw_pane<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
style_sheet: &<Self as container::Renderer>::Style,
|
||||
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
|
||||
body: (&Element<'_, Message, Self>, Layout<'_>),
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output {
|
||||
let style = style_sheet.style();
|
||||
let (body, body_layout) = body;
|
||||
|
||||
let (body_primitive, body_interaction) =
|
||||
body.draw(self, defaults, body_layout, cursor_position, viewport);
|
||||
|
||||
let background = crate::widget::container::background(bounds, &style);
|
||||
|
||||
if let Some((title_bar, title_bar_layout)) = title_bar {
|
||||
let show_controls = bounds.contains(cursor_position);
|
||||
let is_over_pick_area =
|
||||
title_bar.is_over_pick_area(title_bar_layout, cursor_position);
|
||||
|
||||
let (title_bar_primitive, title_bar_interaction) = title_bar.draw(
|
||||
self,
|
||||
defaults,
|
||||
title_bar_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
show_controls,
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![
|
||||
background.unwrap_or(Primitive::None),
|
||||
title_bar_primitive,
|
||||
body_primitive,
|
||||
],
|
||||
},
|
||||
if title_bar_interaction > body_interaction {
|
||||
title_bar_interaction
|
||||
} else if is_over_pick_area {
|
||||
mouse::Interaction::Grab
|
||||
} else {
|
||||
body_interaction
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(
|
||||
if let Some(background) = background {
|
||||
Primitive::Group {
|
||||
primitives: vec![background, body_primitive],
|
||||
}
|
||||
} else {
|
||||
body_primitive
|
||||
},
|
||||
body_interaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_title_bar<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
style_sheet: &<Self as container::Renderer>::Style,
|
||||
content: (&Element<'_, Message, Self>, Layout<'_>),
|
||||
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output {
|
||||
let style = style_sheet.style();
|
||||
let (title_content, title_layout) = content;
|
||||
|
||||
let defaults = Self::Defaults {
|
||||
text: defaults::Text {
|
||||
color: style.text_color.unwrap_or(defaults.text.color),
|
||||
},
|
||||
};
|
||||
|
||||
let background = crate::widget::container::background(bounds, &style);
|
||||
|
||||
let (title_primitive, title_interaction) = title_content.draw(
|
||||
self,
|
||||
&defaults,
|
||||
title_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if let Some((controls, controls_layout)) = controls {
|
||||
let (controls_primitive, controls_interaction) = controls.draw(
|
||||
self,
|
||||
&defaults,
|
||||
controls_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![
|
||||
background.unwrap_or(Primitive::None),
|
||||
title_primitive,
|
||||
controls_primitive,
|
||||
],
|
||||
},
|
||||
controls_interaction.max(title_interaction),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
if let Some(background) = background {
|
||||
Primitive::Group {
|
||||
primitives: vec![background, title_primitive],
|
||||
}
|
||||
} else {
|
||||
title_primitive
|
||||
},
|
||||
title_interaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
iced_native::widget::PaneGrid<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,103 +1,9 @@
|
|||
//! Display a dropdown list of selectable values.
|
||||
use crate::alignment;
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Renderer};
|
||||
use crate::Renderer;
|
||||
|
||||
use iced_native::{mouse, Font, Padding, Point, Rectangle};
|
||||
use iced_style::menu;
|
||||
|
||||
pub use iced_native::pick_list::State;
|
||||
pub use iced_native::widget::pick_list::State;
|
||||
pub use iced_style::pick_list::{Style, StyleSheet};
|
||||
|
||||
/// A widget allowing the selection of a single value from a list of options.
|
||||
pub type PickList<'a, T, Message, Backend> =
|
||||
iced_native::PickList<'a, T, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> iced_native::pick_list::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||
|
||||
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
|
||||
style.menu()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
selected: Option<String>,
|
||||
placeholder: Option<&str>,
|
||||
padding: Padding,
|
||||
text_size: u16,
|
||||
font: Font,
|
||||
style: &Box<dyn StyleSheet>,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let is_selected = selected.is_some();
|
||||
|
||||
let style = if is_mouse_over {
|
||||
style.hovered()
|
||||
} else {
|
||||
style.active()
|
||||
};
|
||||
|
||||
let background = Primitive::Quad {
|
||||
bounds,
|
||||
background: style.background,
|
||||
border_color: style.border_color,
|
||||
border_width: style.border_width,
|
||||
border_radius: style.border_radius,
|
||||
};
|
||||
|
||||
let arrow_down = Primitive::Text {
|
||||
content: B::ARROW_DOWN_ICON.to_string(),
|
||||
font: B::ICON_FONT,
|
||||
size: bounds.height * style.icon_size,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + bounds.width - f32::from(padding.horizontal()),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
color: style.text_color,
|
||||
horizontal_alignment: alignment::Horizontal::Right,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: if let Some(label) =
|
||||
selected.or_else(|| placeholder.map(str::to_string))
|
||||
{
|
||||
let label = Primitive::Text {
|
||||
content: label,
|
||||
size: f32::from(text_size),
|
||||
font,
|
||||
color: is_selected
|
||||
.then(|| style.text_color)
|
||||
.unwrap_or(style.placeholder_color),
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + f32::from(padding.left),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
};
|
||||
|
||||
vec![background, label, arrow_down]
|
||||
} else {
|
||||
vec![background, arrow_down]
|
||||
},
|
||||
},
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
iced_native::widget::PickList<'a, T, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -2,73 +2,4 @@
|
|||
//!
|
||||
//! A [`ProgressBar`] has a range of possible values and a current value,
|
||||
//! as well as a length, height and style.
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::progress_bar;
|
||||
use iced_native::{Color, Rectangle};
|
||||
|
||||
pub use iced_style::progress_bar::{Style, StyleSheet};
|
||||
|
||||
/// A bar that displays progress.
|
||||
///
|
||||
/// This is an alias of an `iced_native` progress bar with an
|
||||
/// `iced_wgpu::Renderer`.
|
||||
pub type ProgressBar<Backend> = iced_native::ProgressBar<Renderer<Backend>>;
|
||||
|
||||
impl<B> progress_bar::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
const DEFAULT_HEIGHT: u16 = 30;
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
range: std::ops::RangeInclusive<f32>,
|
||||
value: f32,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let style = style_sheet.style();
|
||||
let (range_start, range_end) = range.into_inner();
|
||||
|
||||
let active_progress_width = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
bounds.width * (value - range_start) / (range_end - range_start)
|
||||
};
|
||||
|
||||
let background = Primitive::Group {
|
||||
primitives: vec![Primitive::Quad {
|
||||
bounds: Rectangle { ..bounds },
|
||||
background: style.background,
|
||||
border_radius: style.border_radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
}],
|
||||
};
|
||||
|
||||
(
|
||||
if active_progress_width > 0.0 {
|
||||
let bar = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
width: active_progress_width,
|
||||
..bounds
|
||||
},
|
||||
background: style.bar,
|
||||
border_radius: style.border_radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
};
|
||||
|
||||
Primitive::Group {
|
||||
primitives: vec![background, bar],
|
||||
}
|
||||
} else {
|
||||
background
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
pub use iced_native::widget::progress_bar::*;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
//! Encode and display information in a QR code.
|
||||
use crate::canvas;
|
||||
use crate::{Backend, Defaults, Primitive, Renderer, Vector};
|
||||
use crate::renderer::{self, Renderer};
|
||||
use crate::Backend;
|
||||
|
||||
use iced_native::layout;
|
||||
use iced_native::{
|
||||
layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle,
|
||||
Size, Widget,
|
||||
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
||||
Widget,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -80,12 +82,14 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_renderer: &mut Renderer<B>,
|
||||
_defaults: &Defaults,
|
||||
renderer: &mut Renderer<B>,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> (Primitive, mouse::Interaction) {
|
||||
) {
|
||||
use iced_native::Renderer as _;
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let side_length = self.state.width + 2 * QUIET_ZONE;
|
||||
|
||||
|
|
@ -122,13 +126,11 @@ where
|
|||
});
|
||||
});
|
||||
|
||||
(
|
||||
Primitive::Translate {
|
||||
translation: Vector::new(bounds.x, bounds.y),
|
||||
content: Box::new(geometry.into_primitive()),
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
let translation = Vector::new(bounds.x, bounds.y);
|
||||
|
||||
renderer.with_translation(translation, |renderer| {
|
||||
renderer.draw_primitive(geometry.into_primitive());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
//! Create choices using radio buttons.
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::radio;
|
||||
use iced_native::{Background, Color, Rectangle};
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_style::radio::{Style, StyleSheet};
|
||||
|
||||
|
|
@ -10,69 +7,5 @@ pub use iced_style::radio::{Style, StyleSheet};
|
|||
///
|
||||
/// This is an alias of an `iced_native` radio button with an
|
||||
/// `iced_wgpu::Renderer`.
|
||||
pub type Radio<Message, Backend> =
|
||||
iced_native::Radio<Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> radio::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
const DEFAULT_SIZE: u16 = 28;
|
||||
const DEFAULT_SPACING: u16 = 15;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
is_selected: bool,
|
||||
is_mouse_over: bool,
|
||||
(label, _): Self::Output,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let style = if is_mouse_over {
|
||||
style_sheet.hovered()
|
||||
} else {
|
||||
style_sheet.active()
|
||||
};
|
||||
|
||||
let size = bounds.width;
|
||||
let dot_size = size / 2.0;
|
||||
|
||||
let radio = Primitive::Quad {
|
||||
bounds,
|
||||
background: style.background,
|
||||
border_radius: size / 2.0,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: if is_selected {
|
||||
let radio_circle = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + dot_size / 2.0,
|
||||
y: bounds.y + dot_size / 2.0,
|
||||
width: bounds.width - dot_size,
|
||||
height: bounds.height - dot_size,
|
||||
},
|
||||
background: Background::Color(style.dot_color),
|
||||
border_radius: dot_size / 2.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
};
|
||||
|
||||
vec![radio, radio_circle, label]
|
||||
} else {
|
||||
vec![radio, label]
|
||||
},
|
||||
},
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
pub type Radio<'a, Message, Backend> =
|
||||
iced_native::widget::Radio<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,49 +1,5 @@
|
|||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::row;
|
||||
use iced_native::{Element, Layout, Point, Rectangle};
|
||||
use crate::Renderer;
|
||||
|
||||
/// A container that distributes its contents horizontally.
|
||||
pub type Row<'a, Message, Backend> =
|
||||
iced_native::Row<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> row::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
content: &[Element<'_, Message, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output {
|
||||
let mut mouse_interaction = mouse::Interaction::default();
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: content
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
let (primitive, new_mouse_interaction) = child.draw(
|
||||
self,
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if new_mouse_interaction > mouse_interaction {
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
primitive
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
mouse_interaction,
|
||||
)
|
||||
}
|
||||
}
|
||||
iced_native::widget::Row<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,73 +1,3 @@
|
|||
//! Display a horizontal or vertical rule for dividing content.
|
||||
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::rule;
|
||||
use iced_native::{Background, Color, Rectangle};
|
||||
|
||||
pub use iced_style::rule::{FillMode, Style, StyleSheet};
|
||||
|
||||
/// Display a horizontal or vertical rule for dividing content.
|
||||
///
|
||||
/// This is an alias of an `iced_native` rule with an `iced_graphics::Renderer`.
|
||||
pub type Rule<Backend> = iced_native::Rule<Renderer<Backend>>;
|
||||
|
||||
impl<B> rule::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
style_sheet: &Self::Style,
|
||||
is_horizontal: bool,
|
||||
) -> Self::Output {
|
||||
let style = style_sheet.style();
|
||||
|
||||
let line = if is_horizontal {
|
||||
let line_y = (bounds.y + (bounds.height / 2.0)
|
||||
- (style.width as f32 / 2.0))
|
||||
.round();
|
||||
|
||||
let (offset, line_width) = style.fill_mode.fill(bounds.width);
|
||||
let line_x = bounds.x + offset;
|
||||
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: line_x,
|
||||
y: line_y,
|
||||
width: line_width,
|
||||
height: style.width as f32,
|
||||
},
|
||||
background: Background::Color(style.color),
|
||||
border_radius: style.radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
}
|
||||
} else {
|
||||
let line_x = (bounds.x + (bounds.width / 2.0)
|
||||
- (style.width as f32 / 2.0))
|
||||
.round();
|
||||
|
||||
let (offset, line_height) = style.fill_mode.fill(bounds.height);
|
||||
let line_y = bounds.y + offset;
|
||||
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: line_x,
|
||||
y: line_y,
|
||||
width: style.width as f32,
|
||||
height: line_height,
|
||||
},
|
||||
background: Background::Color(style.color),
|
||||
border_radius: style.radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
}
|
||||
};
|
||||
|
||||
(line, mouse::Interaction::default())
|
||||
}
|
||||
}
|
||||
pub use iced_native::widget::rule::*;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
//! Navigate an endless amount of content with a scrollbar.
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::scrollable;
|
||||
use iced_native::{Background, Color, Rectangle, Vector};
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_native::scrollable::State;
|
||||
pub use iced_native::widget::scrollable::State;
|
||||
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
|
||||
|
||||
/// A widget that can vertically display an infinite amount of content
|
||||
|
|
@ -13,146 +10,4 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
|
|||
/// This is an alias of an `iced_native` scrollable with a default
|
||||
/// `Renderer`.
|
||||
pub type Scrollable<'a, Message, Backend> =
|
||||
iced_native::Scrollable<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> scrollable::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn iced_style::scrollable::StyleSheet>;
|
||||
|
||||
fn scrollbar(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
offset: u32,
|
||||
scrollbar_width: u16,
|
||||
scrollbar_margin: u16,
|
||||
scroller_width: u16,
|
||||
) -> Option<scrollable::Scrollbar> {
|
||||
if content_bounds.height > bounds.height {
|
||||
let outer_width =
|
||||
scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
|
||||
|
||||
let outer_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width - outer_width as f32,
|
||||
y: bounds.y,
|
||||
width: outer_width as f32,
|
||||
height: bounds.height,
|
||||
};
|
||||
|
||||
let scrollbar_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width
|
||||
- f32::from(outer_width / 2 + scrollbar_width / 2),
|
||||
y: bounds.y,
|
||||
width: scrollbar_width as f32,
|
||||
height: bounds.height,
|
||||
};
|
||||
|
||||
let ratio = bounds.height / content_bounds.height;
|
||||
let scroller_height = bounds.height * ratio;
|
||||
let y_offset = offset as f32 * ratio;
|
||||
|
||||
let scroller_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width
|
||||
- f32::from(outer_width / 2 + scroller_width / 2),
|
||||
y: scrollbar_bounds.y + y_offset,
|
||||
width: scroller_width as f32,
|
||||
height: scroller_height,
|
||||
};
|
||||
|
||||
Some(scrollable::Scrollbar {
|
||||
outer_bounds,
|
||||
bounds: scrollbar_bounds,
|
||||
margin: scrollbar_margin,
|
||||
scroller: scrollable::Scroller {
|
||||
bounds: scroller_bounds,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
state: &scrollable::State,
|
||||
bounds: Rectangle,
|
||||
_content_bounds: Rectangle,
|
||||
is_mouse_over: bool,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
scrollbar: Option<scrollable::Scrollbar>,
|
||||
offset: u32,
|
||||
style_sheet: &Self::Style,
|
||||
(content, mouse_interaction): Self::Output,
|
||||
) -> Self::Output {
|
||||
(
|
||||
if let Some(scrollbar) = scrollbar {
|
||||
let clip = Primitive::Clip {
|
||||
bounds,
|
||||
offset: Vector::new(0, offset),
|
||||
content: Box::new(content),
|
||||
};
|
||||
|
||||
let style = if state.is_scroller_grabbed() {
|
||||
style_sheet.dragging()
|
||||
} else if is_mouse_over_scrollbar {
|
||||
style_sheet.hovered()
|
||||
} else {
|
||||
style_sheet.active()
|
||||
};
|
||||
|
||||
let is_scrollbar_visible =
|
||||
style.background.is_some() || style.border_width > 0.0;
|
||||
|
||||
let scroller = if is_mouse_over
|
||||
|| state.is_scroller_grabbed()
|
||||
|| is_scrollbar_visible
|
||||
{
|
||||
Primitive::Quad {
|
||||
bounds: scrollbar.scroller.bounds,
|
||||
background: Background::Color(style.scroller.color),
|
||||
border_radius: style.scroller.border_radius,
|
||||
border_width: style.scroller.border_width,
|
||||
border_color: style.scroller.border_color,
|
||||
}
|
||||
} else {
|
||||
Primitive::None
|
||||
};
|
||||
|
||||
let scrollbar = if is_scrollbar_visible {
|
||||
Primitive::Quad {
|
||||
bounds: scrollbar.bounds,
|
||||
background: style
|
||||
.background
|
||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
}
|
||||
} else {
|
||||
Primitive::None
|
||||
};
|
||||
|
||||
let scroll = Primitive::Clip {
|
||||
bounds,
|
||||
offset: Vector::new(0, 0),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: vec![scrollbar, scroller],
|
||||
}),
|
||||
};
|
||||
|
||||
Primitive::Group {
|
||||
primitives: vec![clip, scroll],
|
||||
}
|
||||
} else {
|
||||
content
|
||||
},
|
||||
if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
|
||||
mouse::Interaction::Idle
|
||||
} else {
|
||||
mouse_interaction
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
iced_native::widget::Scrollable<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,123 +1,5 @@
|
|||
//! Display an interactive selector of a single value from a range of values.
|
||||
//!
|
||||
//! A [`Slider`] has some local [`State`].
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::slider;
|
||||
use iced_native::{Background, Color, Point, Rectangle};
|
||||
|
||||
pub use iced_native::slider::State;
|
||||
pub use iced_native::widget::slider::{Slider, State};
|
||||
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
///
|
||||
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
|
||||
pub type Slider<'a, T, Message, Backend> =
|
||||
iced_native::Slider<'a, T, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> slider::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
const DEFAULT_HEIGHT: u16 = 22;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
range: std::ops::RangeInclusive<f32>,
|
||||
value: f32,
|
||||
is_dragging: bool,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let style = if is_dragging {
|
||||
style_sheet.dragging()
|
||||
} else if is_mouse_over {
|
||||
style_sheet.hovered()
|
||||
} else {
|
||||
style_sheet.active()
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||
|
||||
let (rail_top, rail_bottom) = (
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
background: Background::Color(style.rail_colors.0),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y + 2.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
background: Background::Color(style.rail_colors.1),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) = match style
|
||||
.handle
|
||||
.shape
|
||||
{
|
||||
HandleShape::Circle { radius } => {
|
||||
(radius * 2.0, radius * 2.0, radius)
|
||||
}
|
||||
HandleShape::Rectangle {
|
||||
width,
|
||||
border_radius,
|
||||
} => (f32::from(width), f32::from(bounds.height), border_radius),
|
||||
};
|
||||
|
||||
let (range_start, range_end) = range.into_inner();
|
||||
|
||||
let handle_offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.width - handle_width) * (value - range_start)
|
||||
/ (range_end - range_start)
|
||||
};
|
||||
|
||||
let handle = Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
y: rail_y - handle_height / 2.0,
|
||||
width: handle_width,
|
||||
height: handle_height,
|
||||
},
|
||||
background: Background::Color(style.handle.color),
|
||||
border_radius: handle_border_radius,
|
||||
border_width: style.handle.border_width,
|
||||
border_color: style.handle.border_color,
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![rail_top, rail_bottom, handle],
|
||||
},
|
||||
if is_dragging {
|
||||
mouse::Interaction::Grabbing
|
||||
} else if is_mouse_over {
|
||||
mouse::Interaction::Grab
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1 @@
|
|||
use crate::{Backend, Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::space;
|
||||
use iced_native::Rectangle;
|
||||
|
||||
pub use iced_native::Space;
|
||||
|
||||
impl<B> space::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn draw(&mut self, _bounds: Rectangle) -> Self::Output {
|
||||
(Primitive::None, mouse::Interaction::default())
|
||||
}
|
||||
}
|
||||
pub use iced_native::widget::Space;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
//! Display vector graphics in your application.
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{mouse, svg, Layout};
|
||||
use crate::{Primitive, Rectangle, Renderer};
|
||||
use iced_native::svg;
|
||||
|
||||
pub use iced_native::svg::{Handle, Svg};
|
||||
pub use iced_native::widget::svg::Svg;
|
||||
pub use svg::Handle;
|
||||
|
||||
impl<B> svg::Renderer for Renderer<B>
|
||||
where
|
||||
|
|
@ -13,17 +14,7 @@ where
|
|||
self.backend().viewport_dimensions(handle)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
handle: svg::Handle,
|
||||
layout: Layout<'_>,
|
||||
) -> Self::Output {
|
||||
(
|
||||
Primitive::Svg {
|
||||
handle,
|
||||
bounds: layout.bounds(),
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
|
||||
self.draw_primitive(Primitive::Svg { handle, bounds })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +1,7 @@
|
|||
//! Write some text for your users to read.
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Renderer};
|
||||
use iced_native::alignment;
|
||||
use iced_native::mouse;
|
||||
use iced_native::text;
|
||||
use iced_native::{Color, Font, Point, Rectangle, Size};
|
||||
use crate::Renderer;
|
||||
|
||||
/// A paragraph of text.
|
||||
///
|
||||
/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`.
|
||||
pub type Text<Backend> = iced_native::Text<Renderer<Backend>>;
|
||||
|
||||
use std::f32;
|
||||
|
||||
impl<B> text::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Font = Font;
|
||||
|
||||
fn default_size(&self) -> u16 {
|
||||
self.backend().default_size()
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32) {
|
||||
self.backend()
|
||||
.measure(content, f32::from(size), font, bounds)
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
self.backend().hit_test(
|
||||
content,
|
||||
size,
|
||||
font,
|
||||
bounds,
|
||||
point,
|
||||
nearest_only,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
color: Option<Color>,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
) -> Self::Output {
|
||||
let x = match horizontal_alignment {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => bounds.center_x(),
|
||||
alignment::Horizontal::Right => bounds.x + bounds.width,
|
||||
};
|
||||
|
||||
let y = match vertical_alignment {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => bounds.center_y(),
|
||||
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Text {
|
||||
content: content.to_string(),
|
||||
size: f32::from(size),
|
||||
bounds: Rectangle { x, y, ..bounds },
|
||||
color: color.unwrap_or(defaults.text.color),
|
||||
font,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
pub type Text<Backend> = iced_native::widget::Text<Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,266 +1,13 @@
|
|||
//! Display fields that can be filled with text.
|
||||
//!
|
||||
//! A [`TextInput`] has some local [`State`].
|
||||
use crate::alignment;
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{
|
||||
Background, Color, Font, Point, Primitive, Rectangle, Renderer, Size,
|
||||
Vector,
|
||||
};
|
||||
use crate::Renderer;
|
||||
|
||||
use iced_native::mouse;
|
||||
use iced_native::text_input::{self, cursor};
|
||||
use std::f32;
|
||||
|
||||
pub use iced_native::text_input::State;
|
||||
pub use iced_native::widget::text_input::State;
|
||||
pub use iced_style::text_input::{Style, StyleSheet};
|
||||
|
||||
/// A field that can be filled with text.
|
||||
///
|
||||
/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`.
|
||||
pub type TextInput<'a, Message, Backend> =
|
||||
iced_native::TextInput<'a, Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> text_input::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
|
||||
let backend = self.backend();
|
||||
|
||||
let (width, _) =
|
||||
backend.measure(value, f32::from(size), font, Size::INFINITY);
|
||||
|
||||
width
|
||||
}
|
||||
|
||||
fn offset(
|
||||
&self,
|
||||
text_bounds: Rectangle,
|
||||
font: Font,
|
||||
size: u16,
|
||||
value: &text_input::Value,
|
||||
state: &text_input::State,
|
||||
) -> f32 {
|
||||
if state.is_focused() {
|
||||
let cursor = state.cursor();
|
||||
|
||||
let focus_position = match cursor.state(value) {
|
||||
cursor::State::Index(i) => i,
|
||||
cursor::State::Selection { end, .. } => end,
|
||||
};
|
||||
|
||||
let (_, offset) = measure_cursor_and_scroll_offset(
|
||||
self,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
focus_position,
|
||||
font,
|
||||
);
|
||||
|
||||
offset
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
text_bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
font: Font,
|
||||
size: u16,
|
||||
placeholder: &str,
|
||||
value: &text_input::Value,
|
||||
state: &text_input::State,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let style = if state.is_focused() {
|
||||
style_sheet.focused()
|
||||
} else if is_mouse_over {
|
||||
style_sheet.hovered()
|
||||
} else {
|
||||
style_sheet.active()
|
||||
};
|
||||
|
||||
let input = Primitive::Quad {
|
||||
bounds,
|
||||
background: style.background,
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
};
|
||||
|
||||
let text = value.to_string();
|
||||
|
||||
let text_value = Primitive::Text {
|
||||
content: if text.is_empty() {
|
||||
placeholder.to_string()
|
||||
} else {
|
||||
text.clone()
|
||||
},
|
||||
color: if text.is_empty() {
|
||||
style_sheet.placeholder_color()
|
||||
} else {
|
||||
style_sheet.value_color()
|
||||
},
|
||||
font,
|
||||
bounds: Rectangle {
|
||||
y: text_bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
..text_bounds
|
||||
},
|
||||
size: f32::from(size),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
};
|
||||
|
||||
let (contents_primitive, offset) = if state.is_focused() {
|
||||
let cursor = state.cursor();
|
||||
|
||||
let (cursor_primitive, offset) = match cursor.state(value) {
|
||||
cursor::State::Index(position) => {
|
||||
let (text_value_width, offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
self,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
position,
|
||||
font,
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + text_value_width,
|
||||
y: text_bounds.y,
|
||||
width: 1.0,
|
||||
height: text_bounds.height,
|
||||
},
|
||||
background: Background::Color(
|
||||
style_sheet.value_color(),
|
||||
),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
offset,
|
||||
)
|
||||
}
|
||||
cursor::State::Selection { start, end } => {
|
||||
let left = start.min(end);
|
||||
let right = end.max(start);
|
||||
|
||||
let (left_position, left_offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
self,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
left,
|
||||
font,
|
||||
);
|
||||
|
||||
let (right_position, right_offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
self,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
right,
|
||||
font,
|
||||
);
|
||||
|
||||
let width = right_position - left_position;
|
||||
|
||||
(
|
||||
Primitive::Quad {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + left_position,
|
||||
y: text_bounds.y,
|
||||
width,
|
||||
height: text_bounds.height,
|
||||
},
|
||||
background: Background::Color(
|
||||
style_sheet.selection_color(),
|
||||
),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
if end == right {
|
||||
right_offset
|
||||
} else {
|
||||
left_offset
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![cursor_primitive, text_value],
|
||||
},
|
||||
Vector::new(offset as u32, 0),
|
||||
)
|
||||
} else {
|
||||
(text_value, Vector::new(0, 0))
|
||||
};
|
||||
|
||||
let text_width = self.measure_value(
|
||||
if text.is_empty() { placeholder } else { &text },
|
||||
size,
|
||||
font,
|
||||
);
|
||||
|
||||
let contents = if text_width > text_bounds.width {
|
||||
Primitive::Clip {
|
||||
bounds: text_bounds,
|
||||
offset,
|
||||
content: Box::new(contents_primitive),
|
||||
}
|
||||
} else {
|
||||
contents_primitive
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![input, contents],
|
||||
},
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Text
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_cursor_and_scroll_offset<B>(
|
||||
renderer: &Renderer<B>,
|
||||
text_bounds: Rectangle,
|
||||
value: &text_input::Value,
|
||||
size: u16,
|
||||
cursor_index: usize,
|
||||
font: Font,
|
||||
) -> (f32, f32)
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
use iced_native::text_input::Renderer;
|
||||
|
||||
let text_before_cursor = value.until(cursor_index).to_string();
|
||||
|
||||
let text_value_width =
|
||||
renderer.measure_value(&text_before_cursor, size, font);
|
||||
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
|
||||
|
||||
(text_value_width, offset)
|
||||
}
|
||||
iced_native::widget::TextInput<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,99 +1,10 @@
|
|||
//! Show toggle controls using togglers.
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::{Primitive, Renderer};
|
||||
use iced_native::mouse;
|
||||
use iced_native::toggler;
|
||||
use iced_native::Rectangle;
|
||||
use crate::Renderer;
|
||||
|
||||
pub use iced_style::toggler::{Style, StyleSheet};
|
||||
|
||||
/// Makes sure that the border radius of the toggler looks good at every size.
|
||||
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
|
||||
|
||||
/// The space ratio between the background Quad and the Toggler bounds, and
|
||||
/// between the background Quad and foreground Quad.
|
||||
const SPACE_RATIO: f32 = 0.05;
|
||||
|
||||
/// A toggler that can be toggled.
|
||||
///
|
||||
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
|
||||
pub type Toggler<Message, Backend> =
|
||||
iced_native::Toggler<Message, Renderer<Backend>>;
|
||||
|
||||
impl<B> toggler::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Style = Box<dyn StyleSheet>;
|
||||
|
||||
const DEFAULT_SIZE: u16 = 20;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
is_active: bool,
|
||||
is_mouse_over: bool,
|
||||
label: Option<Self::Output>,
|
||||
style_sheet: &Self::Style,
|
||||
) -> Self::Output {
|
||||
let style = if is_mouse_over {
|
||||
style_sheet.hovered(is_active)
|
||||
} else {
|
||||
style_sheet.active(is_active)
|
||||
};
|
||||
|
||||
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
|
||||
let space = SPACE_RATIO * bounds.height as f32;
|
||||
|
||||
let toggler_background_bounds = Rectangle {
|
||||
x: bounds.x + space,
|
||||
y: bounds.y + space,
|
||||
width: bounds.width - (2.0 * space),
|
||||
height: bounds.height - (2.0 * space),
|
||||
};
|
||||
|
||||
let toggler_background = Primitive::Quad {
|
||||
bounds: toggler_background_bounds,
|
||||
background: style.background.into(),
|
||||
border_radius,
|
||||
border_width: 1.0,
|
||||
border_color: style.background_border.unwrap_or(style.background),
|
||||
};
|
||||
|
||||
let toggler_foreground_bounds = Rectangle {
|
||||
x: bounds.x
|
||||
+ if is_active {
|
||||
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
|
||||
} else {
|
||||
2.0 * space
|
||||
},
|
||||
y: bounds.y + (2.0 * space),
|
||||
width: bounds.height - (4.0 * space),
|
||||
height: bounds.height - (4.0 * space),
|
||||
};
|
||||
|
||||
let toggler_foreground = Primitive::Quad {
|
||||
bounds: toggler_foreground_bounds,
|
||||
background: style.foreground.into(),
|
||||
border_radius,
|
||||
border_width: 1.0,
|
||||
border_color: style.foreground_border.unwrap_or(style.foreground),
|
||||
};
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: match label {
|
||||
Some((l, _)) => {
|
||||
vec![l, toggler_background, toggler_foreground]
|
||||
}
|
||||
None => vec![toggler_background, toggler_foreground],
|
||||
},
|
||||
},
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
pub type Toggler<'a, Message, Backend> =
|
||||
iced_native::widget::Toggler<'a, Message, Renderer<Backend>>;
|
||||
|
|
|
|||
|
|
@ -1,168 +1,11 @@
|
|||
//! Decorate content and apply alignment.
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::defaults::{self, Defaults};
|
||||
use crate::{Primitive, Renderer, Vector};
|
||||
|
||||
use iced_native::container;
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
|
||||
use crate::Renderer;
|
||||
|
||||
/// An element decorating some content.
|
||||
///
|
||||
/// This is an alias of an `iced_native` tooltip with a default
|
||||
/// `Renderer`.
|
||||
pub type Tooltip<'a, Message, Backend> =
|
||||
iced_native::Tooltip<'a, Message, Renderer<Backend>>;
|
||||
iced_native::widget::Tooltip<'a, Message, Renderer<Backend>>;
|
||||
|
||||
pub use iced_native::tooltip::Position;
|
||||
|
||||
impl<B> iced_native::tooltip::Renderer for Renderer<B>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
const DEFAULT_PADDING: u16 = 5;
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Defaults,
|
||||
cursor_position: Point,
|
||||
content_layout: Layout<'_>,
|
||||
viewport: &Rectangle,
|
||||
content: &Element<'_, Message, Self>,
|
||||
tooltip: &Text<Self>,
|
||||
position: Position,
|
||||
style_sheet: &<Self as container::Renderer>::Style,
|
||||
gap: u16,
|
||||
padding: u16,
|
||||
) -> Self::Output {
|
||||
let (content, mouse_interaction) = content.draw(
|
||||
self,
|
||||
&defaults,
|
||||
content_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
let bounds = content_layout.bounds();
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
use iced_native::Widget;
|
||||
|
||||
let gap = f32::from(gap);
|
||||
let style = style_sheet.style();
|
||||
|
||||
let defaults = Defaults {
|
||||
text: defaults::Text {
|
||||
color: style.text_color.unwrap_or(defaults.text.color),
|
||||
},
|
||||
};
|
||||
|
||||
let text_layout = Widget::<(), Self>::layout(
|
||||
tooltip,
|
||||
self,
|
||||
&layout::Limits::new(Size::ZERO, viewport.size())
|
||||
.pad(Padding::new(padding)),
|
||||
);
|
||||
|
||||
let padding = f32::from(padding);
|
||||
let text_bounds = text_layout.bounds();
|
||||
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
|
||||
let y_center =
|
||||
bounds.y + (bounds.height - text_bounds.height) / 2.0;
|
||||
|
||||
let mut tooltip_bounds = {
|
||||
let offset = match position {
|
||||
Position::Top => Vector::new(
|
||||
x_center,
|
||||
bounds.y - text_bounds.height - gap - padding,
|
||||
),
|
||||
Position::Bottom => Vector::new(
|
||||
x_center,
|
||||
bounds.y + bounds.height + gap + padding,
|
||||
),
|
||||
Position::Left => Vector::new(
|
||||
bounds.x - text_bounds.width - gap - padding,
|
||||
y_center,
|
||||
),
|
||||
Position::Right => Vector::new(
|
||||
bounds.x + bounds.width + gap + padding,
|
||||
y_center,
|
||||
),
|
||||
Position::FollowCursor => Vector::new(
|
||||
cursor_position.x,
|
||||
cursor_position.y - text_bounds.height,
|
||||
),
|
||||
};
|
||||
|
||||
Rectangle {
|
||||
x: offset.x - padding,
|
||||
y: offset.y - padding,
|
||||
width: text_bounds.width + padding * 2.0,
|
||||
height: text_bounds.height + padding * 2.0,
|
||||
}
|
||||
};
|
||||
|
||||
if tooltip_bounds.x < viewport.x {
|
||||
tooltip_bounds.x = viewport.x;
|
||||
} else if viewport.x + viewport.width
|
||||
< tooltip_bounds.x + tooltip_bounds.width
|
||||
{
|
||||
tooltip_bounds.x =
|
||||
viewport.x + viewport.width - tooltip_bounds.width;
|
||||
}
|
||||
|
||||
if tooltip_bounds.y < viewport.y {
|
||||
tooltip_bounds.y = viewport.y;
|
||||
} else if viewport.y + viewport.height
|
||||
< tooltip_bounds.y + tooltip_bounds.height
|
||||
{
|
||||
tooltip_bounds.y =
|
||||
viewport.y + viewport.height - tooltip_bounds.height;
|
||||
}
|
||||
|
||||
let (tooltip, _) = Widget::<(), Self>::draw(
|
||||
tooltip,
|
||||
self,
|
||||
&defaults,
|
||||
Layout::with_offset(
|
||||
Vector::new(
|
||||
tooltip_bounds.x + padding,
|
||||
tooltip_bounds.y + padding,
|
||||
),
|
||||
&text_layout,
|
||||
),
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
(
|
||||
Primitive::Group {
|
||||
primitives: vec![
|
||||
content,
|
||||
Primitive::Clip {
|
||||
bounds: *viewport,
|
||||
offset: Vector::new(0, 0),
|
||||
content: Box::new(
|
||||
if let Some(background) =
|
||||
crate::container::background(
|
||||
tooltip_bounds,
|
||||
&style,
|
||||
)
|
||||
{
|
||||
Primitive::Group {
|
||||
primitives: vec![background, tooltip],
|
||||
}
|
||||
} else {
|
||||
tooltip
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
mouse_interaction,
|
||||
)
|
||||
} else {
|
||||
(content, mouse_interaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use iced_native::widget::tooltip::Position;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use crate::{Color, Error, Viewport};
|
||||
|
||||
use iced_native::mouse;
|
||||
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -30,9 +28,8 @@ pub trait Compositor: Sized {
|
|||
window: &W,
|
||||
) -> Self::Surface;
|
||||
|
||||
/// Crates a new [`SwapChain`] for the given [`Surface`].
|
||||
/// Configures a new [`Surface`] with the given dimensions.
|
||||
///
|
||||
/// [`SwapChain`]: Self::SwapChain
|
||||
/// [`Surface`]: Self::Surface
|
||||
fn configure_surface(
|
||||
&mut self,
|
||||
|
|
@ -41,18 +38,17 @@ pub trait Compositor: Sized {
|
|||
height: u32,
|
||||
);
|
||||
|
||||
/// Draws the output primitives to the next frame of the given [`SwapChain`].
|
||||
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
||||
///
|
||||
/// [`SwapChain`]: Self::SwapChain
|
||||
fn draw<T: AsRef<str>>(
|
||||
fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
surface: &mut Self::Surface,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
output: &<Self::Renderer as iced_native::Renderer>::Output,
|
||||
overlay: &[T],
|
||||
) -> Result<mouse::Interaction, SurfaceError>;
|
||||
) -> Result<(), SurfaceError>;
|
||||
}
|
||||
|
||||
/// Result of an unsuccessful call to [`Compositor::draw`].
|
||||
|
|
@ -63,13 +59,13 @@ pub enum SurfaceError {
|
|||
"A timeout was encountered while trying to acquire the next frame"
|
||||
)]
|
||||
Timeout,
|
||||
/// The underlying surface has changed, and therefore the swap chain must be updated.
|
||||
/// The underlying surface has changed, and therefore the surface must be updated.
|
||||
#[error(
|
||||
"The underlying surface has changed, and therefore the swap chain must be updated."
|
||||
"The underlying surface has changed, and therefore the surface must be updated."
|
||||
)]
|
||||
Outdated,
|
||||
/// The swap chain has been lost and needs to be recreated.
|
||||
#[error("The swap chain has been lost and needs to be recreated")]
|
||||
#[error("The surface has been lost and needs to be recreated")]
|
||||
Lost,
|
||||
/// There is no more memory left to allocate a new frame.
|
||||
#[error("There is no more memory left to allocate a new frame")]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::{Color, Error, Size, Viewport};
|
||||
use iced_native::mouse;
|
||||
|
||||
use core::ffi::c_void;
|
||||
|
||||
|
|
@ -49,15 +48,15 @@ pub trait GLCompositor: Sized {
|
|||
/// Resizes the viewport of the [`GLCompositor`].
|
||||
fn resize_viewport(&mut self, physical_size: Size<u32>);
|
||||
|
||||
/// Draws the provided output with the given [`Renderer`].
|
||||
/// Presents the primitives of the [`Renderer`] to the next frame of the
|
||||
/// [`GLCompositor`].
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
fn draw<T: AsRef<str>>(
|
||||
fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
output: &<Self::Renderer as iced_native::Renderer>::Output,
|
||||
overlay: &[T],
|
||||
) -> mouse::Interaction;
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,7 @@ path = "../core"
|
|||
version = "0.3"
|
||||
path = "../futures"
|
||||
features = ["thread-pool"]
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.3"
|
||||
path = "../style"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
|
||||
};
|
||||
|
|
@ -77,7 +79,7 @@ where
|
|||
///
|
||||
/// ```
|
||||
/// # mod counter {
|
||||
/// # type Text = iced_native::Text<iced_native::renderer::Null>;
|
||||
/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// # #[derive(Debug, Clone, Copy)]
|
||||
/// # pub enum Message {}
|
||||
|
|
@ -104,7 +106,8 @@ where
|
|||
/// # pub enum Message {
|
||||
/// # Counter(usize, counter::Message)
|
||||
/// # }
|
||||
/// use iced_native::{Element, Row};
|
||||
/// use iced_native::Element;
|
||||
/// use iced_native::widget::Row;
|
||||
/// use iced_wgpu::Renderer;
|
||||
///
|
||||
/// impl ManyCounters {
|
||||
|
|
@ -189,7 +192,7 @@ where
|
|||
) -> Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'static,
|
||||
Renderer: 'a + layout::Debugger,
|
||||
Renderer: 'a,
|
||||
{
|
||||
Element {
|
||||
widget: Box::new(Explain::new(self, color.into())),
|
||||
|
|
@ -241,13 +244,24 @@ where
|
|||
pub fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) {
|
||||
self.widget
|
||||
.draw(renderer, defaults, layout, cursor_position, viewport)
|
||||
.draw(renderer, style, layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of the [`Element`].
|
||||
pub fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
self.widget
|
||||
.mouse_interaction(layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
/// Computes the _layout_ hash of the [`Element`].
|
||||
|
|
@ -336,13 +350,23 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) {
|
||||
self.widget
|
||||
.draw(renderer, defaults, layout, cursor_position, viewport)
|
||||
.draw(renderer, style, layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
self.widget
|
||||
.mouse_interaction(layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -378,7 +402,7 @@ where
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Explain<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer + layout::Debugger,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.element.widget.width()
|
||||
|
|
@ -418,19 +442,51 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.explain(
|
||||
defaults,
|
||||
self.element.widget.as_ref(),
|
||||
) {
|
||||
fn explain_layout<Renderer: crate::Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
color: Color,
|
||||
layout: Layout<'_>,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
border_color: color,
|
||||
border_width: 1.0,
|
||||
border_radius: 0.0,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
||||
for child in layout.children() {
|
||||
explain_layout(renderer, color, child);
|
||||
}
|
||||
}
|
||||
|
||||
self.element.widget.draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
self.color,
|
||||
)
|
||||
);
|
||||
|
||||
explain_layout(renderer, self.color, layout);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
self.element
|
||||
.widget
|
||||
.mouse_interaction(layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
|
|||
124
native/src/image.rs
Normal file
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.
|
||||
mod debugger;
|
||||
mod limits;
|
||||
mod node;
|
||||
|
||||
pub mod flex;
|
||||
|
||||
pub use debugger::Debugger;
|
||||
pub use limits::Limits;
|
||||
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 command;
|
||||
pub mod event;
|
||||
pub mod image;
|
||||
pub mod keyboard;
|
||||
pub mod layout;
|
||||
pub mod mouse;
|
||||
|
|
@ -43,6 +44,8 @@ pub mod overlay;
|
|||
pub mod program;
|
||||
pub mod renderer;
|
||||
pub mod subscription;
|
||||
pub mod svg;
|
||||
pub mod text;
|
||||
pub mod touch;
|
||||
pub mod widget;
|
||||
pub mod window;
|
||||
|
|
@ -84,4 +87,4 @@ pub use renderer::Renderer;
|
|||
pub use runtime::Runtime;
|
||||
pub use subscription::Subscription;
|
||||
pub use user_interface::{Cache, UserInterface};
|
||||
pub use widget::*;
|
||||
pub use widget::Widget;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ pub use menu::Menu;
|
|||
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::{Clipboard, Hasher, Layout, Point, Size};
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size};
|
||||
|
||||
/// An interactive component that can be displayed on top of other widgets.
|
||||
pub trait Overlay<Message, Renderer>
|
||||
|
|
@ -32,10 +34,10 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output;
|
||||
);
|
||||
|
||||
/// Computes the _layout_ hash of the [`Overlay`].
|
||||
///
|
||||
|
|
@ -73,4 +75,16 @@ where
|
|||
) -> event::Status {
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
|
||||
///
|
||||
/// By default, it returns [`mouse::Interaction::Idle`].
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
mouse::Interaction::Idle
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ pub use crate::Overlay;
|
|||
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size, Vector};
|
||||
|
||||
/// A generic [`Overlay`].
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
|
@ -67,16 +69,26 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of the [`Element`].
|
||||
pub fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
self.overlay
|
||||
.mouse_interaction(layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
/// Draws the [`Element`] and its children using the given [`Layout`].
|
||||
pub fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
self.overlay
|
||||
.draw(renderer, defaults, layout, cursor_position)
|
||||
) {
|
||||
self.overlay.draw(renderer, style, layout, cursor_position)
|
||||
}
|
||||
|
||||
/// Computes the _layout_ hash of the [`Element`].
|
||||
|
|
@ -139,15 +151,24 @@ where
|
|||
event_status
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
self.content
|
||||
.mouse_interaction(layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
self.content
|
||||
.draw(renderer, defaults, layout, cursor_position)
|
||||
) {
|
||||
self.content.draw(renderer, style, layout, cursor_position)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher, position: Point) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
//! Build and show dropdown menus.
|
||||
use crate::container;
|
||||
use crate::alignment;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::scrollable;
|
||||
use crate::text;
|
||||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::touch;
|
||||
use crate::widget::scrollable::{self, Scrollable};
|
||||
use crate::widget::Container;
|
||||
use crate::{
|
||||
Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
|
||||
Rectangle, Scrollable, Size, Vector, Widget,
|
||||
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
|
||||
Rectangle, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
pub use iced_style::menu::Style;
|
||||
|
||||
/// A list of selectable options.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Menu<'a, T, Renderer: self::Renderer> {
|
||||
pub struct Menu<'a, T, Renderer: text::Renderer> {
|
||||
state: &'a mut State,
|
||||
options: &'a [T],
|
||||
hovered_option: &'a mut Option<usize>,
|
||||
|
|
@ -23,13 +27,13 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
|
|||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: Renderer::Font,
|
||||
style: <Renderer as self::Renderer>::Style,
|
||||
style: Style,
|
||||
}
|
||||
|
||||
impl<'a, T, Renderer> Menu<'a, T, Renderer>
|
||||
where
|
||||
T: ToString + Clone,
|
||||
Renderer: self::Renderer + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
{
|
||||
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
|
||||
/// the message to produced when an option is selected.
|
||||
|
|
@ -77,10 +81,7 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Menu`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: impl Into<<Renderer as self::Renderer>::Style>,
|
||||
) -> Self {
|
||||
pub fn style(mut self, style: impl Into<Style>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
|
@ -116,14 +117,14 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, Message, Renderer: self::Renderer> {
|
||||
struct Overlay<'a, Message, Renderer: text::Renderer> {
|
||||
container: Container<'a, Message, Renderer>,
|
||||
width: u16,
|
||||
target_height: f32,
|
||||
style: <Renderer as self::Renderer>::Style,
|
||||
style: Style,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer>
|
||||
impl<'a, Message, Renderer: text::Renderer> Overlay<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a,
|
||||
|
|
@ -168,7 +169,7 @@ where
|
|||
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
|
||||
for Overlay<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn layout(
|
||||
&self,
|
||||
|
|
@ -233,45 +234,55 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
self.container
|
||||
.mouse_interaction(layout, cursor_position, viewport)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
let primitives = self.container.draw(
|
||||
renderer,
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
&layout.bounds(),
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_color: self.style.border_color,
|
||||
border_width: self.style.border_width,
|
||||
border_radius: 0.0,
|
||||
},
|
||||
self.style.background,
|
||||
);
|
||||
|
||||
renderer.decorate(
|
||||
layout.bounds(),
|
||||
cursor_position,
|
||||
&self.style,
|
||||
primitives,
|
||||
)
|
||||
self.container
|
||||
.draw(renderer, style, layout, cursor_position, &bounds);
|
||||
}
|
||||
}
|
||||
|
||||
struct List<'a, T, Renderer: self::Renderer> {
|
||||
struct List<'a, T, Renderer: text::Renderer> {
|
||||
options: &'a [T],
|
||||
hovered_option: &'a mut Option<usize>,
|
||||
last_selection: &'a mut Option<T>,
|
||||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: Renderer::Font,
|
||||
style: <Renderer as self::Renderer>::Style,
|
||||
style: Style,
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer: self::Renderer> Widget<Message, Renderer>
|
||||
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
||||
for List<'a, T, Renderer>
|
||||
where
|
||||
T: Clone + ToString,
|
||||
Renderer: self::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
|
|
@ -376,73 +387,92 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
let is_mouse_over = layout.bounds().contains(cursor_position);
|
||||
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
layout.bounds(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
self.options,
|
||||
*self.hovered_option,
|
||||
self.padding,
|
||||
self.text_size.unwrap_or(renderer.default_size()),
|
||||
self.font,
|
||||
&self.style,
|
||||
)
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let text_size = self.text_size.unwrap_or(renderer.default_size());
|
||||
let option_height = (text_size + self.padding.vertical()) as usize;
|
||||
|
||||
let offset = viewport.y - bounds.y;
|
||||
let start = (offset / option_height as f32) as usize;
|
||||
let end =
|
||||
((offset + viewport.height) / option_height as f32).ceil() as usize;
|
||||
|
||||
let visible_options = &self.options[start..end.min(self.options.len())];
|
||||
|
||||
for (i, option) in visible_options.iter().enumerate() {
|
||||
let i = start + i;
|
||||
let is_selected = *self.hovered_option == Some(i);
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y + (option_height * i) as f32,
|
||||
width: bounds.width,
|
||||
height: f32::from(text_size + self.padding.vertical()),
|
||||
};
|
||||
|
||||
if is_selected {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_color: Color::TRANSPARENT,
|
||||
border_width: 0.0,
|
||||
border_radius: 0.0,
|
||||
},
|
||||
self.style.selected_background,
|
||||
);
|
||||
}
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: &option.to_string(),
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + self.padding.left as f32,
|
||||
y: bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
..bounds
|
||||
},
|
||||
size: f32::from(text_size),
|
||||
font: self.font,
|
||||
color: if is_selected {
|
||||
self.style.selected_text_color
|
||||
} else {
|
||||
self.style.text_color
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Menu`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Menu`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer:
|
||||
scrollable::Renderer + container::Renderer + text::Renderer
|
||||
{
|
||||
/// The [`Menu`] style supported by this renderer.
|
||||
type Style: Default + Clone;
|
||||
|
||||
/// Decorates a the list of options of a [`Menu`].
|
||||
///
|
||||
/// This method can be used to draw a background for the [`Menu`].
|
||||
fn decorate(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
style: &<Self as Renderer>::Style,
|
||||
primitive: Self::Output,
|
||||
) -> Self::Output;
|
||||
|
||||
/// Draws the list of options of a [`Menu`].
|
||||
fn draw<T: ToString>(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
options: &[T],
|
||||
hovered_option: Option<usize>,
|
||||
padding: Padding,
|
||||
text_size: u16,
|
||||
font: Self::Font,
|
||||
style: &<Self as Renderer>::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
||||
for List<'a, T, Renderer>
|
||||
where
|
||||
T: ToString + Clone,
|
||||
Message: 'a,
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + text::Renderer,
|
||||
{
|
||||
fn into(self) -> Element<'a, Message, Renderer> {
|
||||
Element::new(self)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::mouse;
|
||||
use crate::{
|
||||
Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
|
||||
Cache, Clipboard, Command, Debug, Event, Point, Program, Size,
|
||||
UserInterface,
|
||||
};
|
||||
|
||||
|
|
@ -12,9 +13,9 @@ where
|
|||
{
|
||||
program: P,
|
||||
cache: Option<Cache>,
|
||||
primitive: <P::Renderer as Renderer>::Output,
|
||||
queued_events: Vec<Event>,
|
||||
queued_messages: Vec<P::Message>,
|
||||
mouse_interaction: mouse::Interaction,
|
||||
}
|
||||
|
||||
impl<P> State<P>
|
||||
|
|
@ -26,11 +27,10 @@ where
|
|||
pub fn new(
|
||||
mut program: P,
|
||||
bounds: Size,
|
||||
cursor_position: Point,
|
||||
renderer: &mut P::Renderer,
|
||||
debug: &mut Debug,
|
||||
) -> Self {
|
||||
let mut user_interface = build_user_interface(
|
||||
let user_interface = build_user_interface(
|
||||
&mut program,
|
||||
Cache::default(),
|
||||
renderer,
|
||||
|
|
@ -38,18 +38,14 @@ where
|
|||
debug,
|
||||
);
|
||||
|
||||
debug.draw_started();
|
||||
let primitive = user_interface.draw(renderer, cursor_position);
|
||||
debug.draw_finished();
|
||||
|
||||
let cache = Some(user_interface.into_cache());
|
||||
|
||||
State {
|
||||
program,
|
||||
cache,
|
||||
primitive,
|
||||
queued_events: Vec::new(),
|
||||
queued_messages: Vec::new(),
|
||||
mouse_interaction: mouse::Interaction::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,11 +54,6 @@ where
|
|||
&self.program
|
||||
}
|
||||
|
||||
/// Returns a reference to the current rendering primitive of the [`State`].
|
||||
pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
|
||||
&self.primitive
|
||||
}
|
||||
|
||||
/// Queues an event in the [`State`] for processing during an [`update`].
|
||||
///
|
||||
/// [`update`]: Self::update
|
||||
|
|
@ -82,6 +73,11 @@ where
|
|||
self.queued_events.is_empty() && self.queued_messages.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of the [`State`].
|
||||
pub fn mouse_interaction(&self) -> mouse::Interaction {
|
||||
self.mouse_interaction
|
||||
}
|
||||
|
||||
/// Processes all the queued events and messages, rebuilding and redrawing
|
||||
/// the widgets of the linked [`Program`] if necessary.
|
||||
///
|
||||
|
|
@ -120,7 +116,8 @@ where
|
|||
|
||||
if messages.is_empty() {
|
||||
debug.draw_started();
|
||||
self.primitive = user_interface.draw(renderer, cursor_position);
|
||||
self.mouse_interaction =
|
||||
user_interface.draw(renderer, cursor_position);
|
||||
debug.draw_finished();
|
||||
|
||||
self.cache = Some(user_interface.into_cache());
|
||||
|
|
@ -151,7 +148,8 @@ where
|
|||
);
|
||||
|
||||
debug.draw_started();
|
||||
self.primitive = user_interface.draw(renderer, cursor_position);
|
||||
self.mouse_interaction =
|
||||
user_interface.draw(renderer, cursor_position);
|
||||
debug.draw_finished();
|
||||
|
||||
self.cache = Some(user_interface.into_cache());
|
||||
|
|
|
|||
|
|
@ -19,28 +19,17 @@
|
|||
//! [`text::Renderer`]: crate::widget::text::Renderer
|
||||
//! [`Checkbox`]: crate::widget::Checkbox
|
||||
//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod null;
|
||||
#[cfg(debug_assertions)]
|
||||
pub use null::Null;
|
||||
|
||||
use crate::{layout, Element, Rectangle};
|
||||
use crate::layout;
|
||||
use crate::{Background, Color, Element, Rectangle, Vector};
|
||||
|
||||
/// A component that can take the state of a user interface and produce an
|
||||
/// output for its users.
|
||||
pub trait Renderer: Sized {
|
||||
/// The type of output of the [`Renderer`].
|
||||
///
|
||||
/// If you are implementing a graphical renderer, your output will most
|
||||
/// likely be a tree of visual primitives.
|
||||
type Output;
|
||||
|
||||
/// The default styling attributes of the [`Renderer`].
|
||||
///
|
||||
/// This type can be leveraged to implement style inheritance.
|
||||
type Defaults: Default;
|
||||
|
||||
/// Lays out the elements of a user interface.
|
||||
///
|
||||
/// You should override this if you need to perform any operations before or
|
||||
|
|
@ -53,12 +42,52 @@ pub trait Renderer: Sized {
|
|||
element.layout(self, limits)
|
||||
}
|
||||
|
||||
/// Overlays the `overlay` output with the given bounds on top of the `base`
|
||||
/// output.
|
||||
fn overlay(
|
||||
/// Draws the primitives recorded in the given closure in a new layer.
|
||||
///
|
||||
/// The layer will clip its contents to the provided `bounds`.
|
||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
|
||||
|
||||
/// Applies a `translation` to the primitives recorded in the given closure.
|
||||
fn with_translation(
|
||||
&mut self,
|
||||
base: Self::Output,
|
||||
overlay: Self::Output,
|
||||
overlay_bounds: Rectangle,
|
||||
) -> Self::Output;
|
||||
translation: Vector,
|
||||
f: impl FnOnce(&mut Self),
|
||||
);
|
||||
|
||||
/// Clears all of the recorded primitives in the [`Renderer`].
|
||||
fn clear(&mut self);
|
||||
|
||||
/// Fills a [`Quad`] with the provided [`Background`].
|
||||
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
|
||||
}
|
||||
|
||||
/// A polygon with four sides.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Quad {
|
||||
/// The bounds of the [`Quad`].
|
||||
pub bounds: Rectangle,
|
||||
|
||||
/// The border radius of the [`Quad`].
|
||||
pub border_radius: f32,
|
||||
|
||||
/// The border width of the [`Quad`].
|
||||
pub border_width: f32,
|
||||
|
||||
/// The border color of the [`Quad`].
|
||||
pub border_color: Color,
|
||||
}
|
||||
|
||||
/// The styling attributes of a [`Renderer`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Style {
|
||||
/// The text color
|
||||
pub text_color: Color,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
text_color: Color::BLACK,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,6 @@
|
|||
use crate::alignment;
|
||||
use crate::button;
|
||||
use crate::checkbox;
|
||||
use crate::column;
|
||||
use crate::container;
|
||||
use crate::pane_grid;
|
||||
use crate::progress_bar;
|
||||
use crate::radio;
|
||||
use crate::row;
|
||||
use crate::scrollable;
|
||||
use crate::slider;
|
||||
use crate::text;
|
||||
use crate::text_input;
|
||||
use crate::toggler;
|
||||
use crate::{
|
||||
Color, Element, Font, Layout, Padding, Point, Rectangle, Renderer, Size,
|
||||
};
|
||||
use crate::renderer::{self, Renderer};
|
||||
use crate::text::{self, Text};
|
||||
use crate::{Background, Font, Point, Rectangle, Size, Vector};
|
||||
|
||||
/// A renderer that does nothing.
|
||||
///
|
||||
|
|
@ -30,33 +16,21 @@ impl Null {
|
|||
}
|
||||
|
||||
impl Renderer for Null {
|
||||
type Output = ();
|
||||
type Defaults = ();
|
||||
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
|
||||
|
||||
fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) {
|
||||
}
|
||||
}
|
||||
|
||||
impl column::Renderer for Null {
|
||||
fn draw<Message>(
|
||||
fn with_translation(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_content: &[Element<'_, Message, Self>],
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_translation: Vector,
|
||||
_f: impl FnOnce(&mut Self),
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl row::Renderer for Null {
|
||||
fn draw<Message>(
|
||||
fn clear(&mut self) {}
|
||||
|
||||
fn fill_quad(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_content: &[Element<'_, Message, Self>],
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_quad: renderer::Quad,
|
||||
_background: impl Into<Background>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +38,10 @@ impl row::Renderer for Null {
|
|||
impl text::Renderer for Null {
|
||||
type Font = Font;
|
||||
|
||||
const ICON_FONT: Font = Font::Default;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
const ARROW_DOWN_ICON: char = '0';
|
||||
|
||||
fn default_size(&self) -> u16 {
|
||||
20
|
||||
}
|
||||
|
|
@ -90,240 +68,5 @@ impl text::Renderer for Null {
|
|||
None
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_bounds: Rectangle,
|
||||
_content: &str,
|
||||
_size: u16,
|
||||
_font: Font,
|
||||
_color: Option<Color>,
|
||||
_horizontal_alignment: alignment::Horizontal,
|
||||
_vertical_alignment: alignment::Vertical,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl scrollable::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
fn scrollbar(
|
||||
&self,
|
||||
_bounds: Rectangle,
|
||||
_content_bounds: Rectangle,
|
||||
_offset: u32,
|
||||
_scrollbar_width: u16,
|
||||
_scrollbar_margin: u16,
|
||||
_scroller_width: u16,
|
||||
) -> Option<scrollable::Scrollbar> {
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_scrollable: &scrollable::State,
|
||||
_bounds: Rectangle,
|
||||
_content_bounds: Rectangle,
|
||||
_is_mouse_over: bool,
|
||||
_is_mouse_over_scrollbar: bool,
|
||||
_scrollbar: Option<scrollable::Scrollbar>,
|
||||
_offset: u32,
|
||||
_style: &Self::Style,
|
||||
_content: Self::Output,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl text_input::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
|
||||
0.0
|
||||
}
|
||||
|
||||
fn offset(
|
||||
&self,
|
||||
_text_bounds: Rectangle,
|
||||
_font: Font,
|
||||
_size: u16,
|
||||
_value: &text_input::Value,
|
||||
_state: &text_input::State,
|
||||
) -> f32 {
|
||||
0.0
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_bounds: Rectangle,
|
||||
_text_bounds: Rectangle,
|
||||
_cursor_position: Point,
|
||||
_font: Font,
|
||||
_size: u16,
|
||||
_placeholder: &str,
|
||||
_value: &text_input::Value,
|
||||
_state: &text_input::State,
|
||||
_style: &Self::Style,
|
||||
) -> Self::Output {
|
||||
}
|
||||
}
|
||||
|
||||
impl button::Renderer for Null {
|
||||
const DEFAULT_PADDING: Padding = Padding::ZERO;
|
||||
|
||||
type Style = ();
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_bounds: Rectangle,
|
||||
_cursor_position: Point,
|
||||
_is_disabled: bool,
|
||||
_is_pressed: bool,
|
||||
_style: &Self::Style,
|
||||
_content: &Element<'_, Message, Self>,
|
||||
_content_layout: Layout<'_>,
|
||||
) -> Self::Output {
|
||||
}
|
||||
}
|
||||
|
||||
impl radio::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
const DEFAULT_SIZE: u16 = 20;
|
||||
const DEFAULT_SPACING: u16 = 15;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_bounds: Rectangle,
|
||||
_is_selected: bool,
|
||||
_is_mouse_over: bool,
|
||||
_label: Self::Output,
|
||||
_style: &Self::Style,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl checkbox::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
const DEFAULT_SIZE: u16 = 20;
|
||||
const DEFAULT_SPACING: u16 = 15;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_bounds: Rectangle,
|
||||
_is_checked: bool,
|
||||
_is_mouse_over: bool,
|
||||
_label: Self::Output,
|
||||
_style: &Self::Style,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl slider::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
const DEFAULT_HEIGHT: u16 = 30;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_bounds: Rectangle,
|
||||
_cursor_position: Point,
|
||||
_range: std::ops::RangeInclusive<f32>,
|
||||
_value: f32,
|
||||
_is_dragging: bool,
|
||||
_style_sheet: &Self::Style,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl progress_bar::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
const DEFAULT_HEIGHT: u16 = 30;
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_bounds: Rectangle,
|
||||
_range: std::ops::RangeInclusive<f32>,
|
||||
_value: f32,
|
||||
_style: &Self::Style,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl container::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_bounds: Rectangle,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_style: &Self::Style,
|
||||
_content: &Element<'_, Message, Self>,
|
||||
_content_layout: Layout<'_>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl pane_grid::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
|
||||
_dragging: Option<(pane_grid::Pane, Point)>,
|
||||
_resizing: Option<(pane_grid::Axis, Rectangle, bool)>,
|
||||
_layout: Layout<'_>,
|
||||
_style: &<Self as pane_grid::Renderer>::Style,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
}
|
||||
|
||||
fn draw_pane<Message>(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_bounds: Rectangle,
|
||||
_style: &<Self as container::Renderer>::Style,
|
||||
_title_bar: Option<(
|
||||
&pane_grid::TitleBar<'_, Message, Self>,
|
||||
Layout<'_>,
|
||||
)>,
|
||||
_body: (&Element<'_, Message, Self>, Layout<'_>),
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
}
|
||||
|
||||
fn draw_title_bar<Message>(
|
||||
&mut self,
|
||||
_defaults: &Self::Defaults,
|
||||
_bounds: Rectangle,
|
||||
_style: &<Self as container::Renderer>::Style,
|
||||
_content: (&Element<'_, Message, Self>, Layout<'_>),
|
||||
_controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl toggler::Renderer for Null {
|
||||
type Style = ();
|
||||
|
||||
const DEFAULT_SIZE: u16 = 20;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
_bounds: Rectangle,
|
||||
_is_checked: bool,
|
||||
_is_mouse_over: bool,
|
||||
_label: Option<Self::Output>,
|
||||
_style: &Self::Style,
|
||||
) {
|
||||
}
|
||||
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
|
||||
}
|
||||
|
|
|
|||
88
native/src/svg.rs
Normal file
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::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
|
||||
|
||||
use std::hash::Hasher;
|
||||
|
|
@ -47,7 +49,7 @@ where
|
|||
/// # pub use iced_native::renderer::Null as Renderer;
|
||||
/// # }
|
||||
/// #
|
||||
/// # use iced_native::Column;
|
||||
/// # use iced_native::widget::Column;
|
||||
/// #
|
||||
/// # pub struct Counter;
|
||||
/// #
|
||||
|
|
@ -141,7 +143,7 @@ where
|
|||
/// # pub use iced_native::renderer::Null as Renderer;
|
||||
/// # }
|
||||
/// #
|
||||
/// # use iced_native::Column;
|
||||
/// # use iced_native::widget::Column;
|
||||
/// #
|
||||
/// # pub struct Counter;
|
||||
/// #
|
||||
|
|
@ -277,7 +279,7 @@ where
|
|||
/// # pub use iced_native::renderer::Null as Renderer;
|
||||
/// # }
|
||||
/// #
|
||||
/// # use iced_native::Column;
|
||||
/// # use iced_native::widget::Column;
|
||||
/// #
|
||||
/// # pub struct Counter;
|
||||
/// #
|
||||
|
|
@ -333,10 +335,13 @@ where
|
|||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
// TODO: Move to shell level (?)
|
||||
renderer.clear();
|
||||
|
||||
let viewport = Rectangle::with_size(self.bounds);
|
||||
|
||||
let overlay = if let Some(mut overlay) =
|
||||
if let Some(mut overlay) =
|
||||
self.root.overlay(Layout::new(&self.base.layout))
|
||||
{
|
||||
let layer = Self::overlay_layer(
|
||||
|
|
@ -346,51 +351,81 @@ where
|
|||
renderer,
|
||||
);
|
||||
|
||||
let overlay_bounds = layer.layout.bounds();
|
||||
|
||||
let overlay_primitives = overlay.draw(
|
||||
renderer,
|
||||
&Renderer::Defaults::default(),
|
||||
Layout::new(&layer.layout),
|
||||
cursor_position,
|
||||
);
|
||||
|
||||
self.overlay = Some(layer);
|
||||
|
||||
Some((overlay_primitives, overlay_bounds))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((overlay_primitives, overlay_bounds)) = overlay {
|
||||
let base_cursor = if overlay_bounds.contains(cursor_position) {
|
||||
if let Some(layer) = &self.overlay {
|
||||
let base_cursor = if layer.layout.bounds().contains(cursor_position)
|
||||
{
|
||||
Point::new(-1.0, -1.0)
|
||||
} else {
|
||||
cursor_position
|
||||
};
|
||||
|
||||
let base_primitives = self.root.widget.draw(
|
||||
self.root.widget.draw(
|
||||
renderer,
|
||||
&Renderer::Defaults::default(),
|
||||
&renderer::Style::default(),
|
||||
Layout::new(&self.base.layout),
|
||||
base_cursor,
|
||||
&viewport,
|
||||
);
|
||||
|
||||
renderer.overlay(
|
||||
base_primitives,
|
||||
overlay_primitives,
|
||||
overlay_bounds,
|
||||
)
|
||||
} else {
|
||||
self.root.widget.draw(
|
||||
renderer,
|
||||
&Renderer::Defaults::default(),
|
||||
&renderer::Style::default(),
|
||||
Layout::new(&self.base.layout),
|
||||
cursor_position,
|
||||
&viewport,
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let base_interaction = self.root.widget.mouse_interaction(
|
||||
Layout::new(&self.base.layout),
|
||||
cursor_position,
|
||||
&viewport,
|
||||
);
|
||||
|
||||
let Self {
|
||||
overlay,
|
||||
root,
|
||||
base,
|
||||
..
|
||||
} = self;
|
||||
|
||||
// TODO: Currently, we need to call Widget::overlay twice to
|
||||
// implement the painter's algorithm properly.
|
||||
//
|
||||
// Once we have a proper persistent widget tree, we should be able to
|
||||
// avoid this additional call.
|
||||
overlay
|
||||
.as_ref()
|
||||
.and_then(|layer| {
|
||||
root.overlay(Layout::new(&base.layout)).map(|overlay| {
|
||||
let overlay_interaction = overlay.mouse_interaction(
|
||||
Layout::new(&layer.layout),
|
||||
cursor_position,
|
||||
&viewport,
|
||||
);
|
||||
|
||||
let overlay_bounds = layer.layout.bounds();
|
||||
|
||||
renderer.with_layer(viewport, |renderer| {
|
||||
overlay.draw(
|
||||
renderer,
|
||||
&renderer::Style::default(),
|
||||
Layout::new(&layer.layout),
|
||||
cursor_position,
|
||||
);
|
||||
});
|
||||
|
||||
if overlay_bounds.contains(cursor_position) {
|
||||
overlay_interaction
|
||||
} else {
|
||||
base_interaction
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or(base_interaction)
|
||||
}
|
||||
|
||||
/// Relayouts and returns a new [`UserInterface`] using the provided
|
||||
|
|
|
|||
|
|
@ -10,14 +10,6 @@
|
|||
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
|
||||
//! source of inspiration.
|
||||
//!
|
||||
//! # Re-exports
|
||||
//! For convenience, the contents of this module are available at the root
|
||||
//! module. Therefore, you can directly type:
|
||||
//!
|
||||
//! ```
|
||||
//! use iced_native::{button, Button, Widget};
|
||||
//! ```
|
||||
//!
|
||||
//! [renderer]: crate::renderer
|
||||
pub mod button;
|
||||
pub mod checkbox;
|
||||
|
|
@ -80,7 +72,9 @@ pub use tooltip::Tooltip;
|
|||
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
|
||||
|
||||
/// A component that displays information and allows interaction.
|
||||
|
|
@ -131,11 +125,11 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output;
|
||||
);
|
||||
|
||||
/// Computes the _layout_ hash of the [`Widget`].
|
||||
///
|
||||
|
|
@ -174,6 +168,18 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
|
||||
///
|
||||
/// By default, it returns [`mouse::Interaction::Idle`].
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
mouse::Interaction::Idle
|
||||
}
|
||||
|
||||
/// Returns the overlay of the [`Widget`], if there is any.
|
||||
fn overlay(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -5,20 +5,24 @@ use crate::event::{self, Event};
|
|||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
||||
Widget,
|
||||
Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
|
||||
Point, Rectangle, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
pub use iced_style::button::{Style, StyleSheet};
|
||||
|
||||
/// A generic widget that produces a message when pressed.
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::{button, Text};
|
||||
/// # use iced_native::widget::{button, Text};
|
||||
/// #
|
||||
/// # type Button<'a, Message> =
|
||||
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// #[derive(Clone)]
|
||||
/// enum Message {
|
||||
|
|
@ -34,10 +38,10 @@ use std::hash::Hash;
|
|||
/// be disabled:
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::{button, Text};
|
||||
/// # use iced_native::widget::{button, Text};
|
||||
/// #
|
||||
/// # type Button<'a, Message> =
|
||||
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// #[derive(Clone)]
|
||||
/// enum Message {
|
||||
|
|
@ -53,7 +57,7 @@ use std::hash::Hash;
|
|||
/// }
|
||||
/// ```
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Button<'a, Message, Renderer: self::Renderer> {
|
||||
pub struct Button<'a, Message, Renderer> {
|
||||
state: &'a mut State,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
on_press: Option<Message>,
|
||||
|
|
@ -62,13 +66,13 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
|
|||
min_width: u32,
|
||||
min_height: u32,
|
||||
padding: Padding,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Creates a new [`Button`] with some local [`State`] and the given
|
||||
/// content.
|
||||
|
|
@ -84,8 +88,8 @@ where
|
|||
height: Length::Shrink,
|
||||
min_width: 0,
|
||||
min_height: 0,
|
||||
padding: Renderer::DEFAULT_PADDING,
|
||||
style: Renderer::Style::default(),
|
||||
padding: Padding::new(5),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,8 +131,11 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Button`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
|||
for Button<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -241,24 +248,88 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
) -> mouse::Interaction {
|
||||
let is_mouse_over = layout.bounds().contains(cursor_position);
|
||||
let is_disabled = self.on_press.is_none();
|
||||
|
||||
if is_mouse_over && !is_disabled {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let is_disabled = self.on_press.is_none();
|
||||
|
||||
let styling = if is_disabled {
|
||||
self.style_sheet.disabled()
|
||||
} else if is_mouse_over {
|
||||
if self.state.is_pressed {
|
||||
self.style_sheet.pressed()
|
||||
} else {
|
||||
self.style_sheet.hovered()
|
||||
}
|
||||
} else {
|
||||
self.style_sheet.active()
|
||||
};
|
||||
|
||||
if styling.background.is_some() || styling.border_width > 0.0 {
|
||||
if styling.shadow_offset != Vector::default() {
|
||||
// TODO: Implement proper shadow support
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + styling.shadow_offset.x,
|
||||
y: bounds.y + styling.shadow_offset.y,
|
||||
..bounds
|
||||
},
|
||||
border_radius: styling.border_radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color([0.0, 0.0, 0.0, 0.5].into()),
|
||||
);
|
||||
}
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_radius: styling.border_radius,
|
||||
border_width: styling.border_width,
|
||||
border_color: styling.border_color,
|
||||
},
|
||||
styling
|
||||
.background
|
||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||
);
|
||||
}
|
||||
|
||||
self.content.draw(
|
||||
renderer,
|
||||
&renderer::Style {
|
||||
text_color: styling.text_color,
|
||||
},
|
||||
content_layout,
|
||||
cursor_position,
|
||||
self.on_press.is_none(),
|
||||
self.state.is_pressed,
|
||||
&self.style,
|
||||
&self.content,
|
||||
layout.children().next().unwrap(),
|
||||
)
|
||||
&bounds,
|
||||
);
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -277,38 +348,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Button`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Button`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer + Sized {
|
||||
/// The default padding of a [`Button`].
|
||||
const DEFAULT_PADDING: Padding;
|
||||
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Draws a [`Button`].
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
is_disabled: bool,
|
||||
is_pressed: bool,
|
||||
style: &Self::Style,
|
||||
content: &Element<'_, Message, Self>,
|
||||
content_layout: Layout<'_>,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
{
|
||||
fn from(
|
||||
button: Button<'a, Message, Renderer>,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,27 @@
|
|||
//! Show toggle controls using checkboxes.
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::alignment::{self, Alignment};
|
||||
use crate::alignment;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::row;
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::touch;
|
||||
use crate::widget::{self, Row, Text};
|
||||
use crate::{
|
||||
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
|
||||
Text, Widget,
|
||||
Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
|
||||
Rectangle, Widget,
|
||||
};
|
||||
|
||||
pub use iced_style::checkbox::{Style, StyleSheet};
|
||||
|
||||
/// A box that can be checked.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>;
|
||||
/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// pub enum Message {
|
||||
/// CheckboxToggled(bool),
|
||||
|
|
@ -31,7 +34,7 @@ use crate::{
|
|||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
|
||||
pub struct Checkbox<'a, Message, Renderer: text::Renderer> {
|
||||
is_checked: bool,
|
||||
on_toggle: Box<dyn Fn(bool) -> Message>,
|
||||
label: String,
|
||||
|
|
@ -41,12 +44,16 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
|
|||
text_size: Option<u16>,
|
||||
font: Renderer::Font,
|
||||
text_color: Option<Color>,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<Message, Renderer: self::Renderer + text::Renderer>
|
||||
Checkbox<Message, Renderer>
|
||||
{
|
||||
impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> {
|
||||
/// The default size of a [`Checkbox`].
|
||||
const DEFAULT_SIZE: u16 = 20;
|
||||
|
||||
/// The default spacing of a [`Checkbox`].
|
||||
const DEFAULT_SPACING: u16 = 15;
|
||||
|
||||
/// Creates a new [`Checkbox`].
|
||||
///
|
||||
/// It expects:
|
||||
|
|
@ -64,12 +71,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
|
|||
on_toggle: Box::new(f),
|
||||
label: label.into(),
|
||||
width: Length::Shrink,
|
||||
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
|
||||
spacing: Renderer::DEFAULT_SPACING,
|
||||
size: Self::DEFAULT_SIZE,
|
||||
spacing: Self::DEFAULT_SPACING,
|
||||
text_size: None,
|
||||
font: Renderer::Font::default(),
|
||||
text_color: None,
|
||||
style: Renderer::Style::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,16 +119,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Checkbox`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer>
|
||||
for Checkbox<Message, Renderer>
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Checkbox<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -180,43 +190,84 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
if layout.bounds().contains(cursor_position) {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let mut children = layout.children();
|
||||
|
||||
let checkbox_layout = children.next().unwrap();
|
||||
let label_layout = children.next().unwrap();
|
||||
let checkbox_bounds = checkbox_layout.bounds();
|
||||
|
||||
let label = text::Renderer::draw(
|
||||
renderer,
|
||||
defaults,
|
||||
label_layout.bounds(),
|
||||
&self.label,
|
||||
self.text_size.unwrap_or(renderer.default_size()),
|
||||
self.font,
|
||||
self.text_color,
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Center,
|
||||
);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
checkbox_bounds,
|
||||
self.is_checked,
|
||||
is_mouse_over,
|
||||
label,
|
||||
&self.style,
|
||||
)
|
||||
let mut children = layout.children();
|
||||
|
||||
{
|
||||
let layout = children.next().unwrap();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let style = if is_mouse_over {
|
||||
self.style_sheet.hovered(self.is_checked)
|
||||
} else {
|
||||
self.style_sheet.active(self.is_checked)
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
if self.is_checked {
|
||||
renderer.fill_text(text::Text {
|
||||
content: &Renderer::CHECKMARK_ICON.to_string(),
|
||||
font: Renderer::ICON_FONT,
|
||||
size: bounds.height * 0.7,
|
||||
bounds: Rectangle {
|
||||
x: bounds.center_x(),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
color: style.checkmark_color,
|
||||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let label_layout = children.next().unwrap();
|
||||
|
||||
widget::text::draw(
|
||||
renderer,
|
||||
style,
|
||||
label_layout,
|
||||
&self.label,
|
||||
self.font,
|
||||
self.text_size,
|
||||
self.text_color,
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Center,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -227,47 +278,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Checkbox`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Checkbox`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::Renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// The default size of a [`Checkbox`].
|
||||
const DEFAULT_SIZE: u16;
|
||||
|
||||
/// The default spacing of a [`Checkbox`].
|
||||
const DEFAULT_SPACING: u16;
|
||||
|
||||
/// Draws a [`Checkbox`].
|
||||
///
|
||||
/// It receives:
|
||||
/// * the bounds of the [`Checkbox`]
|
||||
/// * whether the [`Checkbox`] is selected or not
|
||||
/// * whether the mouse is over the [`Checkbox`] or not
|
||||
/// * the drawn label of the [`Checkbox`]
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
is_checked: bool,
|
||||
is_mouse_over: bool,
|
||||
label: Self::Output,
|
||||
style: &Self::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>>
|
||||
impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
|
||||
Renderer: 'a + text::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
checkbox: Checkbox<Message, Renderer>,
|
||||
checkbox: Checkbox<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(checkbox)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ use std::hash::Hash;
|
|||
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
|
||||
Rectangle, Widget,
|
||||
|
|
@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Column<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -162,21 +164,37 @@ where
|
|||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(
|
||||
defaults,
|
||||
&self.children,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
) -> mouse::Interaction {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
child.widget.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||
child.draw(renderer, style, layout, cursor_position, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -208,33 +226,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Column`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Column`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer + Sized {
|
||||
/// Draws a [`Column`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the children of the [`Column`]
|
||||
/// - the [`Layout`] of the [`Column`] and its children
|
||||
/// - the cursor position
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
content: &[Element<'_, Message, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
|
|
|
|||
|
|
@ -4,19 +4,23 @@ use std::hash::Hash;
|
|||
use crate::alignment::{self, Alignment};
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
||||
Widget,
|
||||
Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding,
|
||||
Point, Rectangle, Widget,
|
||||
};
|
||||
|
||||
use std::u32;
|
||||
|
||||
pub use iced_style::container::{Style, StyleSheet};
|
||||
|
||||
/// An element decorating some content.
|
||||
///
|
||||
/// It is normally used for alignment purposes.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Container<'a, Message, Renderer: self::Renderer> {
|
||||
pub struct Container<'a, Message, Renderer> {
|
||||
padding: Padding,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -24,13 +28,13 @@ pub struct Container<'a, Message, Renderer: self::Renderer> {
|
|||
max_height: u32,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Creates an empty [`Container`].
|
||||
pub fn new<T>(content: T) -> Self
|
||||
|
|
@ -45,7 +49,7 @@ where
|
|||
max_height: u32::MAX,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
style: Renderer::Style::default(),
|
||||
style_sheet: Default::default(),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
|
|
@ -105,8 +109,11 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Container`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +121,7 @@ where
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Container<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -172,25 +179,44 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
) -> mouse::Interaction {
|
||||
self.content.widget.mouse_interaction(
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
&self.style,
|
||||
&self.content,
|
||||
layout.children().next().unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let style = self.style_sheet.style();
|
||||
|
||||
draw_background(renderer, &style, layout.bounds());
|
||||
|
||||
self.content.draw(
|
||||
renderer,
|
||||
&renderer::Style {
|
||||
text_color: style
|
||||
.text_color
|
||||
.unwrap_or(renderer_style.text_color),
|
||||
},
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
struct Marker;
|
||||
std::any::TypeId::of::<Marker>().hash(state);
|
||||
|
|
@ -212,33 +238,33 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Container`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Container`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Draws a [`Container`].
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
style: &Self::Style,
|
||||
content: &Element<'_, Message, Self>,
|
||||
content_layout: Layout<'_>,
|
||||
) -> Self::Output;
|
||||
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
|
||||
pub fn draw_background<Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
style: &Style,
|
||||
bounds: Rectangle,
|
||||
) where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
if style.background.is_some() || style.border_width > 0.0 {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
},
|
||||
style
|
||||
.background
|
||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
|
|
|
|||
|
|
@ -2,21 +2,19 @@
|
|||
pub mod viewer;
|
||||
pub use viewer::Viewer;
|
||||
|
||||
use crate::image::{self, Handle};
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
use std::{
|
||||
hash::{Hash, Hasher as _},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A frame that displays an image while keeping aspect ratio.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::Image;
|
||||
/// # use iced_native::widget::Image;
|
||||
/// #
|
||||
/// let image = Image::new("resources/ferris.png");
|
||||
/// ```
|
||||
|
|
@ -54,7 +52,7 @@ impl Image {
|
|||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Image
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -92,12 +90,12 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(self.handle.clone(), layout)
|
||||
) {
|
||||
renderer.draw(self.handle.clone(), layout.bounds());
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -110,129 +108,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// An [`Image`] handle.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handle {
|
||||
id: u64,
|
||||
data: Arc<Data>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Creates an image [`Handle`] pointing to the image of the given path.
|
||||
///
|
||||
/// Makes an educated guess about the image format by examining the data in the file.
|
||||
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
|
||||
Self::from_data(Data::Path(path.into()))
|
||||
}
|
||||
|
||||
/// Creates an image [`Handle`] containing the image pixels directly. This
|
||||
/// function expects the input data to be provided as a `Vec<u8>` of BGRA
|
||||
/// pixels.
|
||||
///
|
||||
/// This is useful if you have already decoded your image.
|
||||
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
|
||||
Self::from_data(Data::Pixels {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an image [`Handle`] containing the image data directly.
|
||||
///
|
||||
/// Makes an educated guess about the image format by examining the given data.
|
||||
///
|
||||
/// This is useful if you already have your image loaded in-memory, maybe
|
||||
/// because you downloaded or generated it procedurally.
|
||||
pub fn from_memory(bytes: Vec<u8>) -> Handle {
|
||||
Self::from_data(Data::Bytes(bytes))
|
||||
}
|
||||
|
||||
fn from_data(data: Data) -> Handle {
|
||||
let mut hasher = Hasher::default();
|
||||
data.hash(&mut hasher);
|
||||
|
||||
Handle {
|
||||
id: hasher.finish(),
|
||||
data: Arc::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unique identifier of the [`Handle`].
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns a reference to the image [`Data`].
|
||||
pub fn data(&self) -> &Data {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Handle
|
||||
where
|
||||
T: Into<PathBuf>,
|
||||
{
|
||||
fn from(path: T) -> Handle {
|
||||
Handle::from_path(path.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Handle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of an [`Image`].
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Data {
|
||||
/// File data
|
||||
Path(PathBuf),
|
||||
|
||||
/// In-memory data
|
||||
Bytes(Vec<u8>),
|
||||
|
||||
/// Decoded image pixels in BGRA format.
|
||||
Pixels {
|
||||
/// The width of the image.
|
||||
width: u32,
|
||||
/// The height of the image.
|
||||
height: u32,
|
||||
/// The pixels.
|
||||
pixels: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Data {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||
Data::Pixels { width, height, .. } => {
|
||||
write!(f, "Pixels({} * {})", width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The renderer of an [`Image`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being able to use
|
||||
/// an [`Image`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Returns the dimensions of an [`Image`] located on the given path.
|
||||
fn dimensions(&self, handle: &Handle) -> (u32, u32);
|
||||
|
||||
/// Draws an [`Image`].
|
||||
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
fn from(image: Image) -> Element<'a, Message, Renderer> {
|
||||
Element::new(image)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::event::{self, Event};
|
|||
use crate::image;
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
||||
Widget,
|
||||
|
|
@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {
|
|||
/// will be respected.
|
||||
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
|
||||
where
|
||||
Renderer: self::Renderer + image::Renderer,
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
let (width, height) = renderer.dimensions(&self.handle);
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ impl<'a> Viewer<'a> {
|
|||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
|
||||
where
|
||||
Renderer: self::Renderer + image::Renderer,
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -280,14 +281,32 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
if self.state.is_cursor_grabbed() {
|
||||
mouse::Interaction::Grabbing
|
||||
} else if is_mouse_over {
|
||||
mouse::Interaction::Grab
|
||||
} else {
|
||||
mouse::Interaction::Idle
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let image_size = self.image_size(renderer, bounds.size());
|
||||
|
|
@ -301,17 +320,19 @@ where
|
|||
image_top_left - self.state.offset(bounds, image_size)
|
||||
};
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
&self.state,
|
||||
bounds,
|
||||
image_size,
|
||||
translation,
|
||||
self.handle.clone(),
|
||||
is_mouse_over,
|
||||
)
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
renderer.with_translation(translation, |renderer| {
|
||||
image::Renderer::draw(
|
||||
renderer,
|
||||
self.handle.clone(),
|
||||
Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
..Rectangle::with_size(image_size)
|
||||
},
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -373,38 +394,9 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of an [`Viewer`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Viewer`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer + Sized {
|
||||
/// Draws the [`Viewer`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the [`State`] of the [`Viewer`]
|
||||
/// - the bounds of the [`Viewer`] widget
|
||||
/// - the [`Size`] of the scaled [`Viewer`] image
|
||||
/// - the translation of the clipped image
|
||||
/// - the [`Handle`] to the underlying image
|
||||
/// - whether the mouse is over the [`Viewer`] or not
|
||||
///
|
||||
/// [`Handle`]: image::Handle
|
||||
fn draw(
|
||||
&mut self,
|
||||
state: &State,
|
||||
bounds: Rectangle,
|
||||
image_size: Size,
|
||||
translation: Vector,
|
||||
handle: image::Handle,
|
||||
is_mouse_over: bool,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer + image::Renderer,
|
||||
Renderer: 'a + image::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
|
||||
|
|
|
|||
|
|
@ -27,18 +27,19 @@ pub use split::Split;
|
|||
pub use state::State;
|
||||
pub use title_bar::TitleBar;
|
||||
|
||||
use crate::container;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::row;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
|
||||
Widget,
|
||||
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
Vector, Widget,
|
||||
};
|
||||
|
||||
pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||
|
||||
/// A collection of panes distributed using either vertical or horizontal splits
|
||||
/// to completely fill the space available.
|
||||
///
|
||||
|
|
@ -61,10 +62,10 @@ use crate::{
|
|||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::{pane_grid, Text};
|
||||
/// # use iced_native::widget::{pane_grid, Text};
|
||||
/// #
|
||||
/// # type PaneGrid<'a, Message> =
|
||||
/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>;
|
||||
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// enum PaneState {
|
||||
/// SomePane,
|
||||
|
|
@ -89,7 +90,7 @@ use crate::{
|
|||
/// .on_resize(10, Message::PaneResized);
|
||||
/// ```
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
|
||||
pub struct PaneGrid<'a, Message, Renderer> {
|
||||
state: &'a mut state::Internal,
|
||||
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
|
||||
width: Length,
|
||||
|
|
@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
|
|||
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
||||
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
||||
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
||||
style: <Renderer as self::Renderer>::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
|
||||
///
|
||||
|
|
@ -130,7 +131,7 @@ where
|
|||
on_click: None,
|
||||
on_drag: None,
|
||||
on_resize: None,
|
||||
style: Default::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -190,18 +191,15 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`PaneGrid`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: impl Into<<Renderer as self::Renderer>::Style>,
|
||||
) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self {
|
||||
self.style_sheet = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn click_pane(
|
||||
&mut self,
|
||||
|
|
@ -318,7 +316,7 @@ pub struct ResizeEvent {
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for PaneGrid<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer + container::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -473,14 +471,43 @@ where
|
|||
.fold(event_status, event::Status::merge)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
if self.state.picked_pane().is_some() {
|
||||
return mouse::Interaction::Grab;
|
||||
}
|
||||
|
||||
if let Some((_, axis)) = self.state.picked_split() {
|
||||
return match axis {
|
||||
Axis::Horizontal => mouse::Interaction::ResizingHorizontally,
|
||||
Axis::Vertical => mouse::Interaction::ResizingVertically,
|
||||
};
|
||||
}
|
||||
|
||||
self.elements
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|((_pane, content), layout)| {
|
||||
content.mouse_interaction(layout, cursor_position, viewport)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let picked_pane = self.state.picked_pane();
|
||||
|
||||
let picked_split = self
|
||||
.state
|
||||
.picked_split()
|
||||
|
|
@ -529,17 +556,89 @@ where
|
|||
None => None,
|
||||
});
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
defaults,
|
||||
&self.elements,
|
||||
self.state.picked_pane(),
|
||||
picked_split,
|
||||
layout,
|
||||
&self.style,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
let pane_cursor_position = if picked_pane.is_some() {
|
||||
// TODO: Remove once cursor availability is encoded in the type
|
||||
// system
|
||||
Point::new(-1.0, -1.0)
|
||||
} else {
|
||||
cursor_position
|
||||
};
|
||||
|
||||
for ((id, pane), layout) in self.elements.iter().zip(layout.children())
|
||||
{
|
||||
match picked_pane {
|
||||
Some((dragging, origin)) if *id == dragging => {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
renderer.with_translation(
|
||||
cursor_position
|
||||
- Point::new(
|
||||
bounds.x + origin.x,
|
||||
bounds.y + origin.y,
|
||||
),
|
||||
|renderer| {
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
pane.draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
pane_cursor_position,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
pane.draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
pane_cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((axis, split_region, is_picked)) = picked_split {
|
||||
let highlight = if is_picked {
|
||||
self.style_sheet.picked_split()
|
||||
} else {
|
||||
self.style_sheet.hovered_split()
|
||||
};
|
||||
|
||||
if let Some(highlight) = highlight {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: match axis {
|
||||
Axis::Horizontal => Rectangle {
|
||||
x: split_region.x,
|
||||
y: (split_region.y
|
||||
+ (split_region.height - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
width: split_region.width,
|
||||
height: highlight.width,
|
||||
},
|
||||
Axis::Vertical => Rectangle {
|
||||
x: (split_region.x
|
||||
+ (split_region.width - highlight.width)
|
||||
/ 2.0)
|
||||
.round(),
|
||||
y: split_region.y,
|
||||
width: highlight.width,
|
||||
height: split_region.height,
|
||||
},
|
||||
},
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
highlight.color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -569,78 +668,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`PaneGrid`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`PaneGrid`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer + container::Renderer + Sized {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Draws a [`PaneGrid`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the elements of the [`PaneGrid`]
|
||||
/// - the [`Pane`] that is currently being dragged
|
||||
/// - the [`Axis`] that is currently being resized
|
||||
/// - the [`Layout`] of the [`PaneGrid`] and its elements
|
||||
/// - the cursor position
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
content: &[(Pane, Content<'_, Message, Self>)],
|
||||
dragging: Option<(Pane, Point)>,
|
||||
resizing: Option<(Axis, Rectangle, bool)>,
|
||||
layout: Layout<'_>,
|
||||
style: &<Self as self::Renderer>::Style,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output;
|
||||
|
||||
/// Draws a [`Pane`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the [`TitleBar`] of the [`Pane`], if any
|
||||
/// - the [`Content`] of the [`Pane`]
|
||||
/// - the [`Layout`] of the [`Pane`] and its elements
|
||||
/// - the cursor position
|
||||
fn draw_pane<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
style: &<Self as container::Renderer>::Style,
|
||||
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
|
||||
body: (&Element<'_, Message, Self>, Layout<'_>),
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output;
|
||||
|
||||
/// Draws a [`TitleBar`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the bounds, style of the [`TitleBar`]
|
||||
/// - the style of the [`TitleBar`]
|
||||
/// - the content of the [`TitleBar`] with its layout
|
||||
/// - the controls of the [`TitleBar`] with their [`Layout`], if any
|
||||
/// - the cursor position
|
||||
fn draw_title_bar<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
style: &<Self as container::Renderer>::Style,
|
||||
content: (&Element<'_, Message, Self>, Layout<'_>),
|
||||
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer + row::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::pane_grid::Axis;
|
||||
use crate::widget::pane_grid::Axis;
|
||||
|
||||
/// The arrangement of a [`PaneGrid`].
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,30 +1,32 @@
|
|||
use crate::container;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::pane_grid::{self, TitleBar};
|
||||
use crate::renderer;
|
||||
use crate::widget::container;
|
||||
use crate::widget::pane_grid::TitleBar;
|
||||
use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
|
||||
|
||||
/// The content of a [`Pane`].
|
||||
///
|
||||
/// [`Pane`]: crate::widget::pane_grid::Pane
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
|
||||
pub struct Content<'a, Message, Renderer> {
|
||||
title_bar: Option<TitleBar<'a, Message, Renderer>>,
|
||||
body: Element<'a, Message, Renderer>,
|
||||
style: <Renderer as container::Renderer>::Style,
|
||||
style_sheet: Box<dyn container::StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: pane_grid::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Creates a new [`Content`] with the provided body.
|
||||
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
Self {
|
||||
title_bar: None,
|
||||
body: body.into(),
|
||||
style: Default::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,16 +42,16 @@ where
|
|||
/// Sets the style of the [`Content`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: impl Into<<Renderer as container::Renderer>::Style>,
|
||||
style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style = style.into();
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: pane_grid::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
|
||||
///
|
||||
|
|
@ -57,35 +59,45 @@ where
|
|||
pub fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
{
|
||||
let style = self.style_sheet.style();
|
||||
|
||||
container::draw_background(renderer, &style, bounds);
|
||||
}
|
||||
|
||||
if let Some(title_bar) = &self.title_bar {
|
||||
let mut children = layout.children();
|
||||
let title_bar_layout = children.next().unwrap();
|
||||
let body_layout = children.next().unwrap();
|
||||
|
||||
renderer.draw_pane(
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
&self.style,
|
||||
Some((title_bar, title_bar_layout)),
|
||||
(&self.body, body_layout),
|
||||
let show_controls = bounds.contains(cursor_position);
|
||||
|
||||
title_bar.draw(
|
||||
renderer,
|
||||
style,
|
||||
title_bar_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
show_controls,
|
||||
);
|
||||
|
||||
self.body.draw(
|
||||
renderer,
|
||||
style,
|
||||
body_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
} else {
|
||||
renderer.draw_pane(
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
&self.style,
|
||||
None,
|
||||
(&self.body, layout),
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
self.body
|
||||
.draw(renderer, style, layout, cursor_position, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +198,40 @@ where
|
|||
event_status.merge(body_status)
|
||||
}
|
||||
|
||||
pub(crate) fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
let (body_layout, title_bar_interaction) =
|
||||
if let Some(title_bar) = &self.title_bar {
|
||||
let mut children = layout.children();
|
||||
let title_bar_layout = children.next().unwrap();
|
||||
|
||||
let is_over_pick_area = title_bar
|
||||
.is_over_pick_area(title_bar_layout, cursor_position);
|
||||
|
||||
if is_over_pick_area {
|
||||
return mouse::Interaction::Grab;
|
||||
}
|
||||
|
||||
let mouse_interaction = title_bar.mouse_interaction(
|
||||
title_bar_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
(children.next().unwrap(), mouse_interaction)
|
||||
} else {
|
||||
(layout, mouse::Interaction::default())
|
||||
};
|
||||
|
||||
self.body
|
||||
.mouse_interaction(body_layout, cursor_position, viewport)
|
||||
.max(title_bar_interaction)
|
||||
}
|
||||
|
||||
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
|
||||
if let Some(title_bar) = &self.title_bar {
|
||||
title_bar.hash_layout(state);
|
||||
|
|
@ -215,7 +261,7 @@ where
|
|||
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
|
||||
where
|
||||
T: Into<Element<'a, Message, Renderer>>,
|
||||
Renderer: pane_grid::Renderer + container::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn from(element: T) -> Self {
|
||||
Self::new(element)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use crate::{
|
||||
pane_grid::{Axis, Pane, Split},
|
||||
Rectangle, Size,
|
||||
};
|
||||
use crate::widget::pane_grid::{Axis, Pane, Split};
|
||||
use crate::{Rectangle, Size};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
pane_grid::{Axis, Configuration, Direction, Node, Pane, Split},
|
||||
Hasher, Point, Rectangle, Size,
|
||||
use crate::widget::pane_grid::{
|
||||
Axis, Configuration, Direction, Node, Pane, Split,
|
||||
};
|
||||
use crate::{Hasher, Point, Rectangle, Size};
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use crate::container;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::pane_grid;
|
||||
use crate::renderer;
|
||||
use crate::widget::container;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
|
||||
};
|
||||
|
|
@ -11,17 +12,17 @@ use crate::{
|
|||
///
|
||||
/// [`Pane`]: crate::widget::pane_grid::Pane
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
|
||||
pub struct TitleBar<'a, Message, Renderer> {
|
||||
content: Element<'a, Message, Renderer>,
|
||||
controls: Option<Element<'a, Message, Renderer>>,
|
||||
padding: Padding,
|
||||
always_show_controls: bool,
|
||||
style: <Renderer as container::Renderer>::Style,
|
||||
style_sheet: Box<dyn container::StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: pane_grid::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Creates a new [`TitleBar`] with the given content.
|
||||
pub fn new<E>(content: E) -> Self
|
||||
|
|
@ -33,7 +34,7 @@ where
|
|||
controls: None,
|
||||
padding: Padding::ZERO,
|
||||
always_show_controls: false,
|
||||
style: Default::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,9 +56,9 @@ where
|
|||
/// Sets the style of the [`TitleBar`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: impl Into<<Renderer as container::Renderer>::Style>,
|
||||
style: impl Into<Box<dyn container::StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style = style.into();
|
||||
self.style_sheet = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +78,7 @@ where
|
|||
|
||||
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: pane_grid::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
|
||||
///
|
||||
|
|
@ -85,39 +86,47 @@ where
|
|||
pub fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
inherited_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
show_controls: bool,
|
||||
) -> Renderer::Output {
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let style = self.style_sheet.style();
|
||||
let inherited_style = renderer::Style {
|
||||
text_color: style.text_color.unwrap_or(inherited_style.text_color),
|
||||
};
|
||||
|
||||
container::draw_background(renderer, &style, bounds);
|
||||
|
||||
let mut children = layout.children();
|
||||
let padded = children.next().unwrap();
|
||||
|
||||
let mut children = padded.children();
|
||||
let title_layout = children.next().unwrap();
|
||||
|
||||
let controls = if let Some(controls) = &self.controls {
|
||||
self.content.draw(
|
||||
renderer,
|
||||
&inherited_style,
|
||||
title_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if let Some(controls) = &self.controls {
|
||||
let controls_layout = children.next().unwrap();
|
||||
|
||||
if show_controls || self.always_show_controls {
|
||||
Some((controls, controls_layout))
|
||||
} else {
|
||||
None
|
||||
controls.draw(
|
||||
renderer,
|
||||
&inherited_style,
|
||||
controls_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
renderer.draw_title_bar(
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
&self.style,
|
||||
(&self.content, title_layout),
|
||||
controls,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the mouse cursor is over the pick area of the
|
||||
|
|
@ -244,6 +253,35 @@ where
|
|||
control_status.merge(title_status)
|
||||
}
|
||||
|
||||
pub(crate) fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
let mut children = layout.children();
|
||||
let padded = children.next().unwrap();
|
||||
|
||||
let mut children = padded.children();
|
||||
let title_layout = children.next().unwrap();
|
||||
|
||||
let title_interaction = self.content.mouse_interaction(
|
||||
title_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
if let Some(controls) = &self.controls {
|
||||
let controls_layout = children.next().unwrap();
|
||||
|
||||
controls
|
||||
.mouse_interaction(controls_layout, cursor_position, viewport)
|
||||
.max(title_interaction)
|
||||
} else {
|
||||
title_interaction
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
//! Display a dropdown list of selectable values.
|
||||
use crate::alignment;
|
||||
use crate::event::{self, Event};
|
||||
use crate::keyboard;
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::overlay::menu::{self, Menu};
|
||||
use crate::scrollable;
|
||||
use crate::text;
|
||||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::touch;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
||||
|
|
@ -14,9 +15,11 @@ use crate::{
|
|||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use iced_style::pick_list::{Style, StyleSheet};
|
||||
|
||||
/// A widget for selecting a single value from a list of options.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct PickList<'a, T, Message, Renderer: self::Renderer>
|
||||
pub struct PickList<'a, T, Message, Renderer: text::Renderer>
|
||||
where
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
{
|
||||
|
|
@ -33,7 +36,7 @@ where
|
|||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: Renderer::Font,
|
||||
style: <Renderer as self::Renderer>::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
/// The local state of a [`PickList`].
|
||||
|
|
@ -58,12 +61,15 @@ impl<T> Default for State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer: self::Renderer>
|
||||
impl<'a, T: 'a, Message, Renderer: text::Renderer>
|
||||
PickList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
{
|
||||
/// The default padding of a [`PickList`].
|
||||
pub const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||
|
||||
/// Creates a new [`PickList`] with the given [`State`], a list of options,
|
||||
/// the current selected value, and the message to produce when an option is
|
||||
/// selected.
|
||||
|
|
@ -93,9 +99,9 @@ where
|
|||
selected,
|
||||
width: Length::Shrink,
|
||||
text_size: None,
|
||||
padding: Renderer::DEFAULT_PADDING,
|
||||
padding: Self::DEFAULT_PADDING,
|
||||
font: Default::default(),
|
||||
style: Default::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,9 +138,9 @@ where
|
|||
/// Sets the style of the [`PickList`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: impl Into<<Renderer as self::Renderer>::Style>,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style = style.into();
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +151,7 @@ where
|
|||
T: Clone + ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'static,
|
||||
Renderer: self::Renderer + scrollable::Renderer + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -320,25 +326,90 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
layout.bounds(),
|
||||
cursor_position,
|
||||
self.selected.as_ref().map(ToString::to_string),
|
||||
self.placeholder.as_ref().map(String::as_str),
|
||||
self.padding,
|
||||
self.text_size.unwrap_or(renderer.default_size()),
|
||||
self.font,
|
||||
&self.style,
|
||||
)
|
||||
) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let is_selected = self.selected.is_some();
|
||||
|
||||
let style = if is_mouse_over {
|
||||
self.style_sheet.hovered()
|
||||
} else {
|
||||
self.style_sheet.active()
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_color: style.border_color,
|
||||
border_width: style.border_width,
|
||||
border_radius: style.border_radius,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: &Renderer::ARROW_DOWN_ICON.to_string(),
|
||||
font: Renderer::ICON_FONT,
|
||||
size: bounds.height * style.icon_size,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + bounds.width
|
||||
- f32::from(self.padding.horizontal()),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
color: style.text_color,
|
||||
horizontal_alignment: alignment::Horizontal::Right,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
|
||||
if let Some(label) = self
|
||||
.selected
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.as_ref()
|
||||
.or_else(|| self.placeholder.as_ref())
|
||||
{
|
||||
renderer.fill_text(Text {
|
||||
content: label,
|
||||
size: f32::from(
|
||||
self.text_size.unwrap_or(renderer.default_size()),
|
||||
),
|
||||
font: self.font,
|
||||
color: is_selected
|
||||
.then(|| style.text_color)
|
||||
.unwrap_or(style.placeholder_color),
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + f32::from(self.padding.left),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
|
|
@ -357,7 +428,7 @@ where
|
|||
.width(bounds.width.round() as u16)
|
||||
.padding(self.padding)
|
||||
.font(self.font)
|
||||
.style(Renderer::menu_style(&self.style));
|
||||
.style(self.style_sheet.menu());
|
||||
|
||||
if let Some(text_size) = self.text_size {
|
||||
menu = menu.text_size(text_size);
|
||||
|
|
@ -370,44 +441,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`PickList`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`PickList`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: text::Renderer + menu::Renderer {
|
||||
/// The default padding of a [`PickList`].
|
||||
const DEFAULT_PADDING: Padding;
|
||||
|
||||
/// The [`PickList`] style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Returns the style of the [`Menu`] of the [`PickList`].
|
||||
fn menu_style(
|
||||
style: &<Self as Renderer>::Style,
|
||||
) -> <Self as menu::Renderer>::Style;
|
||||
|
||||
/// Draws a [`PickList`].
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
selected: Option<String>,
|
||||
placeholder: Option<&str>,
|
||||
padding: Padding,
|
||||
text_size: u16,
|
||||
font: Self::Font,
|
||||
style: &<Self as Renderer>::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
|
||||
for PickList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Renderer: self::Renderer + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Message: 'static,
|
||||
{
|
||||
fn into(self) -> Element<'a, Message, Renderer> {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
//! Provide progress feedback to your users.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
use std::{hash::Hash, ops::RangeInclusive};
|
||||
|
||||
pub use iced_style::progress_bar::{Style, StyleSheet};
|
||||
|
||||
/// A bar that displays progress.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use iced_native::renderer::Null;
|
||||
/// #
|
||||
/// # pub type ProgressBar = iced_native::ProgressBar<Null>;
|
||||
/// # use iced_native::widget::ProgressBar;
|
||||
/// let value = 50.0;
|
||||
///
|
||||
/// ProgressBar::new(0.0..=100.0, value);
|
||||
|
|
@ -19,15 +21,18 @@ use std::{hash::Hash, ops::RangeInclusive};
|
|||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct ProgressBar<Renderer: self::Renderer> {
|
||||
pub struct ProgressBar<'a> {
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
width: Length,
|
||||
height: Option<Length>,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<Renderer: self::Renderer> ProgressBar<Renderer> {
|
||||
impl<'a> ProgressBar<'a> {
|
||||
/// The default height of a [`ProgressBar`].
|
||||
pub const DEFAULT_HEIGHT: u16 = 30;
|
||||
|
||||
/// Creates a new [`ProgressBar`].
|
||||
///
|
||||
/// It expects:
|
||||
|
|
@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
|
|||
range,
|
||||
width: Length::Fill,
|
||||
height: None,
|
||||
style: Renderer::Style::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,23 +61,25 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
|
|||
}
|
||||
|
||||
/// Sets the style of the [`ProgressBar`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT))
|
||||
self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -80,10 +87,9 @@ where
|
|||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width).height(
|
||||
self.height
|
||||
.unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)),
|
||||
);
|
||||
let limits = limits
|
||||
.width(self.width)
|
||||
.height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));
|
||||
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
|
|
@ -93,17 +99,47 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(
|
||||
layout.bounds(),
|
||||
self.range.clone(),
|
||||
self.value,
|
||||
&self.style,
|
||||
)
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let (range_start, range_end) = self.range.clone().into_inner();
|
||||
|
||||
let active_progress_width = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
bounds.width * (self.value - range_start)
|
||||
/ (range_end - range_start)
|
||||
};
|
||||
|
||||
let style = self.style_sheet.style();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle { ..bounds },
|
||||
border_radius: style.border_radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
if active_progress_width > 0.0 {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
width: active_progress_width,
|
||||
..bounds
|
||||
},
|
||||
border_radius: style.border_radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.bar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -115,45 +151,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`ProgressBar`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`ProgressBar`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// The default height of a [`ProgressBar`].
|
||||
const DEFAULT_HEIGHT: u16;
|
||||
|
||||
/// Draws a [`ProgressBar`].
|
||||
///
|
||||
/// It receives:
|
||||
/// * the bounds of the [`ProgressBar`]
|
||||
/// * the range of values of the [`ProgressBar`]
|
||||
/// * the current value of the [`ProgressBar`]
|
||||
/// * maybe a specific background of the [`ProgressBar`]
|
||||
/// * maybe a specific active color of the [`ProgressBar`]
|
||||
fn draw(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
style: &Self::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
|
||||
impl<'a, Message, Renderer> From<ProgressBar<'a>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
progress_bar: ProgressBar<Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(progress_bar)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,27 @@
|
|||
//! Create choices using radio buttons.
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::alignment::{self, Alignment};
|
||||
use crate::alignment;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::row;
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::touch;
|
||||
use crate::widget::{self, Row, Text};
|
||||
use crate::{
|
||||
Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row,
|
||||
Text, Widget,
|
||||
Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point,
|
||||
Rectangle, Widget,
|
||||
};
|
||||
|
||||
pub use iced_style::radio::{Style, StyleSheet};
|
||||
|
||||
/// A circular button representing a choice.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # type Radio<Message> =
|
||||
/// # iced_native::Radio<Message, iced_native::renderer::Null>;
|
||||
/// # type Radio<'a, Message> =
|
||||
/// # iced_native::widget::Radio<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// pub enum Choice {
|
||||
|
|
@ -40,7 +43,7 @@ use crate::{
|
|||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
|
||||
pub struct Radio<'a, Message, Renderer: text::Renderer> {
|
||||
is_selected: bool,
|
||||
on_click: Message,
|
||||
label: String,
|
||||
|
|
@ -50,14 +53,19 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
|
|||
text_size: Option<u16>,
|
||||
text_color: Option<Color>,
|
||||
font: Renderer::Font,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<Message, Renderer: self::Renderer + text::Renderer>
|
||||
Radio<Message, Renderer>
|
||||
impl<'a, Message, Renderer: text::Renderer> Radio<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
{
|
||||
/// The default size of a [`Radio`] button.
|
||||
pub const DEFAULT_SIZE: u16 = 28;
|
||||
|
||||
/// The default spacing of a [`Radio`] button.
|
||||
pub const DEFAULT_SPACING: u16 = 15;
|
||||
|
||||
/// Creates a new [`Radio`] button.
|
||||
///
|
||||
/// It expects:
|
||||
|
|
@ -81,12 +89,12 @@ where
|
|||
on_click: f(value),
|
||||
label: label.into(),
|
||||
width: Length::Shrink,
|
||||
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
|
||||
spacing: Renderer::DEFAULT_SPACING, //15
|
||||
size: Self::DEFAULT_SIZE,
|
||||
spacing: Self::DEFAULT_SPACING, //15
|
||||
text_size: None,
|
||||
text_color: None,
|
||||
font: Default::default(),
|
||||
style: Renderer::Style::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,16 +135,20 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Radio`] button.
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Radio<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer + text::Renderer + row::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -192,43 +204,88 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
if layout.bounds().contains(cursor_position) {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let mut children = layout.children();
|
||||
|
||||
let radio_layout = children.next().unwrap();
|
||||
let label_layout = children.next().unwrap();
|
||||
let radio_bounds = radio_layout.bounds();
|
||||
|
||||
let label = text::Renderer::draw(
|
||||
renderer,
|
||||
defaults,
|
||||
label_layout.bounds(),
|
||||
&self.label,
|
||||
self.text_size.unwrap_or(renderer.default_size()),
|
||||
self.font,
|
||||
self.text_color,
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Center,
|
||||
);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
radio_bounds,
|
||||
self.is_selected,
|
||||
is_mouse_over,
|
||||
label,
|
||||
&self.style,
|
||||
)
|
||||
let mut children = layout.children();
|
||||
|
||||
{
|
||||
let layout = children.next().unwrap();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let size = bounds.width;
|
||||
let dot_size = size / 2.0;
|
||||
|
||||
let style = if is_mouse_over {
|
||||
self.style_sheet.hovered()
|
||||
} else {
|
||||
self.style_sheet.active()
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_radius: size / 2.0,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
if self.is_selected {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + dot_size / 2.0,
|
||||
y: bounds.y + dot_size / 2.0,
|
||||
width: bounds.width - dot_size,
|
||||
height: bounds.height - dot_size,
|
||||
},
|
||||
border_radius: dot_size / 2.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.dot_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let label_layout = children.next().unwrap();
|
||||
|
||||
widget::text::draw(
|
||||
renderer,
|
||||
style,
|
||||
label_layout,
|
||||
&self.label,
|
||||
self.font,
|
||||
self.text_size,
|
||||
self.text_color,
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Center,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -239,46 +296,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Radio`] button.
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Radio`] button in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// The default size of a [`Radio`] button.
|
||||
const DEFAULT_SIZE: u16;
|
||||
|
||||
/// The default spacing of a [`Radio`] button.
|
||||
const DEFAULT_SPACING: u16;
|
||||
|
||||
/// Draws a [`Radio`] button.
|
||||
///
|
||||
/// It receives:
|
||||
/// * the bounds of the [`Radio`]
|
||||
/// * whether the [`Radio`] is selected or not
|
||||
/// * whether the mouse is over the [`Radio`] or not
|
||||
/// * the drawn label of the [`Radio`]
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
is_selected: bool,
|
||||
is_mouse_over: bool,
|
||||
label: Self::Output,
|
||||
style: &Self::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
|
||||
impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
|
||||
Renderer: 'a + text::Renderer,
|
||||
{
|
||||
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
|
||||
fn from(
|
||||
radio: Radio<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(radio)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
//! Distribute content horizontally.
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
|
||||
Rectangle, Widget,
|
||||
|
|
@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
|
|||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Row<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -161,21 +163,37 @@ where
|
|||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(
|
||||
defaults,
|
||||
&self.children,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
) -> mouse::Interaction {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
child.widget.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||
child.draw(renderer, style, layout, cursor_position, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -207,33 +225,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Row`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Row`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer + Sized {
|
||||
/// Draws a [`Row`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the children of the [`Row`]
|
||||
/// - the [`Layout`] of the [`Row`] and its children
|
||||
/// - the cursor position
|
||||
fn draw<Message>(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
children: &[Element<'_, Message, Self>],
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
//! Display a horizontal or vertical rule for dividing content.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::{
|
||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
};
|
||||
pub use iced_style::rule::{FillMode, Style, StyleSheet};
|
||||
|
||||
/// Display a horizontal or vertical rule for dividing content.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Rule<Renderer: self::Renderer> {
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Rule<'a> {
|
||||
width: Length,
|
||||
height: Length,
|
||||
style: Renderer::Style,
|
||||
is_horizontal: bool,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<Renderer: self::Renderer> Rule<Renderer> {
|
||||
impl<'a> Rule<'a> {
|
||||
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
|
||||
pub fn horizontal(spacing: u16) -> Self {
|
||||
Rule {
|
||||
width: Length::Fill,
|
||||
height: Length::from(Length::Units(spacing)),
|
||||
style: Renderer::Style::default(),
|
||||
is_horizontal: true,
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
|
|||
Rule {
|
||||
width: Length::from(Length::Units(spacing)),
|
||||
height: Length::Fill,
|
||||
style: Renderer::Style::default(),
|
||||
is_horizontal: false,
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Rule`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -68,12 +74,53 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let style = self.style_sheet.style();
|
||||
|
||||
let bounds = if self.is_horizontal {
|
||||
let line_y = (bounds.y + (bounds.height / 2.0)
|
||||
- (style.width as f32 / 2.0))
|
||||
.round();
|
||||
|
||||
let (offset, line_width) = style.fill_mode.fill(bounds.width);
|
||||
let line_x = bounds.x + offset;
|
||||
|
||||
Rectangle {
|
||||
x: line_x,
|
||||
y: line_y,
|
||||
width: line_width,
|
||||
height: style.width as f32,
|
||||
}
|
||||
} else {
|
||||
let line_x = (bounds.x + (bounds.width / 2.0)
|
||||
- (style.width as f32 / 2.0))
|
||||
.round();
|
||||
|
||||
let (offset, line_height) = style.fill_mode.fill(bounds.height);
|
||||
let line_y = bounds.y + offset;
|
||||
|
||||
Rectangle {
|
||||
x: line_x,
|
||||
y: line_y,
|
||||
width: style.width as f32,
|
||||
height: line_height,
|
||||
}
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_radius: style.radius,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.color,
|
||||
);
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -85,32 +132,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Rule`].
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Draws a [`Rule`].
|
||||
///
|
||||
/// It receives:
|
||||
/// * the bounds of the [`Rule`]
|
||||
/// * the style of the [`Rule`]
|
||||
/// * whether the [`Rule`] is horizontal (true) or vertical (false)
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
style: &Self::Style,
|
||||
is_horizontal: bool,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Rule<Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
|
||||
fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(rule)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
//! Navigate an endless amount of content with a scrollbar.
|
||||
use crate::column;
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::widget::Column;
|
||||
use crate::{
|
||||
Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding,
|
||||
Point, Rectangle, Size, Vector, Widget,
|
||||
Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length,
|
||||
Padding, Point, Rectangle, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::{f32, hash::Hash, u32};
|
||||
|
||||
pub use iced_style::scrollable::StyleSheet;
|
||||
|
||||
/// A widget that can vertically display an infinite amount of content with a
|
||||
/// scrollbar.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
|
||||
pub struct Scrollable<'a, Message, Renderer> {
|
||||
state: &'a mut State,
|
||||
height: Length,
|
||||
max_height: u32,
|
||||
|
|
@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
|
|||
scroller_width: u16,
|
||||
content: Column<'a, Message, Renderer>,
|
||||
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
||||
impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
|
||||
/// Creates a new [`Scrollable`] with the given [`State`].
|
||||
pub fn new(state: &'a mut State) -> Self {
|
||||
Scrollable {
|
||||
|
|
@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
|||
scroller_width: 10,
|
||||
content: Column::new(),
|
||||
on_scroll: None,
|
||||
style: Renderer::Style::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,8 +123,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Scrollable`] .
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -151,12 +157,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn scrollbar(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
) -> Option<Scrollbar> {
|
||||
let offset = self.state.offset(bounds, content_bounds);
|
||||
|
||||
if content_bounds.height > bounds.height {
|
||||
let outer_width = self.scrollbar_width.max(self.scroller_width)
|
||||
+ 2 * self.scrollbar_margin;
|
||||
|
||||
let outer_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width - outer_width as f32,
|
||||
y: bounds.y,
|
||||
width: outer_width as f32,
|
||||
height: bounds.height,
|
||||
};
|
||||
|
||||
let scrollbar_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width
|
||||
- f32::from(outer_width / 2 + self.scrollbar_width / 2),
|
||||
y: bounds.y,
|
||||
width: self.scrollbar_width as f32,
|
||||
height: bounds.height,
|
||||
};
|
||||
|
||||
let ratio = bounds.height / content_bounds.height;
|
||||
let scroller_height = bounds.height * ratio;
|
||||
let y_offset = offset as f32 * ratio;
|
||||
|
||||
let scroller_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width
|
||||
- f32::from(outer_width / 2 + self.scroller_width / 2),
|
||||
y: scrollbar_bounds.y + y_offset,
|
||||
width: self.scroller_width as f32,
|
||||
height: scroller_height,
|
||||
};
|
||||
|
||||
Some(Scrollbar {
|
||||
outer_bounds,
|
||||
bounds: scrollbar_bounds,
|
||||
margin: self.scrollbar_margin,
|
||||
scroller: Scroller {
|
||||
bounds: scroller_bounds,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Scrollable<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Widget::<Message, Renderer>::width(&self.content)
|
||||
|
|
@ -202,15 +259,7 @@ where
|
|||
let content = layout.children().next().unwrap();
|
||||
let content_bounds = content.bounds();
|
||||
|
||||
let offset = self.state.offset(bounds, content_bounds);
|
||||
let scrollbar = renderer.scrollbar(
|
||||
bounds,
|
||||
content_bounds,
|
||||
offset,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
);
|
||||
let scrollbar = self.scrollbar(bounds, content_bounds);
|
||||
let is_mouse_over_scrollbar = scrollbar
|
||||
.as_ref()
|
||||
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
||||
|
|
@ -374,26 +423,16 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
let content_bounds = content_layout.bounds();
|
||||
let offset = self.state.offset(bounds, content_bounds);
|
||||
let scrollbar = renderer.scrollbar(
|
||||
bounds,
|
||||
content_bounds,
|
||||
offset,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
);
|
||||
let scrollbar = self.scrollbar(bounds, content_bounds);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let is_mouse_over_scrollbar = scrollbar
|
||||
|
|
@ -401,16 +440,18 @@ where
|
|||
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
||||
.unwrap_or(false);
|
||||
|
||||
let content = {
|
||||
if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() {
|
||||
mouse::Interaction::Idle
|
||||
} else {
|
||||
let offset = self.state.offset(bounds, content_bounds);
|
||||
|
||||
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
||||
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
||||
} else {
|
||||
Point::new(cursor_position.x, -1.0)
|
||||
};
|
||||
|
||||
self.content.draw(
|
||||
renderer,
|
||||
defaults,
|
||||
self.content.mouse_interaction(
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&Rectangle {
|
||||
|
|
@ -418,20 +459,114 @@ where
|
|||
..bounds
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
let content_bounds = content_layout.bounds();
|
||||
let offset = self.state.offset(bounds, content_bounds);
|
||||
let scrollbar = self.scrollbar(bounds, content_bounds);
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
let is_mouse_over_scrollbar = scrollbar
|
||||
.as_ref()
|
||||
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
|
||||
.unwrap_or(false);
|
||||
|
||||
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
|
||||
Point::new(cursor_position.x, cursor_position.y + offset as f32)
|
||||
} else {
|
||||
Point::new(cursor_position.x, -1.0)
|
||||
};
|
||||
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
&self.state,
|
||||
bounds,
|
||||
content_layout.bounds(),
|
||||
is_mouse_over,
|
||||
is_mouse_over_scrollbar,
|
||||
scrollbar,
|
||||
offset,
|
||||
&self.style,
|
||||
content,
|
||||
)
|
||||
if let Some(scrollbar) = scrollbar {
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
renderer.with_translation(
|
||||
Vector::new(0.0, -(offset as f32)),
|
||||
|renderer| {
|
||||
self.content.draw(
|
||||
renderer,
|
||||
style,
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&Rectangle {
|
||||
y: bounds.y + offset as f32,
|
||||
..bounds
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let style = if self.state.is_scroller_grabbed() {
|
||||
self.style_sheet.dragging()
|
||||
} else if is_mouse_over_scrollbar {
|
||||
self.style_sheet.hovered()
|
||||
} else {
|
||||
self.style_sheet.active()
|
||||
};
|
||||
|
||||
let is_scrollbar_visible =
|
||||
style.background.is_some() || style.border_width > 0.0;
|
||||
|
||||
renderer.with_layer(
|
||||
Rectangle {
|
||||
width: bounds.width + 2.0,
|
||||
height: bounds.height + 2.0,
|
||||
..bounds
|
||||
},
|
||||
|renderer| {
|
||||
if is_scrollbar_visible {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: scrollbar.bounds,
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
},
|
||||
style.background.unwrap_or(Background::Color(
|
||||
Color::TRANSPARENT,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
if is_mouse_over
|
||||
|| self.state.is_scroller_grabbed()
|
||||
|| is_scrollbar_visible
|
||||
{
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: scrollbar.scroller.bounds,
|
||||
border_radius: style.scroller.border_radius,
|
||||
border_width: style.scroller.border_width,
|
||||
border_color: style.scroller.border_color,
|
||||
},
|
||||
style.scroller.color,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.content.draw(
|
||||
renderer,
|
||||
style,
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&Rectangle {
|
||||
y: bounds.y + offset as f32,
|
||||
..bounds
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -577,19 +712,19 @@ impl State {
|
|||
|
||||
/// The scrollbar of a [`Scrollable`].
|
||||
#[derive(Debug)]
|
||||
pub struct Scrollbar {
|
||||
struct Scrollbar {
|
||||
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
|
||||
/// [`Scroller`].
|
||||
pub outer_bounds: Rectangle,
|
||||
outer_bounds: Rectangle,
|
||||
|
||||
/// The bounds of the [`Scrollbar`].
|
||||
pub bounds: Rectangle,
|
||||
bounds: Rectangle,
|
||||
|
||||
/// The margin within the [`Scrollbar`].
|
||||
pub margin: u16,
|
||||
margin: u16,
|
||||
|
||||
/// The bounds of the [`Scroller`].
|
||||
pub scroller: Scroller,
|
||||
scroller: Scroller,
|
||||
}
|
||||
|
||||
impl Scrollbar {
|
||||
|
|
@ -624,62 +759,15 @@ impl Scrollbar {
|
|||
|
||||
/// The handle of a [`Scrollbar`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Scroller {
|
||||
struct Scroller {
|
||||
/// The bounds of the [`Scroller`].
|
||||
pub bounds: Rectangle,
|
||||
}
|
||||
|
||||
/// The renderer of a [`Scrollable`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Scrollable`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: column::Renderer + Sized {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Returns the [`Scrollbar`] given the bounds and content bounds of a
|
||||
/// [`Scrollable`].
|
||||
fn scrollbar(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
offset: u32,
|
||||
scrollbar_width: u16,
|
||||
scrollbar_margin: u16,
|
||||
scroller_width: u16,
|
||||
) -> Option<Scrollbar>;
|
||||
|
||||
/// Draws the [`Scrollable`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the [`State`] of the [`Scrollable`]
|
||||
/// - the bounds of the [`Scrollable`] widget
|
||||
/// - the bounds of the [`Scrollable`] content
|
||||
/// - whether the mouse is over the [`Scrollable`] or not
|
||||
/// - whether the mouse is over the [`Scrollbar`] or not
|
||||
/// - a optional [`Scrollbar`] to be rendered
|
||||
/// - the scrolling offset
|
||||
/// - the drawn content
|
||||
fn draw(
|
||||
&mut self,
|
||||
scrollable: &State,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
is_mouse_over: bool,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
scrollbar: Option<Scrollbar>,
|
||||
offset: u32,
|
||||
style: &Self::Style,
|
||||
content: Self::Output,
|
||||
) -> Self::Output;
|
||||
bounds: Rectangle,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@
|
|||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
Background, Clipboard, Color, Element, Hasher, Layout, Length, Point,
|
||||
Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
use std::{hash::Hash, ops::RangeInclusive};
|
||||
use std::hash::Hash;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
|
|
@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use iced_native::{slider, renderer::Null};
|
||||
/// # use iced_native::widget::slider::{self, Slider};
|
||||
/// #
|
||||
/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
|
||||
/// #[derive(Clone)]
|
||||
/// pub enum Message {
|
||||
/// SliderChanged(f32),
|
||||
|
|
@ -37,7 +41,7 @@ use std::{hash::Hash, ops::RangeInclusive};
|
|||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
|
||||
pub struct Slider<'a, T, Message> {
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<T>,
|
||||
step: T,
|
||||
|
|
@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
|
|||
on_release: Option<Message>,
|
||||
width: Length,
|
||||
height: u16,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
|
||||
impl<'a, T, Message> Slider<'a, T, Message>
|
||||
where
|
||||
T: Copy + From<u8> + std::cmp::PartialOrd,
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer,
|
||||
{
|
||||
/// The default height of a [`Slider`].
|
||||
pub const DEFAULT_HEIGHT: u16 = 22;
|
||||
|
||||
/// Creates a new [`Slider`].
|
||||
///
|
||||
/// It expects:
|
||||
|
|
@ -93,8 +99,8 @@ where
|
|||
on_change: Box::new(on_change),
|
||||
on_release: None,
|
||||
width: Length::Fill,
|
||||
height: Renderer::DEFAULT_HEIGHT,
|
||||
style: Renderer::Style::default(),
|
||||
height: Self::DEFAULT_HEIGHT,
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +128,11 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`Slider`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -148,11 +157,11 @@ impl State {
|
|||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
||||
for Slider<'a, T, Message, Renderer>
|
||||
for Slider<'a, T, Message>
|
||||
where
|
||||
T: Copy + Into<f64> + num_traits::FromPrimitive,
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -246,22 +255,113 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
let start = *self.range.start();
|
||||
let end = *self.range.end();
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
renderer.draw(
|
||||
layout.bounds(),
|
||||
cursor_position,
|
||||
start.into() as f32..=end.into() as f32,
|
||||
self.value.into() as f32,
|
||||
self.state.is_dragging,
|
||||
&self.style,
|
||||
)
|
||||
let style = if self.state.is_dragging {
|
||||
self.style_sheet.dragging()
|
||||
} else if is_mouse_over {
|
||||
self.style_sheet.hovered()
|
||||
} else {
|
||||
self.style_sheet.active()
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail_colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y + 2.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(style.rail_colors.1),
|
||||
);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) = match style
|
||||
.handle
|
||||
.shape
|
||||
{
|
||||
HandleShape::Circle { radius } => {
|
||||
(radius * 2.0, radius * 2.0, radius)
|
||||
}
|
||||
HandleShape::Rectangle {
|
||||
width,
|
||||
border_radius,
|
||||
} => (f32::from(width), f32::from(bounds.height), border_radius),
|
||||
};
|
||||
|
||||
let value = self.value.into() as f32;
|
||||
let (range_start, range_end) = {
|
||||
let (start, end) = self.range.clone().into_inner();
|
||||
|
||||
(start.into() as f32, end.into() as f32)
|
||||
};
|
||||
|
||||
let handle_offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.width - handle_width) * (value - range_start)
|
||||
/ (range_end - range_start)
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
y: rail_y - handle_height / 2.0,
|
||||
width: handle_width,
|
||||
height: handle_height,
|
||||
},
|
||||
border_radius: handle_border_radius,
|
||||
border_width: style.handle.border_width,
|
||||
border_color: style.handle.border_color,
|
||||
},
|
||||
style.handle.color,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
if self.state.is_dragging {
|
||||
mouse::Interaction::Grabbing
|
||||
} else if is_mouse_over {
|
||||
mouse::Interaction::Grab
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -272,48 +372,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Slider`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`Slider`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// The default height of a [`Slider`].
|
||||
const DEFAULT_HEIGHT: u16;
|
||||
|
||||
/// Draws a [`Slider`].
|
||||
///
|
||||
/// It receives:
|
||||
/// * the current cursor position
|
||||
/// * the bounds of the [`Slider`]
|
||||
/// * the local state of the [`Slider`]
|
||||
/// * the range of values of the [`Slider`]
|
||||
/// * the current value of the [`Slider`]
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
is_dragging: bool,
|
||||
style: &Self::Style,
|
||||
) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
|
||||
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
{
|
||||
fn from(
|
||||
slider: Slider<'a, T, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(slider)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Distribute content vertically.
|
||||
use std::hash::Hash;
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
use crate::{
|
||||
layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// An amount of empty space.
|
||||
///
|
||||
|
|
@ -39,7 +39,7 @@ impl Space {
|
|||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Space
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -61,13 +61,12 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(layout.bounds())
|
||||
) {
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -78,17 +77,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of an amount of [`Space`].
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Draws an amount of empty [`Space`].
|
||||
///
|
||||
/// You should most likely return an empty primitive here.
|
||||
fn draw(&mut self, bounds: Rectangle) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: crate::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(space: Space) -> Element<'a, Message, Renderer> {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
//! Display vector graphics in your application.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::svg::{self, Handle};
|
||||
use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
use std::{
|
||||
hash::{Hash, Hasher as _},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A vector graphics image.
|
||||
///
|
||||
|
|
@ -52,7 +51,7 @@ impl Svg {
|
|||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Svg
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: svg::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -90,12 +89,12 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(self.handle.clone(), layout)
|
||||
) {
|
||||
renderer.draw(self.handle.clone(), layout.bounds())
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -107,94 +106,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// An [`Svg`] handle.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handle {
|
||||
id: u64,
|
||||
data: Arc<Data>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Creates an SVG [`Handle`] pointing to the vector image of the given
|
||||
/// path.
|
||||
pub fn from_path(path: impl Into<PathBuf>) -> Handle {
|
||||
Self::from_data(Data::Path(path.into()))
|
||||
}
|
||||
|
||||
/// Creates an SVG [`Handle`] from raw bytes containing either an SVG string
|
||||
/// or gzip compressed data.
|
||||
///
|
||||
/// This is useful if you already have your SVG data in-memory, maybe
|
||||
/// because you downloaded or generated it procedurally.
|
||||
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
|
||||
Self::from_data(Data::Bytes(bytes.into()))
|
||||
}
|
||||
|
||||
fn from_data(data: Data) -> Handle {
|
||||
let mut hasher = Hasher::default();
|
||||
data.hash(&mut hasher);
|
||||
|
||||
Handle {
|
||||
id: hasher.finish(),
|
||||
data: Arc::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unique identifier of the [`Handle`].
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns a reference to the SVG [`Data`].
|
||||
pub fn data(&self) -> &Data {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Handle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of an [`Svg`].
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Data {
|
||||
/// File data
|
||||
Path(PathBuf),
|
||||
|
||||
/// In-memory data
|
||||
///
|
||||
/// Can contain an SVG string or a gzip compressed data.
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Data {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The renderer of an [`Svg`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being able to use
|
||||
/// an [`Svg`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
|
||||
fn dimensions(&self, handle: &Handle) -> (u32, u32);
|
||||
|
||||
/// Draws an [`Svg`].
|
||||
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: svg::Renderer,
|
||||
{
|
||||
fn from(icon: Svg) -> Element<'a, Message, Renderer> {
|
||||
Element::new(icon)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Write some text for your users to read.
|
||||
use crate::alignment;
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::{
|
||||
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
||||
};
|
||||
|
||||
pub use iced_core::text::Hit;
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A paragraph of text.
|
||||
|
|
@ -14,7 +14,7 @@ use std::hash::Hash;
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # type Text = iced_native::Text<iced_native::renderer::Null>;
|
||||
/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// Text::new("I <3 iced!")
|
||||
/// .color([0.0, 0.0, 1.0])
|
||||
|
|
@ -23,7 +23,7 @@ use std::hash::Hash;
|
|||
///
|
||||
/// 
|
||||
#[derive(Debug)]
|
||||
pub struct Text<Renderer: self::Renderer> {
|
||||
pub struct Text<Renderer: text::Renderer> {
|
||||
content: String,
|
||||
size: Option<u16>,
|
||||
color: Option<Color>,
|
||||
|
|
@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {
|
|||
vertical_alignment: alignment::Vertical,
|
||||
}
|
||||
|
||||
impl<Renderer: self::Renderer> Text<Renderer> {
|
||||
impl<Renderer: text::Renderer> Text<Renderer> {
|
||||
/// Create a new fragment of [`Text`] with the given contents.
|
||||
pub fn new<T: Into<String>>(label: T) -> Self {
|
||||
Text {
|
||||
|
|
@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {
|
|||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -134,21 +134,22 @@ where
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
defaults: &Renderer::Defaults,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(
|
||||
defaults,
|
||||
layout.bounds(),
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
&self.content,
|
||||
self.size.unwrap_or(renderer.default_size()),
|
||||
self.font,
|
||||
self.size,
|
||||
self.color,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -162,79 +163,65 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`Text`] fragment.
|
||||
/// Draws text using the same logic as the [`Text`] widget.
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use [`Text`] in your user interface.
|
||||
/// Specifically:
|
||||
///
|
||||
/// [renderer]: crate::Renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The font type used for [`Text`].
|
||||
type Font: Default + Copy;
|
||||
/// * If no `size` is provided, the default text size of the `Renderer` will be
|
||||
/// used.
|
||||
/// * If no `color` is provided, the [`renderer::Style::text_color`] will be
|
||||
/// used.
|
||||
/// * The alignment attributes do not affect the position of the bounds of the
|
||||
/// [`Layout`].
|
||||
pub fn draw<Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
content: &str,
|
||||
font: Renderer::Font,
|
||||
size: Option<u16>,
|
||||
color: Option<Color>,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let bounds = layout.bounds();
|
||||
|
||||
/// Returns the default size of [`Text`].
|
||||
fn default_size(&self) -> u16;
|
||||
let x = match horizontal_alignment {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => bounds.center_x(),
|
||||
alignment::Horizontal::Right => bounds.x + bounds.width,
|
||||
};
|
||||
|
||||
/// Measures the [`Text`] in the given bounds and returns the minimum
|
||||
/// boundaries that can fit the contents.
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Self::Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32);
|
||||
let y = match vertical_alignment {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => bounds.center_y(),
|
||||
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
||||
};
|
||||
|
||||
/// Tests whether the provided point is within the boundaries of [`Text`]
|
||||
/// laid out with the given parameters, returning information about
|
||||
/// the nearest character.
|
||||
///
|
||||
/// If `nearest_only` is true, the hit test does not consider whether the
|
||||
/// the point is interior to any glyph bounds, returning only the character
|
||||
/// with the nearest centeroid.
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
font: Self::Font,
|
||||
bounds: Size,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<Hit>;
|
||||
|
||||
/// Draws a [`Text`] fragment.
|
||||
///
|
||||
/// It receives:
|
||||
/// * the bounds of the [`Text`]
|
||||
/// * the contents of the [`Text`]
|
||||
/// * the size of the [`Text`]
|
||||
/// * the color of the [`Text`]
|
||||
/// * the [`HorizontalAlignment`] of the [`Text`]
|
||||
/// * the [`VerticalAlignment`] of the [`Text`]
|
||||
fn draw(
|
||||
&mut self,
|
||||
defaults: &Self::Defaults,
|
||||
bounds: Rectangle,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Self::Font,
|
||||
color: Option<Color>,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
) -> Self::Output;
|
||||
renderer.fill_text(crate::text::Text {
|
||||
content,
|
||||
size: f32::from(size.unwrap_or(renderer.default_size())),
|
||||
bounds: Rectangle { x, y, ..bounds },
|
||||
color: color.unwrap_or(style.text_color),
|
||||
font,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
});
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Text<Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
{
|
||||
fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Renderer: self::Renderer> Clone for Text<Renderer> {
|
||||
impl<Renderer: text::Renderer> Clone for Text<Renderer> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
content: self.content.clone(),
|
||||
|
|
|
|||
|
|
@ -11,26 +11,31 @@ pub use value::Value;
|
|||
|
||||
use editor::Editor;
|
||||
|
||||
use crate::alignment;
|
||||
use crate::event::{self, Event};
|
||||
use crate::keyboard;
|
||||
use crate::layout;
|
||||
use crate::mouse::{self, click};
|
||||
use crate::text;
|
||||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::touch;
|
||||
use crate::{
|
||||
Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
|
||||
Size, Widget,
|
||||
Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point,
|
||||
Rectangle, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::u32;
|
||||
|
||||
pub use iced_style::text_input::{Style, StyleSheet};
|
||||
|
||||
/// A field that can be filled with text.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use iced_native::{text_input, renderer::Null};
|
||||
/// # use iced_native::renderer::Null;
|
||||
/// # use iced_native::widget::text_input;
|
||||
/// #
|
||||
/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>;
|
||||
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
|
||||
/// #[derive(Debug, Clone)]
|
||||
/// enum Message {
|
||||
/// TextInputChanged(String),
|
||||
|
|
@ -49,7 +54,7 @@ use std::u32;
|
|||
/// ```
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct TextInput<'a, Message, Renderer: self::Renderer> {
|
||||
pub struct TextInput<'a, Message, Renderer: text::Renderer> {
|
||||
state: &'a mut State,
|
||||
placeholder: String,
|
||||
value: Value,
|
||||
|
|
@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
|
|||
size: Option<u16>,
|
||||
on_change: Box<dyn Fn(String) -> Message>,
|
||||
on_submit: Option<Message>,
|
||||
style: Renderer::Style,
|
||||
style_sheet: Box<dyn StyleSheet + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
/// Creates a new [`TextInput`].
|
||||
///
|
||||
|
|
@ -97,7 +102,7 @@ where
|
|||
size: None,
|
||||
on_change: Box::new(on_change),
|
||||
on_submit: None,
|
||||
style: Renderer::Style::default(),
|
||||
style_sheet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,8 +152,11 @@ where
|
|||
}
|
||||
|
||||
/// Sets the style of the [`TextInput`].
|
||||
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
pub fn style(
|
||||
mut self,
|
||||
style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
|
||||
) -> Self {
|
||||
self.style_sheet = style_sheet.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +168,7 @@ where
|
|||
|
||||
impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: self::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||
/// [`Value`] if provided.
|
||||
|
|
@ -170,37 +178,165 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
value: Option<&Value>,
|
||||
) -> Renderer::Output {
|
||||
) {
|
||||
let value = value.unwrap_or(&self.value);
|
||||
let secure_value = self.is_secure.then(|| value.secure());
|
||||
let value = secure_value.as_ref().unwrap_or(&value);
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let text_bounds = layout.children().next().unwrap().bounds();
|
||||
|
||||
if self.is_secure {
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
bounds,
|
||||
text_bounds,
|
||||
cursor_position,
|
||||
self.font,
|
||||
self.size.unwrap_or(renderer.default_size()),
|
||||
&self.placeholder,
|
||||
&value.secure(),
|
||||
&self.state,
|
||||
&self.style,
|
||||
)
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let style = if self.state.is_focused() {
|
||||
self.style_sheet.focused()
|
||||
} else if is_mouse_over {
|
||||
self.style_sheet.hovered()
|
||||
} else {
|
||||
self::Renderer::draw(
|
||||
renderer,
|
||||
self.style_sheet.active()
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
text_bounds,
|
||||
cursor_position,
|
||||
self.font,
|
||||
self.size.unwrap_or(renderer.default_size()),
|
||||
&self.placeholder,
|
||||
value,
|
||||
&self.state,
|
||||
&self.style,
|
||||
)
|
||||
border_radius: style.border_radius,
|
||||
border_width: style.border_width,
|
||||
border_color: style.border_color,
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
let text = value.to_string();
|
||||
let size = self.size.unwrap_or(renderer.default_size());
|
||||
|
||||
let (cursor, offset) = if self.state.is_focused() {
|
||||
match self.state.cursor.state(&value) {
|
||||
cursor::State::Index(position) => {
|
||||
let (text_value_width, offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
text_bounds,
|
||||
&value,
|
||||
size,
|
||||
position,
|
||||
self.font,
|
||||
);
|
||||
|
||||
(
|
||||
Some((
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + text_value_width,
|
||||
y: text_bounds.y,
|
||||
width: 1.0,
|
||||
height: text_bounds.height,
|
||||
},
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
self.style_sheet.value_color(),
|
||||
)),
|
||||
offset,
|
||||
)
|
||||
}
|
||||
cursor::State::Selection { start, end } => {
|
||||
let left = start.min(end);
|
||||
let right = end.max(start);
|
||||
|
||||
let (left_position, left_offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
text_bounds,
|
||||
&value,
|
||||
size,
|
||||
left,
|
||||
self.font,
|
||||
);
|
||||
|
||||
let (right_position, right_offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
text_bounds,
|
||||
&value,
|
||||
size,
|
||||
right,
|
||||
self.font,
|
||||
);
|
||||
|
||||
let width = right_position - left_position;
|
||||
|
||||
(
|
||||
Some((
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + left_position,
|
||||
y: text_bounds.y,
|
||||
width,
|
||||
height: text_bounds.height,
|
||||
},
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
self.style_sheet.selection_color(),
|
||||
)),
|
||||
if end == right {
|
||||
right_offset
|
||||
} else {
|
||||
left_offset
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(None, 0.0)
|
||||
};
|
||||
|
||||
let text_width = renderer.measure_width(
|
||||
if text.is_empty() {
|
||||
&self.placeholder
|
||||
} else {
|
||||
&text
|
||||
},
|
||||
size,
|
||||
self.font,
|
||||
);
|
||||
|
||||
let render = |renderer: &mut Renderer| {
|
||||
if let Some((cursor, color)) = cursor {
|
||||
renderer.fill_quad(cursor, color);
|
||||
}
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: if text.is_empty() {
|
||||
&self.placeholder
|
||||
} else {
|
||||
&text
|
||||
},
|
||||
color: if text.is_empty() {
|
||||
self.style_sheet.placeholder_color()
|
||||
} else {
|
||||
self.style_sheet.value_color()
|
||||
},
|
||||
font: self.font,
|
||||
bounds: Rectangle {
|
||||
y: text_bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
..text_bounds
|
||||
},
|
||||
size: f32::from(size),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
};
|
||||
|
||||
if text_width > text_bounds.width {
|
||||
renderer.with_layer(text_bounds, |renderer| {
|
||||
renderer.with_translation(Vector::new(-offset, 0.0), render)
|
||||
});
|
||||
} else {
|
||||
render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -209,7 +345,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
|||
for TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: self::Renderer,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
|
|
@ -275,7 +411,8 @@ where
|
|||
self.value.clone()
|
||||
};
|
||||
|
||||
renderer.find_cursor_position(
|
||||
find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
self.font,
|
||||
self.size,
|
||||
|
|
@ -294,16 +431,16 @@ where
|
|||
if self.is_secure {
|
||||
self.state.cursor.select_all(&self.value);
|
||||
} else {
|
||||
let position = renderer
|
||||
.find_cursor_position(
|
||||
text_layout.bounds(),
|
||||
self.font,
|
||||
self.size,
|
||||
&self.value,
|
||||
&self.state,
|
||||
target,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
let position = find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
self.font,
|
||||
self.size,
|
||||
&self.value,
|
||||
&self.state,
|
||||
target,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.state.cursor.select_range(
|
||||
self.value.previous_start_of_word(position),
|
||||
|
|
@ -341,16 +478,16 @@ where
|
|||
self.value.clone()
|
||||
};
|
||||
|
||||
let position = renderer
|
||||
.find_cursor_position(
|
||||
text_layout.bounds(),
|
||||
self.font,
|
||||
self.size,
|
||||
&value,
|
||||
&self.state,
|
||||
target,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
let position = find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
self.font,
|
||||
self.size,
|
||||
&value,
|
||||
&self.state,
|
||||
target,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.state.cursor.select_range(
|
||||
self.state.cursor.start(&value),
|
||||
|
|
@ -621,14 +758,27 @@ where
|
|||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_defaults: &Renderer::Defaults,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) -> Renderer::Output {
|
||||
) -> mouse::Interaction {
|
||||
if layout.bounds().contains(cursor_position) {
|
||||
mouse::Interaction::Text
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
self.draw(renderer, layout, cursor_position, None)
|
||||
}
|
||||
|
||||
|
|
@ -644,87 +794,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The renderer of a [`TextInput`].
|
||||
///
|
||||
/// Your [renderer] will need to implement this trait before being
|
||||
/// able to use a [`TextInput`] in your user interface.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: text::Renderer + Sized {
|
||||
/// The style supported by this renderer.
|
||||
type Style: Default;
|
||||
|
||||
/// Returns the width of the value of the [`TextInput`].
|
||||
fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
|
||||
|
||||
/// Returns the current horizontal offset of the value of the
|
||||
/// [`TextInput`].
|
||||
///
|
||||
/// This is the amount of horizontal scrolling applied when the [`Value`]
|
||||
/// does not fit the [`TextInput`].
|
||||
fn offset(
|
||||
&self,
|
||||
text_bounds: Rectangle,
|
||||
font: Self::Font,
|
||||
size: u16,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
) -> f32;
|
||||
|
||||
/// Draws a [`TextInput`].
|
||||
///
|
||||
/// It receives:
|
||||
/// - the bounds of the [`TextInput`]
|
||||
/// - the bounds of the text (i.e. the current value)
|
||||
/// - the cursor position
|
||||
/// - the placeholder to show when the value is empty
|
||||
/// - the current [`Value`]
|
||||
/// - the current [`State`]
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
text_bounds: Rectangle,
|
||||
cursor_position: Point,
|
||||
font: Self::Font,
|
||||
size: u16,
|
||||
placeholder: &str,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
style: &Self::Style,
|
||||
) -> Self::Output;
|
||||
|
||||
/// Computes the position of the text cursor at the given X coordinate of
|
||||
/// a [`TextInput`].
|
||||
fn find_cursor_position(
|
||||
&self,
|
||||
text_bounds: Rectangle,
|
||||
font: Self::Font,
|
||||
size: Option<u16>,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
x: f32,
|
||||
) -> Option<usize> {
|
||||
let size = size.unwrap_or(self.default_size());
|
||||
|
||||
let offset = self.offset(text_bounds, font, size, &value, &state);
|
||||
|
||||
self.hit_test(
|
||||
&value.to_string(),
|
||||
size.into(),
|
||||
font,
|
||||
Size::INFINITY,
|
||||
Point::new(x + offset, text_bounds.height / 2.0),
|
||||
true,
|
||||
)
|
||||
.map(text::Hit::cursor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + self::Renderer,
|
||||
Renderer: 'a + text::Renderer,
|
||||
{
|
||||
fn from(
|
||||
text_input: TextInput<'a, Message, Renderer>,
|
||||
|
|
@ -815,3 +889,88 @@ mod platform {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn offset<Renderer>(
|
||||
renderer: &Renderer,
|
||||
text_bounds: Rectangle,
|
||||
font: Renderer::Font,
|
||||
size: u16,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
) -> f32
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
if state.is_focused() {
|
||||
let cursor = state.cursor();
|
||||
|
||||
let focus_position = match cursor.state(value) {
|
||||
cursor::State::Index(i) => i,
|
||||
cursor::State::Selection { end, .. } => end,
|
||||
};
|
||||
|
||||
let (_, offset) = measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
focus_position,
|
||||
font,
|
||||
);
|
||||
|
||||
offset
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_cursor_and_scroll_offset<Renderer>(
|
||||
renderer: &Renderer,
|
||||
text_bounds: Rectangle,
|
||||
value: &Value,
|
||||
size: u16,
|
||||
cursor_index: usize,
|
||||
font: Renderer::Font,
|
||||
) -> (f32, f32)
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let text_before_cursor = value.until(cursor_index).to_string();
|
||||
|
||||
let text_value_width =
|
||||
renderer.measure_width(&text_before_cursor, size, font);
|
||||
|
||||
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
|
||||
|
||||
(text_value_width, offset)
|
||||
}
|
||||
|
||||
/// Computes the position of the text cursor at the given X coordinate of
|
||||
/// a [`TextInput`].
|
||||
fn find_cursor_position<Renderer>(
|
||||
renderer: &Renderer,
|
||||
text_bounds: Rectangle,
|
||||
font: Renderer::Font,
|
||||
size: Option<u16>,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
x: f32,
|
||||
) -> Option<usize>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let size = size.unwrap_or(renderer.default_size());
|
||||
|
||||
let offset = offset(renderer, text_bounds, font, size, &value, &state);
|
||||
|
||||
renderer
|
||||
.hit_test(
|
||||
&value.to_string(),
|
||||
size.into(),
|
||||
font,
|
||||
Size::INFINITY,
|
||||
Point::new(x + offset, text_bounds.height / 2.0),
|
||||
true,
|
||||
)
|
||||
.map(text::Hit::cursor)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue