Rename iced_virtual to iced_pure
`virtual` is a reserved keyword in Rust 😬
This commit is contained in:
parent
e03de01988
commit
897188317b
15 changed files with 16 additions and 15 deletions
1
pure/src/application.rs
Normal file
1
pure/src/application.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
232
pure/src/flex.rs
Normal file
232
pure/src/flex.rs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
//! Distribute elements using a flex-based layout.
|
||||
// This code is heavily inspired by the [`druid`] codebase.
|
||||
//
|
||||
// [`druid`]: https://github.com/xi-editor/druid
|
||||
//
|
||||
// Copyright 2018 The xi-editor Authors, Héctor Ramón
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use crate::Element;
|
||||
|
||||
use iced_native::layout::{Limits, Node};
|
||||
use iced_native::{Alignment, Padding, Point, Size};
|
||||
|
||||
/// The main axis of a flex layout.
|
||||
#[derive(Debug)]
|
||||
pub enum Axis {
|
||||
/// The horizontal axis
|
||||
Horizontal,
|
||||
|
||||
/// The vertical axis
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
fn main(&self, size: Size) -> f32 {
|
||||
match self {
|
||||
Axis::Horizontal => size.width,
|
||||
Axis::Vertical => size.height,
|
||||
}
|
||||
}
|
||||
|
||||
fn cross(&self, size: Size) -> f32 {
|
||||
match self {
|
||||
Axis::Horizontal => size.height,
|
||||
Axis::Vertical => size.width,
|
||||
}
|
||||
}
|
||||
|
||||
fn pack(&self, main: f32, cross: f32) -> (f32, f32) {
|
||||
match self {
|
||||
Axis::Horizontal => (main, cross),
|
||||
Axis::Vertical => (cross, main),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the flex layout with the given axis and limits, applying spacing,
|
||||
/// padding and alignment to the items as needed.
|
||||
///
|
||||
/// It returns a new layout [`Node`].
|
||||
pub fn resolve<Message, Renderer>(
|
||||
axis: Axis,
|
||||
renderer: &Renderer,
|
||||
limits: &Limits,
|
||||
padding: Padding,
|
||||
spacing: f32,
|
||||
align_items: Alignment,
|
||||
items: &[Element<Message, Renderer>],
|
||||
) -> Node
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
{
|
||||
let limits = limits.pad(padding);
|
||||
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
|
||||
let max_cross = axis.cross(limits.max());
|
||||
|
||||
let mut fill_sum = 0;
|
||||
let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill()));
|
||||
let mut available = axis.main(limits.max()) - total_spacing;
|
||||
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
nodes.resize(items.len(), Node::default());
|
||||
|
||||
if align_items == Alignment::Fill {
|
||||
let mut fill_cross = axis.cross(limits.min());
|
||||
|
||||
items.iter().for_each(|child| {
|
||||
let cross_fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().height(),
|
||||
Axis::Vertical => child.as_widget().width(),
|
||||
}
|
||||
.fill_factor();
|
||||
|
||||
if cross_fill_factor == 0 {
|
||||
let (max_width, max_height) = axis.pack(available, max_cross);
|
||||
|
||||
let child_limits =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
fill_cross = fill_cross.max(axis.cross(size));
|
||||
}
|
||||
});
|
||||
|
||||
cross = fill_cross;
|
||||
}
|
||||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
}
|
||||
.fill_factor();
|
||||
|
||||
if fill_factor == 0 {
|
||||
let (min_width, min_height) = if align_items == Alignment::Fill {
|
||||
axis.pack(0.0, cross)
|
||||
} else {
|
||||
axis.pack(0.0, 0.0)
|
||||
};
|
||||
|
||||
let (max_width, max_height) = if align_items == Alignment::Fill {
|
||||
axis.pack(available, cross)
|
||||
} else {
|
||||
axis.pack(available, max_cross)
|
||||
};
|
||||
|
||||
let child_limits = Limits::new(
|
||||
Size::new(min_width, min_height),
|
||||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
|
||||
if align_items != Alignment::Fill {
|
||||
cross = cross.max(axis.cross(size));
|
||||
}
|
||||
|
||||
nodes[i] = layout;
|
||||
} else {
|
||||
fill_sum += fill_factor;
|
||||
}
|
||||
}
|
||||
|
||||
let remaining = available.max(0.0);
|
||||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
}
|
||||
.fill_factor();
|
||||
|
||||
if fill_factor != 0 {
|
||||
let max_main = remaining * fill_factor as f32 / fill_sum as f32;
|
||||
let min_main = if max_main.is_infinite() {
|
||||
0.0
|
||||
} else {
|
||||
max_main
|
||||
};
|
||||
|
||||
let (min_width, min_height) = if align_items == Alignment::Fill {
|
||||
axis.pack(min_main, cross)
|
||||
} else {
|
||||
axis.pack(min_main, axis.cross(limits.min()))
|
||||
};
|
||||
|
||||
let (max_width, max_height) = if align_items == Alignment::Fill {
|
||||
axis.pack(max_main, cross)
|
||||
} else {
|
||||
axis.pack(max_main, max_cross)
|
||||
};
|
||||
|
||||
let child_limits = Limits::new(
|
||||
Size::new(min_width, min_height),
|
||||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
|
||||
if align_items != Alignment::Fill {
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
}
|
||||
|
||||
nodes[i] = layout;
|
||||
}
|
||||
}
|
||||
|
||||
let pad = axis.pack(padding.left as f32, padding.top as f32);
|
||||
let mut main = pad.0;
|
||||
|
||||
for (i, node) in nodes.iter_mut().enumerate() {
|
||||
if i > 0 {
|
||||
main += spacing;
|
||||
}
|
||||
|
||||
let (x, y) = axis.pack(main, pad.1);
|
||||
|
||||
node.move_to(Point::new(x, y));
|
||||
|
||||
match axis {
|
||||
Axis::Horizontal => {
|
||||
node.align(
|
||||
Alignment::Start,
|
||||
align_items,
|
||||
Size::new(0.0, cross),
|
||||
);
|
||||
}
|
||||
Axis::Vertical => {
|
||||
node.align(
|
||||
align_items,
|
||||
Alignment::Start,
|
||||
Size::new(cross, 0.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let size = node.size();
|
||||
|
||||
main += axis.main(size);
|
||||
}
|
||||
|
||||
let (width, height) = axis.pack(main - pad.0, cross);
|
||||
let size = limits.resolve(Size::new(width, height));
|
||||
|
||||
Node::with_children(size.pad(padding), nodes)
|
||||
}
|
||||
146
pure/src/lib.rs
Normal file
146
pure/src/lib.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
pub mod widget;
|
||||
|
||||
pub(crate) mod flex;
|
||||
|
||||
pub use widget::*;
|
||||
|
||||
use iced_native::event::{self, Event};
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::mouse;
|
||||
use iced_native::renderer;
|
||||
use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell};
|
||||
|
||||
pub struct Pure<'a, Message, Renderer> {
|
||||
state: &'a mut State<Message, Renderer>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Pure<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'static,
|
||||
Renderer: iced_native::Renderer + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
state: &'a mut State<Message, Renderer>,
|
||||
content: impl Into<Element<Message, Renderer>>,
|
||||
) -> Self {
|
||||
let _ = state.diff(content.into());
|
||||
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State<Message, Renderer> {
|
||||
state_tree: widget::Tree<Message, Renderer>,
|
||||
last_element: Element<Message, Renderer>,
|
||||
}
|
||||
|
||||
impl<Message, Renderer> State<Message, Renderer>
|
||||
where
|
||||
Message: 'static,
|
||||
Renderer: iced_native::Renderer + 'static,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
let last_element = Element::new(widget::Column::new());
|
||||
|
||||
Self {
|
||||
state_tree: widget::Tree::new(&last_element),
|
||||
last_element,
|
||||
}
|
||||
}
|
||||
|
||||
fn diff(&mut self, new_element: Element<Message, Renderer>) {
|
||||
self.state_tree.diff(&self.last_element, &new_element);
|
||||
|
||||
self.last_element = new_element;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> iced_native::Widget<Message, Renderer>
|
||||
for Pure<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.state.last_element.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.state.last_element.as_widget().height()
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
self.state.last_element.as_widget().hash_layout(state)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.state.last_element.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.state.last_element.as_widget_mut().on_event(
|
||||
&mut self.state.state_tree,
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.state.last_element.as_widget().draw(
|
||||
&self.state.state_tree,
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.state.last_element.as_widget().mouse_interaction(
|
||||
&self.state.state_tree,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Into<iced_native::Element<'a, Message, Renderer>>
|
||||
for Pure<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
{
|
||||
fn into(self) -> iced_native::Element<'a, Message, Renderer> {
|
||||
iced_native::Element::new(self)
|
||||
}
|
||||
}
|
||||
73
pure/src/widget.rs
Normal file
73
pure/src/widget.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
mod button;
|
||||
mod column;
|
||||
mod element;
|
||||
mod text;
|
||||
mod tree;
|
||||
|
||||
pub use button::Button;
|
||||
pub use column::Column;
|
||||
pub use element::Element;
|
||||
pub use text::Text;
|
||||
pub use tree::Tree;
|
||||
|
||||
use iced_native::event::{self, Event};
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::mouse;
|
||||
use iced_native::renderer;
|
||||
use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell};
|
||||
|
||||
use std::any::{self, Any};
|
||||
|
||||
pub trait Widget<Message, Renderer> {
|
||||
fn tag(&self) -> any::TypeId;
|
||||
|
||||
fn state(&self) -> Box<dyn Any>;
|
||||
|
||||
fn children(&self) -> &[Element<Message, Renderer>];
|
||||
|
||||
fn width(&self) -> Length;
|
||||
|
||||
fn height(&self) -> Length;
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher);
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Tree<Message, Renderer>,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
);
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree<Message, Renderer>,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse::Interaction::Idle
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut Tree<Message, Renderer>,
|
||||
_event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
_shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
event::Status::Ignored
|
||||
}
|
||||
}
|
||||
272
pure/src/widget/button.rs
Normal file
272
pure/src/widget/button.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
use crate::widget::{Element, Tree, Widget};
|
||||
|
||||
use iced_native::event::{self, Event};
|
||||
use iced_native::layout;
|
||||
use iced_native::mouse;
|
||||
use iced_native::renderer;
|
||||
use iced_native::touch;
|
||||
use iced_native::{
|
||||
Background, Clipboard, Color, Hasher, Layout, Length, Padding, Point,
|
||||
Rectangle, Shell, Vector,
|
||||
};
|
||||
use iced_style::button::StyleSheet;
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
pub struct Button<Message, Renderer> {
|
||||
content: Element<Message, Renderer>,
|
||||
on_press: Option<Message>,
|
||||
style_sheet: Box<dyn StyleSheet>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
padding: Padding,
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Button<Message, Renderer> {
|
||||
pub fn new(content: impl Into<Element<Message, Renderer>>) -> Self {
|
||||
Button {
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
style_sheet: Default::default(),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
padding: Padding::new(5),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_press(mut self, on_press: Message) -> Self {
|
||||
self.on_press = Some(on_press);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Button<Message, Renderer>
|
||||
where
|
||||
Message: 'static + Clone,
|
||||
Renderer: 'static + iced_native::Renderer,
|
||||
{
|
||||
fn tag(&self) -> std::any::TypeId {
|
||||
std::any::TypeId::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> Box<dyn Any> {
|
||||
Box::new(State { is_pressed: false })
|
||||
}
|
||||
|
||||
fn children(&self) -> &[Element<Message, Renderer>] {
|
||||
std::slice::from_ref(&self.content)
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
self.tag().hash(state);
|
||||
self.width.hash(state);
|
||||
self.content.as_widget().hash_layout(state);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits
|
||||
.width(self.width)
|
||||
.height(self.height)
|
||||
.pad(self.padding);
|
||||
|
||||
let mut content = self.content.as_widget().layout(renderer, &limits);
|
||||
content.move_to(Point::new(
|
||||
self.padding.left.into(),
|
||||
self.padding.top.into(),
|
||||
));
|
||||
|
||||
let size = limits.resolve(content.size()).pad(self.padding);
|
||||
|
||||
layout::Node::with_children(size, vec![content])
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree<Message, Renderer>,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let state = if let Some(state) = tree.state.downcast_mut::<State>() {
|
||||
state
|
||||
} else {
|
||||
return event::Status::Ignored;
|
||||
};
|
||||
|
||||
if let event::Status::Captured = self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
) {
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
if self.on_press.is_some() {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
state.is_pressed = true;
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. }) => {
|
||||
if let Some(on_press) = self.on_press.clone() {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
if state.is_pressed {
|
||||
state.is_pressed = false;
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
shell.publish(on_press);
|
||||
}
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||
state.is_pressed = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree<Message, Renderer>,
|
||||
renderer: &mut Renderer,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = if let Some(state) = tree.state.downcast_ref::<State>() {
|
||||
state
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
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 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.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
&renderer::Style {
|
||||
text_color: styling.text_color,
|
||||
},
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&bounds,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree<Message, Renderer>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct State {
|
||||
is_pressed: bool,
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Into<Element<Message, Renderer>>
|
||||
for Button<Message, Renderer>
|
||||
where
|
||||
Message: Clone + 'static,
|
||||
Renderer: iced_native::Renderer + 'static,
|
||||
{
|
||||
fn into(self) -> Element<Message, Renderer> {
|
||||
Element::new(self)
|
||||
}
|
||||
}
|
||||
220
pure/src/widget/column.rs
Normal file
220
pure/src/widget/column.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
use crate::flex;
|
||||
use crate::widget::{Element, Tree, Widget};
|
||||
|
||||
use iced_native::event::{self, Event};
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::mouse;
|
||||
use iced_native::renderer;
|
||||
use iced_native::{
|
||||
Alignment, Clipboard, Hasher, Length, Padding, Point, Rectangle, Shell,
|
||||
};
|
||||
|
||||
use std::any::{self, Any};
|
||||
|
||||
pub struct Column<Message, Renderer> {
|
||||
spacing: u16,
|
||||
padding: Padding,
|
||||
width: Length,
|
||||
height: Length,
|
||||
align_items: Alignment,
|
||||
children: Vec<Element<Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Column<Message, Renderer> {
|
||||
pub fn new() -> Self {
|
||||
Self::with_children(Vec::new())
|
||||
}
|
||||
|
||||
pub fn with_children(children: Vec<Element<Message, Renderer>>) -> Self {
|
||||
Column {
|
||||
spacing: 0,
|
||||
padding: Padding::ZERO,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
align_items: Alignment::Start,
|
||||
children,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spacing(mut self, units: u16) -> Self {
|
||||
self.spacing = units;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn align_items(mut self, align: Alignment) -> Self {
|
||||
self.align_items = align;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push(
|
||||
mut self,
|
||||
child: impl Into<Element<Message, Renderer>>,
|
||||
) -> Self {
|
||||
self.children.push(child.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Column<Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
{
|
||||
fn tag(&self) -> any::TypeId {
|
||||
struct Marker;
|
||||
any::TypeId::of::<Marker>()
|
||||
}
|
||||
|
||||
fn state(&self) -> Box<dyn Any> {
|
||||
Box::new(())
|
||||
}
|
||||
|
||||
fn children(&self) -> &[Element<Message, Renderer>] {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
|
||||
flex::resolve(
|
||||
flex::Axis::Vertical,
|
||||
renderer,
|
||||
&limits,
|
||||
self.padding,
|
||||
self.spacing as f32,
|
||||
self.align_items,
|
||||
&self.children,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree<Message, Renderer>,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget_mut().on_event(
|
||||
state,
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree<Message, Renderer>,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget().mouse_interaction(
|
||||
state,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree<Message, Renderer>,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for ((child, state), layout) in self
|
||||
.children
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
child.as_widget().draw(
|
||||
state,
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
self.tag().hash(state);
|
||||
self.width.hash(state);
|
||||
self.height.hash(state);
|
||||
self.align_items.hash(state);
|
||||
self.spacing.hash(state);
|
||||
self.padding.hash(state);
|
||||
|
||||
for child in &self.children {
|
||||
child.as_widget().hash_layout(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Into<Element<Message, Renderer>>
|
||||
for Column<Message, Renderer>
|
||||
where
|
||||
Message: 'static,
|
||||
Renderer: iced_native::Renderer + 'static,
|
||||
{
|
||||
fn into(self) -> Element<Message, Renderer> {
|
||||
Element::new(self)
|
||||
}
|
||||
}
|
||||
21
pure/src/widget/element.rs
Normal file
21
pure/src/widget/element.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use crate::Widget;
|
||||
|
||||
pub struct Element<Message, Renderer> {
|
||||
widget: Box<dyn Widget<Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Element<Message, Renderer> {
|
||||
pub fn new(widget: impl Widget<Message, Renderer> + 'static) -> Self {
|
||||
Self {
|
||||
widget: Box::new(widget),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_widget(&self) -> &dyn Widget<Message, Renderer> {
|
||||
self.widget.as_ref()
|
||||
}
|
||||
|
||||
pub fn as_widget_mut(&mut self) -> &mut dyn Widget<Message, Renderer> {
|
||||
self.widget.as_mut()
|
||||
}
|
||||
}
|
||||
185
pure/src/widget/text.rs
Normal file
185
pure/src/widget/text.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
use crate::{Element, Tree, Widget};
|
||||
|
||||
use iced_native::alignment;
|
||||
use iced_native::layout::{self, Layout};
|
||||
use iced_native::renderer;
|
||||
use iced_native::text;
|
||||
use iced_native::{Color, Hasher, Length, Point, Rectangle, Size};
|
||||
|
||||
use std::any::{self, Any};
|
||||
|
||||
pub struct Text<Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
content: String,
|
||||
size: Option<u16>,
|
||||
color: Option<Color>,
|
||||
font: Renderer::Font,
|
||||
width: Length,
|
||||
height: Length,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
}
|
||||
|
||||
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 {
|
||||
content: label.into(),
|
||||
size: None,
|
||||
color: None,
|
||||
font: Default::default(),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the size of the [`Text`].
|
||||
pub fn size(mut self, size: u16) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Color`] of the [`Text`].
|
||||
pub fn color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Font`] of the [`Text`].
|
||||
///
|
||||
/// [`Font`]: Renderer::Font
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = font.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Text`] boundaries.
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Text`] boundaries.
|
||||
pub fn height(mut self, height: Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`HorizontalAlignment`] of the [`Text`].
|
||||
pub fn horizontal_alignment(
|
||||
mut self,
|
||||
alignment: alignment::Horizontal,
|
||||
) -> Self {
|
||||
self.horizontal_alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`VerticalAlignment`] of the [`Text`].
|
||||
pub fn vertical_alignment(
|
||||
mut self,
|
||||
alignment: alignment::Vertical,
|
||||
) -> Self {
|
||||
self.vertical_alignment = alignment;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn tag(&self) -> any::TypeId {
|
||||
any::TypeId::of::<()>()
|
||||
}
|
||||
|
||||
fn state(&self) -> Box<dyn Any> {
|
||||
Box::new(())
|
||||
}
|
||||
|
||||
fn children(&self) -> &[Element<Message, Renderer>] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
|
||||
let size = self.size.unwrap_or(renderer.default_size());
|
||||
|
||||
let bounds = limits.max();
|
||||
|
||||
let (width, height) =
|
||||
renderer.measure(&self.content, size, self.font.clone(), bounds);
|
||||
|
||||
let size = limits.resolve(Size::new(width, height));
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &Tree<Message, Renderer>,
|
||||
renderer: &mut Renderer,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
iced_native::widget::text::draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
&self.content,
|
||||
self.font.clone(),
|
||||
self.size,
|
||||
self.color,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
);
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
struct Marker;
|
||||
std::any::TypeId::of::<Marker>().hash(state);
|
||||
|
||||
self.content.hash(state);
|
||||
self.size.hash(state);
|
||||
self.width.hash(state);
|
||||
self.height.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Into<Element<Message, Renderer>> for Text<Renderer>
|
||||
where
|
||||
Renderer: text::Renderer + 'static,
|
||||
{
|
||||
fn into(self) -> Element<Message, Renderer> {
|
||||
Element::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Into<Element<Message, Renderer>> for &'static str
|
||||
where
|
||||
Renderer: text::Renderer + 'static,
|
||||
{
|
||||
fn into(self) -> Element<Message, Renderer> {
|
||||
Text::new(self).into()
|
||||
}
|
||||
}
|
||||
58
pure/src/widget/tree.rs
Normal file
58
pure/src/widget/tree.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::widget::Element;
|
||||
|
||||
use std::any::Any;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Tree<Message, Renderer> {
|
||||
pub state: Box<dyn Any>,
|
||||
pub children: Vec<Tree<Message, Renderer>>,
|
||||
types_: PhantomData<(Message, Renderer)>,
|
||||
}
|
||||
|
||||
impl<Message, Renderer> Tree<Message, Renderer> {
|
||||
pub fn new(element: &Element<Message, Renderer>) -> Self {
|
||||
Self {
|
||||
state: element.as_widget().state(),
|
||||
children: element
|
||||
.as_widget()
|
||||
.children()
|
||||
.iter()
|
||||
.map(Self::new)
|
||||
.collect(),
|
||||
types_: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diff(
|
||||
&mut self,
|
||||
current: &Element<Message, Renderer>,
|
||||
new: &Element<Message, Renderer>,
|
||||
) {
|
||||
if current.as_widget().tag() == new.as_widget().tag() {
|
||||
let current_children = current.as_widget().children();
|
||||
let new_children = new.as_widget().children();
|
||||
|
||||
if current_children.len() > new_children.len() {
|
||||
self.children.truncate(new_children.len());
|
||||
}
|
||||
|
||||
for (child_state, (current, new)) in self
|
||||
.children
|
||||
.iter_mut()
|
||||
.zip(current_children.iter().zip(new_children.iter()))
|
||||
{
|
||||
child_state.diff(current, new);
|
||||
}
|
||||
|
||||
if current_children.len() < new_children.len() {
|
||||
self.children.extend(
|
||||
new_children[current_children.len()..]
|
||||
.iter()
|
||||
.map(Self::new),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
*self = Self::new(new);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue