Merge pull request #1284 from iced-rs/virtual-widgets

Stateless widgets
This commit is contained in:
Héctor Ramón 2022-03-23 17:11:14 +07:00 committed by GitHub
commit 0eef527fa5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
140 changed files with 12733 additions and 2709 deletions

View file

@ -1,12 +1,13 @@
//! Organize rendering primitives into a flattened list of layers.
use crate::alignment;
use crate::image;
use crate::svg;
use crate::triangle;
use crate::{
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
};
use iced_native::image;
use iced_native::svg;
/// A group of primitives that should be clipped together.
#[derive(Debug, Clone)]
pub struct Layer<'a> {

View file

@ -1,8 +1,10 @@
//! Create a renderer from a [`Backend`].
use crate::backend::{self, Backend};
use crate::{Primitive, Vector};
use iced_native::image;
use iced_native::layout;
use iced_native::renderer;
use iced_native::svg;
use iced_native::text::{self, Text};
use iced_native::{Background, Element, Font, Point, Rectangle, Size};
@ -168,3 +170,31 @@ where
});
}
}
impl<B> image::Renderer for Renderer<B>
where
B: Backend + backend::Image,
{
type Handle = image::Handle;
fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
self.backend().dimensions(handle)
}
fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
self.draw_primitive(Primitive::Image { handle, bounds })
}
}
impl<B> svg::Renderer for Renderer<B>
where
B: Backend + backend::Svg,
{
fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
self.backend().viewport_dimensions(handle)
}
fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
self.draw_primitive(Primitive::Svg { handle, bounds })
}
}

View file

@ -1,67 +1,4 @@
//! Use the widgets supported out-of-the-box.
//!
//! # Re-exports
//! For convenience, the contents of this module are available at the root
//! module. Therefore, you can directly type:
//!
//! ```
//! use iced_graphics::{button, Button};
//! ```
pub mod button;
pub mod checkbox;
pub mod container;
pub mod image;
pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
pub mod radio;
pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
mod column;
mod row;
mod space;
mod text;
#[doc(no_inline)]
pub use button::Button;
#[doc(no_inline)]
pub use checkbox::Checkbox;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
#[doc(no_inline)]
pub use rule::Rule;
#[doc(no_inline)]
pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
pub use column::Column;
pub use image::Image;
pub use row::Row;
pub use space::Space;
pub use svg::Svg;
pub use text::Text;
//! Use the graphical widgets supported out-of-the-box.
#[cfg(feature = "canvas")]
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
pub mod canvas;
@ -77,3 +14,6 @@ pub mod qr_code;
#[cfg(feature = "qr_code")]
#[doc(no_inline)]
pub use qr_code::QRCode;
#[cfg(feature = "pure")]
pub mod pure;

View file

@ -1,12 +0,0 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
use crate::Renderer;
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::widget::Button<'a, Message, Renderer<Backend>>;

View file

@ -6,14 +6,6 @@
use crate::renderer::{self, Renderer};
use crate::{Backend, Primitive};
use iced_native::layout;
use iced_native::mouse;
use iced_native::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
};
use std::marker::PhantomData;
pub mod event;
pub mod path;
@ -37,6 +29,15 @@ pub use program::Program;
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use text::Text;
use iced_native::layout;
use iced_native::mouse;
use iced_native::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
};
use std::marker::PhantomData;
/// A widget capable of drawing 2D graphics.
///
/// # Examples
@ -97,7 +98,7 @@ pub struct Canvas<Message, P: Program<Message>> {
width: Length,
height: Length,
program: P,
phantom: PhantomData<Message>,
message_: PhantomData<Message>,
}
impl<Message, P: Program<Message>> Canvas<Message, P> {
@ -109,7 +110,7 @@ impl<Message, P: Program<Message>> Canvas<Message, P> {
width: Length::Units(Self::DEFAULT_SIZE),
height: Length::Units(Self::DEFAULT_SIZE),
program,
phantom: PhantomData,
message_: PhantomData,
}
}

View file

@ -1,10 +0,0 @@
//! Show toggle controls using checkboxes.
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<'a, Message, Backend> =
iced_native::widget::Checkbox<'a, Message, Renderer<Backend>>;

View file

@ -1,5 +0,0 @@
use crate::Renderer;
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> =
iced_native::widget::Column<'a, Message, Renderer<Backend>>;

View file

@ -1,11 +0,0 @@
//! Decorate content and apply alignment.
use crate::Renderer;
pub use iced_style::container::{Style, StyleSheet};
/// An element decorating some content.
///
/// This is an alias of an `iced_native` container with a default
/// `Renderer`.
pub type Container<'a, Message, Backend> =
iced_native::widget::Container<'a, Message, Renderer<Backend>>;

View file

@ -1,25 +0,0 @@
//! Display images in your user interface.
pub mod viewer;
use crate::backend::{self, Backend};
use crate::{Primitive, Rectangle, Renderer};
use iced_native::image;
pub use iced_native::widget::image::{Image, Viewer};
pub use image::Handle;
impl<B> image::Renderer for Renderer<B>
where
B: Backend + backend::Image,
{
type Handle = image::Handle;
fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
self.backend().dimensions(handle)
}
fn draw(&mut self, handle: image::Handle, bounds: Rectangle) {
self.draw_primitive(Primitive::Image { handle, bounds })
}
}

View file

@ -1,2 +0,0 @@
//! Zoom and pan on an image.
pub use iced_native::widget::image::Viewer;

View file

@ -1,26 +0,0 @@
//! Let your users split regions of your application and organize layout dynamically.
//!
//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
//!
//! # Example
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
pub use iced_native::widget::pane_grid::{
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
ResizeEvent, Split, State, TitleBar,
};
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.
///
/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
///
/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`.
pub type PaneGrid<'a, Message, Backend> =
iced_native::widget::PaneGrid<'a, Message, Renderer<Backend>>;

View file

@ -1,9 +0,0 @@
//! Display a dropdown list of selectable values.
use crate::Renderer;
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::widget::PickList<'a, T, Message, Renderer<Backend>>;

View file

@ -1,5 +0,0 @@
//! Allow your users to visually track the progress of a computation.
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
pub use iced_native::widget::progress_bar::*;

View file

@ -0,0 +1,12 @@
//! Leverage pure, virtual widgets in your application.
#[cfg(feature = "canvas")]
pub mod canvas;
#[cfg(feature = "canvas")]
pub use canvas::Canvas;
#[cfg(feature = "qr_code")]
pub mod qr_code;
#[cfg(feature = "qr_code")]
pub use qr_code::QRCode;

View file

@ -0,0 +1,237 @@
//! Draw 2D graphics for your users.
//!
//! 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!
mod program;
pub use crate::widget::canvas::{Canvas as _, Program as _, *};
pub use program::Program;
use crate::{Backend, Primitive, Renderer};
use iced_native::layout::{self, Layout};
use iced_native::mouse;
use iced_native::renderer;
use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Size, Vector};
use iced_pure::widget::tree::{self, Tree};
use iced_pure::{Element, Widget};
use std::marker::PhantomData;
/// A widget capable of drawing 2D graphics.
///
/// ## Drawing a simple circle
/// If you want to get a quick overview, here's how we can draw a simple circle:
///
/// ```no_run
/// # mod iced {
/// # pub mod pure {
/// # pub use iced_graphics::pure::canvas;
/// # }
/// # pub use iced_native::{Color, Rectangle};
/// # }
/// use iced::pure::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program};
/// use iced::{Color, Rectangle};
///
/// // First, we define the data we need for drawing
/// #[derive(Debug)]
/// struct Circle {
/// radius: f32,
/// }
///
/// // Then, we implement the `Program` trait
/// impl Program<()> for Circle {
/// type State = ();
///
/// fn draw(&self, _state: &(), bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{
/// // We prepare a new `Frame`
/// let mut frame = Frame::new(bounds.size());
///
/// // We create a `Path` representing a simple circle
/// let circle = Path::circle(frame.center(), self.radius);
///
/// // And fill it with some color
/// frame.fill(&circle, Color::BLACK);
///
/// // Finally, we produce the geometry
/// vec![frame.into_geometry()]
/// }
/// }
///
/// // Finally, we simply use our `Circle` to create the `Canvas`!
/// let canvas = Canvas::new(Circle { radius: 50.0 });
/// ```
#[derive(Debug)]
pub struct Canvas<Message, P>
where
P: Program<Message>,
{
width: Length,
height: Length,
program: P,
message_: PhantomData<Message>,
}
impl<Message, P> Canvas<Message, P>
where
P: Program<Message>,
{
const DEFAULT_SIZE: u16 = 100;
/// Creates a new [`Canvas`].
pub fn new(program: P) -> Self {
Canvas {
width: Length::Units(Self::DEFAULT_SIZE),
height: Length::Units(Self::DEFAULT_SIZE),
program,
message_: PhantomData,
}
}
/// Sets the width of the [`Canvas`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Canvas`].
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
}
impl<Message, P, B> Widget<Message, Renderer<B>> for Canvas<Message, P>
where
P: Program<Message>,
B: Backend,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<P::State>()
}
fn state(&self) -> tree::State {
tree::State::new(P::State::default())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
_renderer: &Renderer<B>,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: iced_native::Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer<B>,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let canvas_event = match event {
iced_native::Event::Mouse(mouse_event) => {
Some(Event::Mouse(mouse_event))
}
iced_native::Event::Keyboard(keyboard_event) => {
Some(Event::Keyboard(keyboard_event))
}
_ => None,
};
let cursor = Cursor::from_window_position(cursor_position);
if let Some(canvas_event) = canvas_event {
let state = tree.state.downcast_mut::<P::State>();
let (event_status, message) =
self.program.update(state, canvas_event, bounds, cursor);
if let Some(message) = message {
shell.publish(message);
}
return event_status;
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer<B>,
) -> mouse::Interaction {
let bounds = layout.bounds();
let cursor = Cursor::from_window_position(cursor_position);
let state = tree.state.downcast_ref::<P::State>();
self.program.mouse_interaction(state, bounds, cursor)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer<B>,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
use iced_native::Renderer as _;
let bounds = layout.bounds();
if bounds.width < 1.0 || bounds.height < 1.0 {
return;
}
let translation = Vector::new(bounds.x, bounds.y);
let cursor = Cursor::from_window_position(cursor_position);
let state = tree.state.downcast_ref::<P::State>();
renderer.with_translation(translation, |renderer| {
renderer.draw_primitive(Primitive::Group {
primitives: self
.program
.draw(state, bounds, cursor)
.into_iter()
.map(Geometry::into_primitive)
.collect(),
});
});
}
}
impl<'a, Message, P, B> From<Canvas<Message, P>>
for Element<'a, Message, Renderer<B>>
where
Message: 'a,
P: Program<Message> + 'a,
B: Backend,
{
fn from(canvas: Canvas<Message, P>) -> Element<'a, Message, Renderer<B>> {
Element::new(canvas)
}
}

View file

@ -0,0 +1,100 @@
use crate::widget::pure::canvas::event::{self, Event};
use crate::widget::pure::canvas::mouse;
use crate::widget::pure::canvas::{Cursor, Geometry};
use crate::Rectangle;
/// The state and logic of a [`Canvas`].
///
/// A [`Program`] can mutate internal state and produce messages for an
/// application.
///
/// [`Canvas`]: crate::widget::Canvas
pub trait Program<Message> {
/// The internal [`State`] mutated by the [`Program`].
type State: Default + 'static;
/// Updates the state of the [`Program`].
///
/// When a [`Program`] is used in a [`Canvas`], the runtime will call this
/// method for each [`Event`].
///
/// This method can optionally return a `Message` to notify an application
/// of any meaningful interactions.
///
/// By default, this method does and returns nothing.
///
/// [`Canvas`]: crate::widget::Canvas
fn update(
&self,
_state: &mut Self::State,
_event: Event,
_bounds: Rectangle,
_cursor: Cursor,
) -> (event::Status, Option<Message>) {
(event::Status::Ignored, None)
}
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
///
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
/// [`Frame`]: crate::widget::canvas::Frame
/// [`Cache`]: crate::widget::canvas::Cache
fn draw(
&self,
state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
) -> Vec<Geometry>;
/// Returns the current mouse interaction of the [`Program`].
///
/// The interaction returned will be in effect even if the cursor position
/// is out of bounds of the program's [`Canvas`].
///
/// [`Canvas`]: crate::widget::Canvas
fn mouse_interaction(
&self,
_state: &Self::State,
_bounds: Rectangle,
_cursor: Cursor,
) -> mouse::Interaction {
mouse::Interaction::default()
}
}
impl<Message, T> Program<Message> for &T
where
T: Program<Message>,
{
type State = T::State;
fn update(
&self,
state: &mut Self::State,
event: Event,
bounds: Rectangle,
cursor: Cursor,
) -> (event::Status, Option<Message>) {
T::update(self, state, event, bounds, cursor)
}
fn draw(
&self,
state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
) -> Vec<Geometry> {
T::draw(self, state, bounds, cursor)
}
fn mouse_interaction(
&self,
state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
) -> mouse::Interaction {
T::mouse_interaction(self, state, bounds, cursor)
}
}

View file

@ -0,0 +1,61 @@
//! Encode and display information in a QR code.
pub use crate::qr_code::*;
use crate::{Backend, Renderer};
use iced_native::layout::{self, Layout};
use iced_native::renderer;
use iced_native::{Length, Point, Rectangle};
use iced_pure::widget::tree::Tree;
use iced_pure::{Element, Widget};
impl<'a, Message, B> Widget<Message, Renderer<B>> for QRCode<'a>
where
B: Backend,
{
fn width(&self) -> Length {
<Self as iced_native::Widget<Message, Renderer<B>>>::width(self)
}
fn height(&self) -> Length {
<Self as iced_native::Widget<Message, Renderer<B>>>::height(self)
}
fn layout(
&self,
renderer: &Renderer<B>,
limits: &layout::Limits,
) -> layout::Node {
<Self as iced_native::Widget<Message, Renderer<B>>>::layout(
self, renderer, limits,
)
}
fn draw(
&self,
_tree: &Tree,
renderer: &mut Renderer<B>,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
<Self as iced_native::Widget<Message, Renderer<B>>>::draw(
self,
renderer,
style,
layout,
cursor_position,
viewport,
)
}
}
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for QRCode<'a>
where
B: Backend,
{
fn into(self) -> Element<'a, Message, Renderer<B>> {
Element::new(self)
}
}

View file

@ -1,11 +0,0 @@
//! Create choices using radio buttons.
use crate::Renderer;
pub use iced_style::radio::{Style, StyleSheet};
/// A circular button representing a choice.
///
/// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`.
pub type Radio<'a, Message, Backend> =
iced_native::widget::Radio<'a, Message, Renderer<Backend>>;

View file

@ -1,5 +0,0 @@
use crate::Renderer;
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> =
iced_native::widget::Row<'a, Message, Renderer<Backend>>;

View file

@ -1,3 +0,0 @@
//! Display a horizontal or vertical rule for dividing content.
pub use iced_native::widget::rule::*;

View file

@ -1,13 +0,0 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::Renderer;
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
/// with a scrollbar.
///
/// This is an alias of an `iced_native` scrollable with a default
/// `Renderer`.
pub type Scrollable<'a, Message, Backend> =
iced_native::widget::Scrollable<'a, Message, Renderer<Backend>>;

View file

@ -1,5 +0,0 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
pub use iced_native::widget::slider::{Slider, State};
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};

View file

@ -1 +0,0 @@
pub use iced_native::widget::Space;

View file

@ -1,20 +0,0 @@
//! Display vector graphics in your application.
use crate::backend::{self, Backend};
use crate::{Primitive, Rectangle, Renderer};
use iced_native::svg;
pub use iced_native::widget::svg::Svg;
pub use svg::Handle;
impl<B> svg::Renderer for Renderer<B>
where
B: Backend + backend::Svg,
{
fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
self.backend().viewport_dimensions(handle)
}
fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
self.draw_primitive(Primitive::Svg { handle, bounds })
}
}

View file

@ -1,7 +0,0 @@
//! Write some text for your users to read.
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::widget::Text<Renderer<Backend>>;

View file

@ -1,13 +0,0 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
use crate::Renderer;
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::widget::TextInput<'a, Message, Renderer<Backend>>;

View file

@ -1,10 +0,0 @@
//! Show toggle controls using togglers.
use crate::Renderer;
pub use iced_style::toggler::{Style, StyleSheet};
/// A toggler that can be toggled.
///
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
pub type Toggler<'a, Message, Backend> =
iced_native::widget::Toggler<'a, Message, Renderer<Backend>>;

View file

@ -1,11 +0,0 @@
//! Decorate content and apply alignment.
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::widget::Tooltip<'a, Message, Renderer<Backend>>;
pub use iced_native::widget::tooltip::Position;