Rename iced_virtual to iced_pure

`virtual` is a reserved keyword in Rust 😬
This commit is contained in:
Héctor Ramón Jiménez 2022-02-11 17:50:12 +07:00
parent e03de01988
commit 897188317b
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
15 changed files with 16 additions and 15 deletions

272
pure/src/widget/button.rs Normal file
View 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
View 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)
}
}

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