Create iced_widget subcrate and re-organize the whole codebase

This commit is contained in:
Héctor Ramón Jiménez 2023-03-04 05:37:11 +01:00
parent c54409d171
commit 3a0d34c024
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
209 changed files with 1959 additions and 2183 deletions

23
core/src/clipboard.rs Normal file
View file

@ -0,0 +1,23 @@
//! Access the clipboard.
/// A buffer for short-term storage and transfer within and between
/// applications.
pub trait Clipboard {
/// Reads the current content of the [`Clipboard`] as text.
fn read(&self) -> Option<String>;
/// Writes the given text contents to the [`Clipboard`].
fn write(&mut self, contents: String);
}
/// A null implementation of the [`Clipboard`] trait.
#[derive(Debug, Clone, Copy)]
pub struct Null;
impl Clipboard for Null {
fn read(&self) -> Option<String> {
None
}
fn write(&mut self, _contents: String) {}
}

608
core/src/element.rs Normal file
View file

@ -0,0 +1,608 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
};
use std::any::Any;
use std::borrow::Borrow;
/// A generic [`Widget`].
///
/// It is useful to build composable user interfaces that do not leak
/// implementation details in their __view logic__.
///
/// If you have a [built-in widget], you should be able to use `Into<Element>`
/// to turn it into an [`Element`].
///
/// [built-in widget]: crate::widget
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
widget: Box<dyn Widget<Message, Renderer> + 'a>,
}
impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// Creates a new [`Element`] containing the given [`Widget`].
pub fn new(widget: impl Widget<Message, Renderer> + 'a) -> Self
where
Renderer: crate::Renderer,
{
Self {
widget: Box::new(widget),
}
}
/// Returns a reference to the [`Widget`] of the [`Element`],
pub fn as_widget(&self) -> &dyn Widget<Message, Renderer> {
self.widget.as_ref()
}
/// Returns a mutable reference to the [`Widget`] of the [`Element`],
pub fn as_widget_mut(&mut self) -> &mut dyn Widget<Message, Renderer> {
self.widget.as_mut()
}
/// Applies a transformation to the produced message of the [`Element`].
///
/// This method is useful when you want to decouple different parts of your
/// UI and make them __composable__.
///
/// # Example
/// Imagine we want to use [our counter](index.html#usage). But instead of
/// showing a single counter, we want to display many of them. We can reuse
/// the `Counter` type as it is!
///
/// We use composition to model the __state__ of our new application:
///
/// ```
/// # mod counter {
/// # pub struct Counter;
/// # }
/// use counter::Counter;
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
/// }
/// ```
///
/// We can store the state of multiple counters now. However, the
/// __messages__ we implemented before describe the user interactions
/// of a __single__ counter. Right now, we need to also identify which
/// counter is receiving user interactions. Can we use composition again?
/// Yes.
///
/// ```
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # }
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// Counter(usize, counter::Message)
/// }
/// ```
///
/// We compose the previous __messages__ with the index of the counter
/// producing them. Let's implement our __view logic__ now:
///
/// ```no_run
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn view(
/// # &self,
/// # ) -> iced_core::Element<Message, iced_core::renderer::Null> {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// #
/// # mod iced {
/// # pub use iced_core::renderer::Null as Renderer;
/// # pub use iced_core::Element;
/// #
/// # pub mod widget {
/// # pub struct Row<Message> {
/// # _t: std::marker::PhantomData<Message>,
/// # }
/// #
/// # impl<Message> Row<Message> {
/// # pub fn new() -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn spacing(mut self, _: u32) -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn push(
/// # mut self,
/// # _: iced_core::Element<Message, iced_core::renderer::Null>,
/// # ) -> Self {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// # }
/// #
/// use counter::Counter;
///
/// use iced::widget::Row;
/// use iced::{Element, Renderer};
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
/// }
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// Counter(usize, counter::Message),
/// }
///
/// impl ManyCounters {
/// pub fn view(&mut self) -> Row<Message> {
/// // We can quickly populate a `Row` by folding over our counters
/// self.counters.iter_mut().enumerate().fold(
/// Row::new().spacing(20),
/// |row, (index, counter)| {
/// // We display the counter
/// let element: Element<counter::Message, Renderer> =
/// counter.view().into();
///
/// row.push(
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
/// element
/// .map(move |message| Message::Counter(index, message)),
/// )
/// },
/// )
/// }
/// }
/// ```
///
/// Finally, our __update logic__ is pretty straightforward: simple
/// delegation.
///
/// ```
/// # mod counter {
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn update(&mut self, _message: Message) {}
/// # }
/// # }
/// #
/// # use counter::Counter;
/// #
/// # struct ManyCounters {
/// # counters: Vec<Counter>,
/// # }
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
/// impl ManyCounters {
/// pub fn update(&mut self, message: Message) {
/// match message {
/// Message::Counter(index, counter_msg) => {
/// if let Some(counter) = self.counters.get_mut(index) {
/// counter.update(counter_msg);
/// }
/// }
/// }
/// }
/// }
/// ```
pub fn map<B>(
self,
f: impl Fn(Message) -> B + 'a,
) -> Element<'a, B, Renderer>
where
Message: 'a,
Renderer: crate::Renderer + 'a,
B: 'a,
{
Element::new(Map::new(self.widget, f))
}
/// Marks the [`Element`] as _to-be-explained_.
///
/// The [`Renderer`] will explain the layout of the [`Element`] graphically.
/// This can be very useful for debugging your layout!
///
/// [`Renderer`]: crate::Renderer
pub fn explain<C: Into<Color>>(
self,
color: C,
) -> Element<'a, Message, Renderer>
where
Message: 'static,
Renderer: crate::Renderer + 'a,
{
Element {
widget: Box::new(Explain::new(self, color.into())),
}
}
}
impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
for Element<'a, Message, Renderer>
{
fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
self.widget.borrow()
}
}
impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
for &Element<'a, Message, Renderer>
{
fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
self.widget.borrow()
}
}
struct Map<'a, A, B, Renderer> {
widget: Box<dyn Widget<A, Renderer> + 'a>,
mapper: Box<dyn Fn(A) -> B + 'a>,
}
impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
pub fn new<F>(
widget: Box<dyn Widget<A, Renderer> + 'a>,
mapper: F,
) -> Map<'a, A, B, Renderer>
where
F: 'a + Fn(A) -> B,
{
Map {
widget,
mapper: Box::new(mapper),
}
}
}
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
Renderer: crate::Renderer + 'a,
A: 'a,
B: 'a,
{
fn tag(&self) -> tree::Tag {
self.widget.tag()
}
fn state(&self) -> tree::State {
self.widget.state()
}
fn children(&self) -> Vec<Tree> {
self.widget.children()
}
fn diff(&self, tree: &mut Tree) {
self.widget.diff(tree)
}
fn width(&self) -> Length {
self.widget.width()
}
fn height(&self) -> Length {
self.widget.height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.widget.layout(renderer, limits)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<B>,
) {
struct MapOperation<'a, B> {
operation: &'a mut dyn widget::Operation<B>,
}
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
fn container(
&mut self,
id: Option<&widget::Id>,
operate_on_children: &mut dyn FnMut(
&mut dyn widget::Operation<T>,
),
) {
self.operation.container(id, &mut |operation| {
operate_on_children(&mut MapOperation { operation });
});
}
fn focusable(
&mut self,
state: &mut dyn widget::operation::Focusable,
id: Option<&widget::Id>,
) {
self.operation.focusable(state, id);
}
fn scrollable(
&mut self,
state: &mut dyn widget::operation::Scrollable,
id: Option<&widget::Id>,
) {
self.operation.scrollable(state, id);
}
fn text_input(
&mut self,
state: &mut dyn widget::operation::TextInput,
id: Option<&widget::Id>,
) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
self.operation.custom(state, id);
}
}
self.widget.operate(
tree,
layout,
renderer,
&mut MapOperation { operation },
);
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
) -> event::Status {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
let status = self.widget.on_event(
tree,
event,
layout,
cursor_position,
renderer,
clipboard,
&mut local_shell,
);
shell.merge(local_shell, &self.mapper);
status
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.widget.draw(
tree,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.widget.mouse_interaction(
tree,
layout,
cursor_position,
viewport,
renderer,
)
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, B, Renderer>> {
let mapper = &self.mapper;
self.widget
.overlay(tree, layout, renderer)
.map(move |overlay| overlay.map(mapper))
}
}
struct Explain<'a, Message, Renderer: crate::Renderer> {
element: Element<'a, Message, Renderer>,
color: Color,
}
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
Explain { element, color }
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.element.widget.width()
}
fn height(&self) -> Length {
self.element.widget.height()
}
fn tag(&self) -> tree::Tag {
self.element.widget.tag()
}
fn state(&self) -> tree::State {
self.element.widget.state()
}
fn children(&self) -> Vec<Tree> {
self.element.widget.children()
}
fn diff(&self, tree: &mut Tree) {
self.element.widget.diff(tree);
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.element.widget.layout(renderer, limits)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.element
.widget
.operate(state, layout, renderer, operation)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.element.widget.on_event(
state,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
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.into(),
},
Color::TRANSPARENT,
);
for child in layout.children() {
explain_layout(renderer, color, child);
}
}
self.element.widget.draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
explain_layout(renderer, self.color, layout);
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.element.widget.mouse_interaction(
state,
layout,
cursor_position,
viewport,
renderer,
)
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.element.widget.overlay(state, layout, renderer)
}
}

78
core/src/event.rs Normal file
View file

@ -0,0 +1,78 @@
//! Handle events of a user interface.
use crate::keyboard;
use crate::mouse;
use crate::touch;
use crate::window;
/// A user interface event.
///
/// _**Note:** This type is largely incomplete! If you need to track
/// additional events, feel free to [open an issue] and share your use case!_
///
/// [open an issue]: https://github.com/iced-rs/iced/issues
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
/// A keyboard event
Keyboard(keyboard::Event),
/// A mouse event
Mouse(mouse::Event),
/// A window event
Window(window::Event),
/// A touch event
Touch(touch::Event),
/// A platform specific event
PlatformSpecific(PlatformSpecific),
}
/// A platform specific event
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlatformSpecific {
/// A MacOS specific event
MacOS(MacOS),
}
/// Describes an event specific to MacOS
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacOS {
/// Triggered when the app receives an URL from the system
///
/// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_
///
/// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19
ReceivedUrl(String),
}
/// The status of an [`Event`] after being processed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Event`] was **NOT** handled by any widget.
Ignored,
/// The [`Event`] was handled and processed by a widget.
Captured,
}
impl Status {
/// Merges two [`Status`] into one.
///
/// `Captured` takes precedence over `Ignored`:
///
/// ```
/// use iced_core::event::Status;
///
/// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);
/// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured);
/// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured);
/// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured);
/// ```
pub fn merge(self, b: Self) -> Self {
match self {
Status::Ignored => b,
Status::Captured => Status::Captured,
}
}
}

13
core/src/hasher.rs Normal file
View file

@ -0,0 +1,13 @@
/// The hasher used to compare layouts.
#[derive(Debug, Default)]
pub struct Hasher(twox_hash::XxHash64);
impl core::hash::Hasher for Hasher {
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
fn finish(&self) -> u64 {
self.0.finish()
}
}

174
core/src/image.rs Normal file
View file

@ -0,0 +1,174 @@
//! Load and draw raster graphics.
use crate::{Hasher, Rectangle, Size};
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
/// A handle of some image data.
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
data: 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 RGBA
/// pixels.
///
/// This is useful if you have already decoded your image.
pub fn from_pixels(
width: u32,
height: u32,
pixels: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Handle {
Self::from_data(Data::Rgba {
width,
height,
pixels: Bytes::new(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: impl AsRef<[u8]> + Send + Sync + 'static,
) -> Handle {
Self::from_data(Data::Bytes(Bytes::new(bytes)))
}
fn from_data(data: Data) -> Handle {
let mut hasher = Hasher::default();
data.hash(&mut hasher);
Handle {
id: hasher.finish(),
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);
}
}
/// A wrapper around raw image data.
///
/// It behaves like a `&[u8]`.
#[derive(Clone)]
pub struct Bytes(Arc<dyn AsRef<[u8]> + Send + Sync + 'static>);
impl Bytes {
/// Creates new [`Bytes`] around `data`.
pub fn new(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self {
Self(Arc::new(data))
}
}
impl std::fmt::Debug for Bytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_ref().as_ref().fmt(f)
}
}
impl std::hash::Hash for Bytes {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.as_ref().as_ref().hash(state);
}
}
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_ref()
}
}
impl std::ops::Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.0.as_ref().as_ref()
}
}
/// The data of a raster image.
#[derive(Clone, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
/// In-memory data
Bytes(Bytes),
/// Decoded image pixels in RGBA format.
Rgba {
/// The width of the image.
width: u32,
/// The height of the image.
height: u32,
/// The pixels.
pixels: Bytes,
},
}
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::Rgba { width, height, .. } => {
write!(f, "Pixels({width} * {height})")
}
}
}
}
/// A [`Renderer`] that can render raster graphics.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`]
///
/// [`Handle`]: Self::Handle
type Handle: Clone + Hash;
/// Returns the dimensions of an image for the given [`Handle`].
fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
fn draw(&mut self, handle: Self::Handle, bounds: Rectangle);
}

65
core/src/layout.rs Normal file
View file

@ -0,0 +1,65 @@
//! Position your widgets properly.
mod limits;
mod node;
pub mod flex;
pub use limits::Limits;
pub use node::Node;
use crate::{Point, Rectangle, Vector};
/// The bounds of a [`Node`] and its children, using absolute coordinates.
#[derive(Debug, Clone, Copy)]
pub struct Layout<'a> {
position: Point,
node: &'a Node,
}
impl<'a> Layout<'a> {
/// Creates a new [`Layout`] for the given [`Node`] at the origin.
pub fn new(node: &'a Node) -> Self {
Self::with_offset(Vector::new(0.0, 0.0), node)
}
/// Creates a new [`Layout`] for the given [`Node`] with the provided offset
/// from the origin.
pub fn with_offset(offset: Vector, node: &'a Node) -> Self {
let bounds = node.bounds();
Self {
position: Point::new(bounds.x, bounds.y) + offset,
node,
}
}
/// Returns the position of the [`Layout`].
pub fn position(&self) -> Point {
self.position
}
/// Returns the bounds of the [`Layout`].
///
/// The returned [`Rectangle`] describes the position and size of a
/// [`Node`].
pub fn bounds(&self) -> Rectangle {
let bounds = self.node.bounds();
Rectangle {
x: self.position.x,
y: self.position.y,
width: bounds.width,
height: bounds.height,
}
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
self.node.children().iter().map(move |node| {
Layout::with_offset(
Vector::new(self.position.x, self.position.y),
node,
)
})
}
}

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

232
core/src/layout/flex.rs Normal file
View 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 crate::layout::{Limits, Node};
use crate::{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: crate::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, padding.top);
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)
}

163
core/src/layout/limits.rs Normal file
View file

@ -0,0 +1,163 @@
#![allow(clippy::manual_clamp)]
use crate::{Length, Padding, Size};
/// A set of size constraints for layouting.
#[derive(Debug, Clone, Copy)]
pub struct Limits {
min: Size,
max: Size,
fill: Size,
}
impl Limits {
/// No limits
pub const NONE: Limits = Limits {
min: Size::ZERO,
max: Size::INFINITY,
fill: Size::INFINITY,
};
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
pub const fn new(min: Size, max: Size) -> Limits {
Limits {
min,
max,
fill: Size::INFINITY,
}
}
/// Returns the minimum [`Size`] of the [`Limits`].
pub fn min(&self) -> Size {
self.min
}
/// Returns the maximum [`Size`] of the [`Limits`].
pub fn max(&self) -> Size {
self.max
}
/// Returns the fill [`Size`] of the [`Limits`].
pub fn fill(&self) -> Size {
self.fill
}
/// Applies a width constraint to the current [`Limits`].
pub fn width(mut self, width: impl Into<Length>) -> Limits {
match width.into() {
Length::Shrink => {
self.fill.width = self.min.width;
}
Length::Fill | Length::FillPortion(_) => {
self.fill.width = self.fill.width.min(self.max.width);
}
Length::Fixed(amount) => {
let new_width = amount.min(self.max.width).max(self.min.width);
self.min.width = new_width;
self.max.width = new_width;
self.fill.width = new_width;
}
}
self
}
/// Applies a height constraint to the current [`Limits`].
pub fn height(mut self, height: impl Into<Length>) -> Limits {
match height.into() {
Length::Shrink => {
self.fill.height = self.min.height;
}
Length::Fill | Length::FillPortion(_) => {
self.fill.height = self.fill.height.min(self.max.height);
}
Length::Fixed(amount) => {
let new_height =
amount.min(self.max.height).max(self.min.height);
self.min.height = new_height;
self.max.height = new_height;
self.fill.height = new_height;
}
}
self
}
/// Applies a minimum width constraint to the current [`Limits`].
pub fn min_width(mut self, min_width: f32) -> Limits {
self.min.width = self.min.width.max(min_width).min(self.max.width);
self
}
/// Applies a maximum width constraint to the current [`Limits`].
pub fn max_width(mut self, max_width: f32) -> Limits {
self.max.width = self.max.width.min(max_width).max(self.min.width);
self
}
/// Applies a minimum height constraint to the current [`Limits`].
pub fn min_height(mut self, min_height: f32) -> Limits {
self.min.height = self.min.height.max(min_height).min(self.max.height);
self
}
/// Applies a maximum height constraint to the current [`Limits`].
pub fn max_height(mut self, max_height: f32) -> Limits {
self.max.height = self.max.height.min(max_height).max(self.min.height);
self
}
/// Shrinks the current [`Limits`] to account for the given padding.
pub fn pad(&self, padding: Padding) -> Limits {
self.shrink(Size::new(padding.horizontal(), padding.vertical()))
}
/// Shrinks the current [`Limits`] by the given [`Size`].
pub fn shrink(&self, size: Size) -> Limits {
let min = Size::new(
(self.min().width - size.width).max(0.0),
(self.min().height - size.height).max(0.0),
);
let max = Size::new(
(self.max().width - size.width).max(0.0),
(self.max().height - size.height).max(0.0),
);
let fill = Size::new(
(self.fill.width - size.width).max(0.0),
(self.fill.height - size.height).max(0.0),
);
Limits { min, max, fill }
}
/// Removes the minimum width constraint for the current [`Limits`].
pub fn loose(&self) -> Limits {
Limits {
min: Size::ZERO,
max: self.max,
fill: self.fill,
}
}
/// Computes the resulting [`Size`] that fits the [`Limits`] given the
/// intrinsic size of some content.
pub fn resolve(&self, intrinsic_size: Size) -> Size {
Size::new(
intrinsic_size
.width
.min(self.max.width)
.max(self.fill.width),
intrinsic_size
.height
.min(self.max.height)
.max(self.fill.height),
)
}
}

91
core/src/layout/node.rs Normal file
View file

@ -0,0 +1,91 @@
use crate::{Alignment, Point, Rectangle, Size, Vector};
/// The bounds of an element and its children.
#[derive(Debug, Clone, Default)]
pub struct Node {
bounds: Rectangle,
children: Vec<Node>,
}
impl Node {
/// Creates a new [`Node`] with the given [`Size`].
pub const fn new(size: Size) -> Self {
Self::with_children(size, Vec::new())
}
/// Creates a new [`Node`] with the given [`Size`] and children.
pub const fn with_children(size: Size, children: Vec<Node>) -> Self {
Node {
bounds: Rectangle {
x: 0.0,
y: 0.0,
width: size.width,
height: size.height,
},
children,
}
}
/// Returns the [`Size`] of the [`Node`].
pub fn size(&self) -> Size {
Size::new(self.bounds.width, self.bounds.height)
}
/// Returns the bounds of the [`Node`].
pub fn bounds(&self) -> Rectangle {
self.bounds
}
/// Returns the children of the [`Node`].
pub fn children(&self) -> &[Node] {
&self.children
}
/// Aligns the [`Node`] in the given space.
pub fn align(
&mut self,
horizontal_alignment: Alignment,
vertical_alignment: Alignment,
space: Size,
) {
match horizontal_alignment {
Alignment::Start => {}
Alignment::Center => {
self.bounds.x += (space.width - self.bounds.width) / 2.0;
}
Alignment::End => {
self.bounds.x += space.width - self.bounds.width;
}
Alignment::Fill => {
self.bounds.width = space.width;
}
}
match vertical_alignment {
Alignment::Start => {}
Alignment::Center => {
self.bounds.y += (space.height - self.bounds.height) / 2.0;
}
Alignment::End => {
self.bounds.y += space.height - self.bounds.height;
}
Alignment::Fill => {
self.bounds.height = space.height;
}
}
}
/// Moves the [`Node`] to the given position.
pub fn move_to(&mut self, position: Point) {
self.bounds.x = position.x;
self.bounds.y = position.y;
}
/// Translates the [`Node`] by the given translation.
pub fn translate(self, translation: Vector) -> Self {
Self {
bounds: self.bounds + translation,
..self
}
}
}

View file

@ -25,33 +25,57 @@
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
pub mod alignment;
pub mod clipboard;
pub mod event;
pub mod font;
pub mod gradient;
pub mod image;
pub mod keyboard;
pub mod layout;
pub mod mouse;
pub mod overlay;
pub mod renderer;
pub mod svg;
pub mod text;
pub mod time;
pub mod touch;
pub mod widget;
pub mod window;
mod background;
mod color;
mod content_fit;
mod element;
mod hasher;
mod length;
mod padding;
mod pixels;
mod point;
mod rectangle;
mod shell;
mod size;
mod vector;
pub use alignment::Alignment;
pub use background::Background;
pub use clipboard::Clipboard;
pub use color::Color;
pub use content_fit::ContentFit;
pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
pub use hasher::Hasher;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
pub use padding::Padding;
pub use pixels::Pixels;
pub use point::Point;
pub use rectangle::Rectangle;
pub use renderer::Renderer;
pub use shell::Shell;
pub use size::Size;
pub use text::Text;
pub use vector::Vector;
pub use widget::Widget;

View file

@ -1,8 +1,11 @@
//! Handle mouse events.
pub mod click;
mod button;
mod event;
mod interaction;
pub use button::Button;
pub use click::Click;
pub use event::{Event, ScrollDelta};
pub use interaction::Interaction;

76
core/src/mouse/click.rs Normal file
View file

@ -0,0 +1,76 @@
//! Track mouse clicks.
use crate::time::Instant;
use crate::Point;
/// A mouse click.
#[derive(Debug, Clone, Copy)]
pub struct Click {
kind: Kind,
position: Point,
time: Instant,
}
/// The kind of mouse click.
#[derive(Debug, Clone, Copy)]
pub enum Kind {
/// A single click
Single,
/// A double click
Double,
/// A triple click
Triple,
}
impl Kind {
fn next(&self) -> Kind {
match self {
Kind::Single => Kind::Double,
Kind::Double => Kind::Triple,
Kind::Triple => Kind::Double,
}
}
}
impl Click {
/// Creates a new [`Click`] with the given position and previous last
/// [`Click`].
pub fn new(position: Point, previous: Option<Click>) -> Click {
let time = Instant::now();
let kind = if let Some(previous) = previous {
if previous.is_consecutive(position, time) {
previous.kind.next()
} else {
Kind::Single
}
} else {
Kind::Single
};
Click {
kind,
position,
time,
}
}
/// Returns the [`Kind`] of [`Click`].
pub fn kind(&self) -> Kind {
self.kind
}
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
let duration = if time > self.time {
Some(time - self.time)
} else {
None
};
self.position == new_position
&& duration
.map(|duration| duration.as_millis() <= 300)
.unwrap_or(false)
}
}

122
core/src/overlay.rs Normal file
View file

@ -0,0 +1,122 @@
//! Display interactive elements on top of other widgets.
mod element;
mod group;
pub use element::Element;
pub use group::Group;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::widget::Tree;
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Returns the layout [`Node`] of the [`Overlay`].
///
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
///
/// [`Node`]: layout::Node
fn layout(
&self,
renderer: &Renderer,
bounds: Size,
position: Point,
) -> layout::Node;
/// Draws the [`Overlay`] using the associated `Renderer`.
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
);
/// Applies a [`widget::Operation`] to the [`Overlay`].
fn operate(
&mut self,
_layout: Layout<'_>,
_renderer: &Renderer,
_operation: &mut dyn widget::Operation<Message>,
) {
}
/// Processes a runtime [`Event`].
///
/// It receives:
/// * an [`Event`] describing user interaction
/// * the computed [`Layout`] of the [`Overlay`]
/// * the current cursor position
/// * a mutable `Message` list, allowing the [`Overlay`] to produce
/// new messages based on user interaction.
/// * the `Renderer`
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
fn on_event(
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
}
/// Returns the current [`mouse::Interaction`] of the [`Overlay`].
///
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse::Interaction::Idle
}
/// Returns true if the cursor is over the [`Overlay`].
///
/// By default, it returns true if the bounds of the `layout` contain
/// the `cursor_position`.
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
layout.bounds().contains(cursor_position)
}
}
/// Returns a [`Group`] of overlay [`Element`] children.
///
/// This method will generally only be used by advanced users that are
/// implementing the [`Widget`](crate::Widget) trait.
pub fn from_children<'a, Message, Renderer>(
children: &'a mut [crate::Element<'_, Message, Renderer>],
tree: &'a mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<Element<'a, Message, Renderer>>
where
Renderer: crate::Renderer,
{
let children = children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|((child, state), layout)| {
child.as_widget_mut().overlay(state, layout, renderer)
})
.collect::<Vec<_>>();
(!children.is_empty()).then(|| Group::with_children(children).overlay())
}

270
core/src/overlay/element.rs Normal file
View file

@ -0,0 +1,270 @@
pub use crate::Overlay;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
use std::any::Any;
/// A generic [`Overlay`].
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
position: Point,
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
}
impl<'a, Message, Renderer> Element<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Creates a new [`Element`] containing the given [`Overlay`].
pub fn new(
position: Point,
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
) -> Self {
Self { position, overlay }
}
/// Returns the position of the [`Element`].
pub fn position(&self) -> Point {
self.position
}
/// Translates the [`Element`].
pub fn translate(mut self, translation: Vector) -> Self {
self.position = self.position + translation;
self
}
/// Applies a transformation to the produced message of the [`Element`].
pub fn map<B>(self, f: &'a dyn Fn(Message) -> B) -> Element<'a, B, Renderer>
where
Message: 'a,
Renderer: 'a,
B: 'a,
{
Element {
position: self.position,
overlay: Box::new(Map::new(self.overlay, f)),
}
}
/// Computes the layout of the [`Element`] in the given bounds.
pub fn layout(
&self,
renderer: &Renderer,
bounds: Size,
translation: Vector,
) -> layout::Node {
self.overlay
.layout(renderer, bounds, self.position + translation)
}
/// Processes a runtime [`Event`].
pub 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.overlay.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
/// Returns the current [`mouse::Interaction`] of the [`Element`].
pub fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.overlay.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
}
/// Draws the [`Element`] and its children using the given [`Layout`].
pub fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
self.overlay
.draw(renderer, theme, style, layout, cursor_position)
}
/// Applies a [`widget::Operation`] to the [`Element`].
pub fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.overlay.operate(layout, renderer, operation);
}
/// Returns true if the cursor is over the [`Element`].
pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
self.overlay.is_over(layout, cursor_position)
}
}
struct Map<'a, A, B, Renderer> {
content: Box<dyn Overlay<A, Renderer> + 'a>,
mapper: &'a dyn Fn(A) -> B,
}
impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
pub fn new(
content: Box<dyn Overlay<A, Renderer> + 'a>,
mapper: &'a dyn Fn(A) -> B,
) -> Map<'a, A, B, Renderer> {
Map { content, mapper }
}
}
impl<'a, A, B, Renderer> Overlay<B, Renderer> for Map<'a, A, B, Renderer>
where
Renderer: crate::Renderer,
{
fn layout(
&self,
renderer: &Renderer,
bounds: Size,
position: Point,
) -> layout::Node {
self.content.layout(renderer, bounds, position)
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<B>,
) {
struct MapOperation<'a, B> {
operation: &'a mut dyn widget::Operation<B>,
}
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
fn container(
&mut self,
id: Option<&widget::Id>,
operate_on_children: &mut dyn FnMut(
&mut dyn widget::Operation<T>,
),
) {
self.operation.container(id, &mut |operation| {
operate_on_children(&mut MapOperation { operation });
});
}
fn focusable(
&mut self,
state: &mut dyn widget::operation::Focusable,
id: Option<&widget::Id>,
) {
self.operation.focusable(state, id);
}
fn scrollable(
&mut self,
state: &mut dyn widget::operation::Scrollable,
id: Option<&widget::Id>,
) {
self.operation.scrollable(state, id);
}
fn text_input(
&mut self,
state: &mut dyn widget::operation::TextInput,
id: Option<&widget::Id>,
) {
self.operation.text_input(state, id)
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
self.operation.custom(state, id);
}
}
self.content
.operate(layout, renderer, &mut MapOperation { operation });
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
) -> event::Status {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
let event_status = self.content.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
&mut local_shell,
);
shell.merge(local_shell, self.mapper);
event_status
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
self.content
.draw(renderer, theme, style, layout, cursor_position)
}
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
self.content.is_over(layout, cursor_position)
}
}

172
core/src/overlay/group.rs Normal file
View file

@ -0,0 +1,172 @@
use crate::event;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
/// children.
#[allow(missing_debug_implementations)]
pub struct Group<'a, Message, Renderer> {
children: Vec<overlay::Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Group<'a, Message, Renderer>
where
Renderer: 'a + crate::Renderer,
Message: 'a,
{
/// Creates an empty [`Group`].
pub fn new() -> Self {
Self::default()
}
/// Creates a [`Group`] with the given elements.
pub fn with_children(
children: Vec<overlay::Element<'a, Message, Renderer>>,
) -> Self {
Group { children }
}
/// Adds an [`overlay::Element`] to the [`Group`].
pub fn push(
mut self,
child: impl Into<overlay::Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
self
}
/// Turns the [`Group`] into an overlay [`overlay::Element`].
pub fn overlay(self) -> overlay::Element<'a, Message, Renderer> {
overlay::Element::new(Point::ORIGIN, Box::new(self))
}
}
impl<'a, Message, Renderer> Default for Group<'a, Message, Renderer>
where
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn default() -> Self {
Self::with_children(Vec::new())
}
}
impl<'a, Message, Renderer> Overlay<Message, Renderer>
for Group<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn layout(
&self,
renderer: &Renderer,
bounds: Size,
position: Point,
) -> layout::Node {
let translation = position - Point::ORIGIN;
layout::Node::with_children(
bounds,
self.children
.iter()
.map(|child| child.layout(renderer, bounds, translation))
.collect(),
)
}
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.children
.iter_mut()
.zip(layout.children())
.map(|(child, layout)| {
child.on_event(
event.clone(),
layout,
cursor_position,
renderer,
clipboard,
shell,
)
})
.fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &<Renderer as crate::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
for (child, layout) in self.children.iter().zip(layout.children()) {
child.draw(renderer, theme, style, layout, cursor_position);
}
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.children
.iter()
.zip(layout.children())
.map(|(child, layout)| {
child.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| {
child.operate(layout, renderer, operation);
},
)
});
}
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
self.children
.iter()
.zip(layout.children())
.any(|(child, layout)| child.is_over(layout, cursor_position))
}
}
impl<'a, Message, Renderer> From<Group<'a, Message, Renderer>>
for overlay::Element<'a, Message, Renderer>
where
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(group: Group<'a, Message, Renderer>) -> Self {
group.overlay()
}
}

99
core/src/renderer.rs Normal file
View file

@ -0,0 +1,99 @@
//! Write your own renderer.
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
use crate::layout;
use crate::{Background, Color, Element, Rectangle, Vector};
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
/// The supported theme of the [`Renderer`].
type Theme;
/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
/// after layouting. For instance, trimming the measurements cache.
fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.as_widget().layout(self, limits)
}
/// 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,
translation: Vector,
f: impl FnOnce(&mut Self),
);
/// Fills a [`Quad`] with the provided [`Background`].
fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);
/// Clears all of the recorded primitives in the [`Renderer`].
fn clear(&mut self);
}
/// 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: BorderRadius,
/// The border width of the [`Quad`].
pub border_width: f32,
/// The border color of the [`Quad`].
pub border_color: Color,
}
/// The border radi for the corners of a graphics primitive in the order:
/// top-left, top-right, bottom-right, bottom-left.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct BorderRadius([f32; 4]);
impl From<f32> for BorderRadius {
fn from(w: f32) -> Self {
Self([w; 4])
}
}
impl From<[f32; 4]> for BorderRadius {
fn from(radi: [f32; 4]) -> Self {
Self(radi)
}
}
impl From<BorderRadius> for [f32; 4] {
fn from(radi: BorderRadius) -> Self {
radi.0
}
}
/// 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,
}
}
}

82
core/src/renderer/null.rs Normal file
View file

@ -0,0 +1,82 @@
use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Vector};
use std::borrow::Cow;
/// A renderer that does nothing.
///
/// It can be useful if you are writing tests!
#[derive(Debug, Clone, Copy, Default)]
pub struct Null;
impl Null {
/// Creates a new [`Null`] renderer.
pub fn new() -> Null {
Null
}
}
impl Renderer for Null {
type Theme = ();
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
fn with_translation(
&mut self,
_translation: Vector,
_f: impl FnOnce(&mut Self),
) {
}
fn clear(&mut self) {}
fn fill_quad(
&mut self,
_quad: renderer::Quad,
_background: impl Into<Background>,
) {
}
}
impl text::Renderer for Null {
type Font = Font;
const ICON_FONT: Font = Font::SansSerif;
const CHECKMARK_ICON: char = '0';
const ARROW_DOWN_ICON: char = '0';
fn default_font(&self) -> Self::Font {
Font::SansSerif
}
fn default_size(&self) -> f32 {
16.0
}
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn measure(
&self,
_content: &str,
_size: f32,
_font: Font,
_bounds: Size,
) -> (f32, f32) {
(0.0, 20.0)
}
fn hit_test(
&self,
_contents: &str,
_size: f32,
_font: Self::Font,
_bounds: Size,
_point: Point,
_nearest_only: bool,
) -> Option<text::Hit> {
None
}
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
}

108
core/src/shell.rs Normal file
View file

@ -0,0 +1,108 @@
use crate::window;
/// A connection to the state of a shell.
///
/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application,
/// like publishing messages or invalidating the current layout.
///
/// [`Widget`]: crate::Widget
#[derive(Debug)]
pub struct Shell<'a, Message> {
messages: &'a mut Vec<Message>,
redraw_request: Option<window::RedrawRequest>,
is_layout_invalid: bool,
are_widgets_invalid: bool,
}
impl<'a, Message> Shell<'a, Message> {
/// Creates a new [`Shell`] with the provided buffer of messages.
pub fn new(messages: &'a mut Vec<Message>) -> Self {
Self {
messages,
redraw_request: None,
is_layout_invalid: false,
are_widgets_invalid: false,
}
}
/// Returns true if the [`Shell`] contains no published messages
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
/// Publish the given `Message` for an application to process it.
pub fn publish(&mut self, message: Message) {
self.messages.push(message);
}
/// Requests a new frame to be drawn at the given [`Instant`].
pub fn request_redraw(&mut self, request: window::RedrawRequest) {
match self.redraw_request {
None => {
self.redraw_request = Some(request);
}
Some(current) if request < current => {
self.redraw_request = Some(request);
}
_ => {}
}
}
/// Returns the requested [`Instant`] a redraw should happen, if any.
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
self.redraw_request
}
/// Returns whether the current layout is invalid or not.
pub fn is_layout_invalid(&self) -> bool {
self.is_layout_invalid
}
/// Invalidates the current application layout.
///
/// The shell will relayout the application widgets.
pub fn invalidate_layout(&mut self) {
self.is_layout_invalid = true;
}
/// Triggers the given function if the layout is invalid, cleaning it in the
/// process.
pub fn revalidate_layout(&mut self, f: impl FnOnce()) {
if self.is_layout_invalid {
self.is_layout_invalid = false;
f()
}
}
/// Returns whether the widgets of the current application have been
/// invalidated.
pub fn are_widgets_invalid(&self) -> bool {
self.are_widgets_invalid
}
/// Invalidates the current application widgets.
///
/// The shell will rebuild and relayout the widget tree.
pub fn invalidate_widgets(&mut self) {
self.are_widgets_invalid = true;
}
/// Merges the current [`Shell`] with another one by applying the given
/// function to the messages of the latter.
///
/// This method is useful for composition.
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
self.messages.extend(other.messages.drain(..).map(f));
if let Some(at) = other.redraw_request {
self.request_redraw(at);
}
self.is_layout_invalid =
self.is_layout_invalid || other.is_layout_invalid;
self.are_widgets_invalid =
self.are_widgets_invalid || other.are_widgets_invalid;
}
}

89
core/src/svg.rs Normal file
View file

@ -0,0 +1,89 @@
//! Load and draw vector graphics.
use crate::{Color, Hasher, Rectangle, Size};
use std::borrow::Cow;
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
/// A handle of Svg data.
#[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<Cow<'static, [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 a vectorial image.
#[derive(Clone, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
/// In-memory data
///
/// Can contain an SVG string or a gzip compressed data.
Bytes(Cow<'static, [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) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
}

111
core/src/text.rs Normal file
View file

@ -0,0 +1,111 @@
//! Draw and interact with text.
use crate::alignment;
use crate::{Color, Point, Rectangle, Size};
use std::borrow::Cow;
/// 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),
}
impl Hit {
/// Computes the cursor position of the [`Hit`] .
pub fn cursor(self) -> usize {
match self {
Self::CharOffset(i) => i,
}
}
}
/// A renderer capable of measuring and drawing [`Text`].
pub trait Renderer: crate::Renderer {
/// The font type used.
type Font: 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 [`Self::Font`].
fn default_font(&self) -> Self::Font;
/// Returns the default size of [`Text`].
fn default_size(&self) -> f32;
/// Measures the text in the given bounds and returns the minimum boundaries
/// that can fit the contents.
fn measure(
&self,
content: &str,
size: f32,
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: f32, 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>;
/// Loads a [`Self::Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
/// Draws the given [`Text`].
fn fill_text(&mut self, text: Text<'_, Self::Font>);
}

23
core/src/touch.rs Normal file
View file

@ -0,0 +1,23 @@
//! Build touch events.
use crate::Point;
/// A touch interaction.
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(missing_docs)]
pub enum Event {
/// A touch interaction was started.
FingerPressed { id: Finger, position: Point },
/// An on-going touch interaction was moved.
FingerMoved { id: Finger, position: Point },
/// A touch interaction was ended.
FingerLifted { id: Finger, position: Point },
/// A touch interaction was canceled.
FingerLost { id: Finger, position: Point },
}
/// A unique identifier representing a finger on a touch interaction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Finger(pub u64);

145
core/src/widget.rs Normal file
View file

@ -0,0 +1,145 @@
//! Create custom widgets and operate on them.
pub mod operation;
pub mod text;
pub mod tree;
mod id;
pub use id::Id;
pub use operation::Operation;
pub use text::Text;
pub use tree::Tree;
use crate::event::{self, Event};
use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Length, Point, Rectangle, Shell};
/// A component that displays information and allows interaction.
///
/// If you want to build your own widgets, you will need to implement this
/// trait.
///
/// # Examples
/// The repository has some [examples] showcasing how to implement a custom
/// widget:
///
/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using
/// [`lyon`].
/// - [`custom_widget`], a demonstration of how to build a custom widget that
/// draws a circle.
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Returns the width of the [`Widget`].
fn width(&self) -> Length;
/// Returns the height of the [`Widget`].
fn height(&self) -> Length;
/// Returns the [`layout::Node`] of the [`Widget`].
///
/// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node;
/// Draws the [`Widget`] using the associated `Renderer`.
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
);
/// Returns the [`Tag`] of the [`Widget`].
///
/// [`Tag`]: tree::Tag
fn tag(&self) -> tree::Tag {
tree::Tag::stateless()
}
/// Returns the [`State`] of the [`Widget`].
///
/// [`State`]: tree::State
fn state(&self) -> tree::State {
tree::State::None
}
/// Returns the state [`Tree`] of the children of the [`Widget`].
fn children(&self) -> Vec<Tree> {
Vec::new()
}
/// Reconciliates the [`Widget`] with the provided [`Tree`].
fn diff(&self, _tree: &mut Tree) {}
/// Applies an [`Operation`] to the [`Widget`].
fn operate(
&self,
_state: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
_operation: &mut dyn Operation<Message>,
) {
}
/// Processes a runtime [`Event`].
///
/// By default, it does nothing.
fn on_event(
&mut self,
_state: &mut Tree,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
}
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
///
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
_state: &Tree,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse::Interaction::Idle
}
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay<'a>(
&'a mut self,
_state: &'a mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'a, Message, Renderer>> {
None
}
}

43
core/src/widget/id.rs Normal file
View file

@ -0,0 +1,43 @@
use std::borrow;
use std::sync::atomic::{self, AtomicUsize};
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
/// The identifier of a generic widget.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(Internal);
impl Id {
/// Creates a custom [`Id`].
pub fn new(id: impl Into<borrow::Cow<'static, str>>) -> Self {
Self(Internal::Custom(id.into()))
}
/// Creates a unique [`Id`].
///
/// This function produces a different [`Id`] every time it is called.
pub fn unique() -> Self {
let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed);
Self(Internal::Unique(id))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Internal {
Unique(usize),
Custom(borrow::Cow<'static, str>),
}
#[cfg(test)]
mod tests {
use super::Id;
#[test]
fn unique_generates_different_ids() {
let a = Id::unique();
let b = Id::unique();
assert_ne!(a, b);
}
}

View file

@ -0,0 +1,112 @@
//! Query or update internal widget state.
pub mod focusable;
pub mod scrollable;
pub mod text_input;
pub use focusable::Focusable;
pub use scrollable::Scrollable;
pub use text_input::TextInput;
use crate::widget::Id;
use std::any::Any;
use std::fmt;
/// A piece of logic that can traverse the widget tree of an application in
/// order to query or update some widget state.
pub trait Operation<T> {
/// Operates on a widget that contains other widgets.
///
/// The `operate_on_children` function can be called to return control to
/// the widget tree and keep traversing it.
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
);
/// Operates on a widget that can be focused.
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
/// Operates on a widget that can be scrolled.
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
/// Operates on a widget that has text input.
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
/// Operates on a custom widget with some state.
fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {}
/// Finishes the [`Operation`] and returns its [`Outcome`].
fn finish(&self) -> Outcome<T> {
Outcome::None
}
}
/// The result of an [`Operation`].
pub enum Outcome<T> {
/// The [`Operation`] produced no result.
None,
/// The [`Operation`] produced some result.
Some(T),
/// The [`Operation`] needs to be followed by another [`Operation`].
Chain(Box<dyn Operation<T>>),
}
impl<T> fmt::Debug for Outcome<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "Outcome::None"),
Self::Some(output) => write!(f, "Outcome::Some({output:?})"),
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
}
}
}
/// Produces an [`Operation`] that applies the given [`Operation`] to the
/// children of a container with the given [`Id`].
pub fn scoped<T: 'static>(
target: Id,
operation: impl Operation<T> + 'static,
) -> impl Operation<T> {
struct ScopedOperation<Message> {
target: Id,
operation: Box<dyn Operation<Message>>,
}
impl<Message: 'static> Operation<Message> for ScopedOperation<Message> {
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
) {
if id == Some(&self.target) {
operate_on_children(self.operation.as_mut());
} else {
operate_on_children(self);
}
}
fn finish(&self) -> Outcome<Message> {
match self.operation.finish() {
Outcome::Chain(next) => {
Outcome::Chain(Box::new(ScopedOperation {
target: self.target.clone(),
operation: next,
}))
}
outcome => outcome,
}
}
}
ScopedOperation {
target,
operation: Box::new(operation),
}
}

View file

@ -0,0 +1,203 @@
//! Operate on widgets that can be focused.
use crate::widget::operation::{Operation, Outcome};
use crate::widget::Id;
/// The internal state of a widget that can be focused.
pub trait Focusable {
/// Returns whether the widget is focused or not.
fn is_focused(&self) -> bool;
/// Focuses the widget.
fn focus(&mut self);
/// Unfocuses the widget.
fn unfocus(&mut self);
}
/// A summary of the focusable widgets present on a widget tree.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Count {
/// The index of the current focused widget, if any.
pub focused: Option<usize>,
/// The total amount of focusable widgets.
pub total: usize,
}
/// Produces an [`Operation`] that focuses the widget with the given [`Id`].
pub fn focus<T>(target: Id) -> impl Operation<T> {
struct Focus {
target: Id,
}
impl<T> Operation<T> for Focus {
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
match id {
Some(id) if id == &self.target => {
state.focus();
}
_ => {
state.unfocus();
}
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
Focus { target }
}
/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
/// provided function to build a new [`Operation`].
pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T>
where
O: Operation<T> + 'static,
{
struct CountFocusable<O> {
count: Count,
next: fn(Count) -> O,
}
impl<T, O> Operation<T> for CountFocusable<O>
where
O: Operation<T> + 'static,
{
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
if state.is_focused() {
self.count.focused = Some(self.count.total);
}
self.count.total += 1;
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
fn finish(&self) -> Outcome<T> {
Outcome::Chain(Box::new((self.next)(self.count)))
}
}
CountFocusable {
count: Count::default(),
next: f,
}
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the previous focusable widget.
/// - if not found, focuses the last focusable widget.
pub fn focus_previous<T>() -> impl Operation<T> {
struct FocusPrevious {
count: Count,
current: usize,
}
impl<T> Operation<T> for FocusPrevious {
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
if self.count.total == 0 {
return;
}
match self.count.focused {
None if self.current == self.count.total - 1 => state.focus(),
Some(0) if self.current == 0 => state.unfocus(),
Some(0) => {}
Some(focused) if focused == self.current => state.unfocus(),
Some(focused) if focused - 1 == self.current => state.focus(),
_ => {}
}
self.current += 1;
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
count(|count| FocusPrevious { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the next focusable widget.
/// - if not found, focuses the first focusable widget.
pub fn focus_next<T>() -> impl Operation<T> {
struct FocusNext {
count: Count,
current: usize,
}
impl<T> Operation<T> for FocusNext {
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
match self.count.focused {
None if self.current == 0 => state.focus(),
Some(focused) if focused == self.current => state.unfocus(),
Some(focused) if focused + 1 == self.current => state.focus(),
_ => {}
}
self.current += 1;
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
count(|count| FocusNext { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget
/// and stores its ID. This ignores widgets that do not have an ID.
pub fn find_focused() -> impl Operation<Id> {
struct FindFocused {
focused: Option<Id>,
}
impl Operation<Id> for FindFocused {
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
if state.is_focused() && id.is_some() {
self.focused = id.cloned();
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
) {
operate_on_children(self)
}
fn finish(&self) -> Outcome<Id> {
if let Some(id) = &self.focused {
Outcome::Some(id.clone())
} else {
Outcome::None
}
}
}
FindFocused { focused: None }
}

View file

@ -0,0 +1,54 @@
//! Operate on widgets that can be scrolled.
use crate::widget::{Id, Operation};
/// The internal state of a widget that can be scrolled.
pub trait Scrollable {
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
fn snap_to(&mut self, offset: RelativeOffset);
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
/// the provided `percentage`.
pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
struct SnapTo {
target: Id,
offset: RelativeOffset,
}
impl<T> Operation<T> for SnapTo {
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
if Some(&self.target) == id {
state.snap_to(self.offset);
}
}
}
SnapTo { target, offset }
}
/// The amount of offset in each direction of a [`Scrollable`].
///
/// A value of `0.0` means start, while `1.0` means end.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct RelativeOffset {
/// The amount of horizontal offset
pub x: f32,
/// The amount of vertical offset
pub y: f32,
}
impl RelativeOffset {
/// A relative offset that points to the top-left of a [`Scrollable`].
pub const START: Self = Self { x: 0.0, y: 0.0 };
/// A relative offset that points to the bottom-right of a [`Scrollable`].
pub const END: Self = Self { x: 1.0, y: 1.0 };
}

View file

@ -0,0 +1,131 @@
//! Operate on widgets that have text input.
use crate::widget::operation::Operation;
use crate::widget::Id;
/// The internal state of a widget that has text input.
pub trait TextInput {
/// Moves the cursor of the text input to the front of the input text.
fn move_cursor_to_front(&mut self);
/// Moves the cursor of the text input to the end of the input text.
fn move_cursor_to_end(&mut self);
/// Moves the cursor of the text input to an arbitrary location.
fn move_cursor_to(&mut self, position: usize);
/// Selects all the content of the text input.
fn select_all(&mut self);
}
/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
/// front.
pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
struct MoveCursor {
target: Id,
}
impl<T> Operation<T> for MoveCursor {
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
match id {
Some(id) if id == &self.target => {
state.move_cursor_to_front();
}
_ => {}
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
MoveCursor { target }
}
/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
/// end.
pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
struct MoveCursor {
target: Id,
}
impl<T> Operation<T> for MoveCursor {
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
match id {
Some(id) if id == &self.target => {
state.move_cursor_to_end();
}
_ => {}
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
MoveCursor { target }
}
/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
/// provided position.
pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
struct MoveCursor {
target: Id,
position: usize,
}
impl<T> Operation<T> for MoveCursor {
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
match id {
Some(id) if id == &self.target => {
state.move_cursor_to(self.position);
}
_ => {}
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
MoveCursor { target, position }
}
/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`].
pub fn select_all<T>(target: Id) -> impl Operation<T> {
struct MoveCursor {
target: Id,
}
impl<T> Operation<T> for MoveCursor {
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
match id {
Some(id) if id == &self.target => {
state.select_all();
}
_ => {}
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
MoveCursor { target }
}

277
core/src/widget/text.rs Normal file
View file

@ -0,0 +1,277 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
use crate::renderer;
use crate::text;
use crate::widget::Tree;
use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
};
use std::borrow::Cow;
/// A paragraph of text.
#[allow(missing_debug_implementations)]
pub struct Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
content: Cow<'a, str>,
size: Option<f32>,
width: Length,
height: Length,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Renderer> Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
/// Create a new fragment of [`Text`] with the given contents.
pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
Text {
content: content.into(),
size: None,
font: None,
width: Length::Shrink,
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
style: Default::default(),
}
}
/// Sets the size of the [`Text`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = Some(size.into().0);
self
}
/// Sets the [`Font`] of the [`Text`].
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
/// Sets the style of the [`Text`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
/// Sets the width of the [`Text`] boundaries.
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Text`] boundaries.
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the [`alignment::Horizontal`] of the [`Text`].
pub fn horizontal_alignment(
mut self,
alignment: alignment::Horizontal,
) -> Self {
self.horizontal_alignment = alignment;
self
}
/// Sets the [`alignment::Vertical`] of the [`Text`].
pub fn vertical_alignment(
mut self,
alignment: alignment::Vertical,
) -> Self {
self.vertical_alignment = alignment;
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
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_else(|| renderer.default_size());
let bounds = limits.max();
let (width, height) = renderer.measure(
&self.content,
size,
self.font.unwrap_or_else(|| renderer.default_font()),
bounds,
);
let size = limits.resolve(Size::new(width, height));
layout::Node::new(size)
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
draw(
renderer,
style,
layout,
&self.content,
self.size,
self.font,
theme.appearance(self.style),
self.horizontal_alignment,
self.vertical_alignment,
);
}
}
/// Draws text using the same logic as the [`Text`] widget.
///
/// Specifically:
///
/// * 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,
size: Option<f32>,
font: Option<Renderer::Font>,
appearance: Appearance,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
) where
Renderer: text::Renderer,
{
let bounds = layout.bounds();
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.center_x(),
alignment::Horizontal::Right => bounds.x + bounds.width,
};
let y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.center_y(),
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
renderer.fill_text(crate::Text {
content,
size: size.unwrap_or_else(|| renderer.default_size()),
bounds: Rectangle { x, y, ..bounds },
color: appearance.color.unwrap_or(style.text_color),
font: font.unwrap_or_else(|| renderer.default_font()),
horizontal_alignment,
vertical_alignment,
});
}
impl<'a, Message, Renderer> From<Text<'a, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(text: Text<'a, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(text)
}
}
impl<'a, Renderer> Clone for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn clone(&self) -> Self {
Self {
content: self.content.clone(),
size: self.size,
width: self.width,
height: self.height,
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
font: self.font,
style: self.style,
}
}
}
impl<'a, Renderer> From<&'a str> for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(content: &'a str) -> Self {
Self::new(content)
}
}
impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
where
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(content: &'a str) -> Self {
Text::from(content).into()
}
}
/// The style sheet of some text.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Copy;
/// Produces the [`Appearance`] of some text.
fn appearance(&self, style: Self::Style) -> Appearance;
}
/// The apperance of some text.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
/// The [`Color`] of the text.
///
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}

187
core/src/widget/tree.rs Normal file
View file

@ -0,0 +1,187 @@
//! Store internal widget state in a state tree to ensure continuity.
use crate::Widget;
use std::any::{self, Any};
use std::borrow::Borrow;
use std::fmt;
/// A persistent state widget tree.
///
/// A [`Tree`] is normally associated with a specific widget in the widget tree.
#[derive(Debug)]
pub struct Tree {
/// The tag of the [`Tree`].
pub tag: Tag,
/// The [`State`] of the [`Tree`].
pub state: State,
/// The children of the root widget of the [`Tree`].
pub children: Vec<Tree>,
}
impl Tree {
/// Creates an empty, stateless [`Tree`] with no children.
pub fn empty() -> Self {
Self {
tag: Tag::stateless(),
state: State::None,
children: Vec::new(),
}
}
/// Creates a new [`Tree`] for the provided [`Widget`].
pub fn new<'a, Message, Renderer>(
widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
) -> Self
where
Renderer: crate::Renderer,
{
let widget = widget.borrow();
Self {
tag: widget.tag(),
state: widget.state(),
children: widget.children(),
}
}
/// Reconciliates the current tree with the provided [`Widget`].
///
/// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the
/// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
///
/// Otherwise, the whole [`Tree`] is recreated.
///
/// [`Widget::diff`]: crate::Widget::diff
pub fn diff<'a, Message, Renderer>(
&mut self,
new: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
) where
Renderer: crate::Renderer,
{
if self.tag == new.borrow().tag() {
new.borrow().diff(self)
} else {
*self = Self::new(new);
}
}
/// Reconciliates the children of the tree with the provided list of widgets.
pub fn diff_children<'a, Message, Renderer>(
&mut self,
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
) where
Renderer: crate::Renderer,
{
self.diff_children_custom(
new_children,
|tree, widget| tree.diff(widget.borrow()),
|widget| Self::new(widget.borrow()),
)
}
/// Reconciliates the children of the tree with the provided list of widgets using custom
/// logic both for diffing and creating new widget state.
pub fn diff_children_custom<T>(
&mut self,
new_children: &[T],
diff: impl Fn(&mut Tree, &T),
new_state: impl Fn(&T) -> Self,
) {
if self.children.len() > new_children.len() {
self.children.truncate(new_children.len());
}
for (child_state, new) in
self.children.iter_mut().zip(new_children.iter())
{
diff(child_state, new);
}
if self.children.len() < new_children.len() {
self.children.extend(
new_children[self.children.len()..].iter().map(new_state),
);
}
}
}
/// The identifier of some widget state.
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Tag(any::TypeId);
impl Tag {
/// Creates a [`Tag`] for a state of type `T`.
pub fn of<T>() -> Self
where
T: 'static,
{
Self(any::TypeId::of::<T>())
}
/// Creates a [`Tag`] for a stateless widget.
pub fn stateless() -> Self {
Self::of::<()>()
}
}
/// The internal [`State`] of a widget.
pub enum State {
/// No meaningful internal state.
None,
/// Some meaningful internal state.
Some(Box<dyn Any>),
}
impl State {
/// Creates a new [`State`].
pub fn new<T>(state: T) -> Self
where
T: 'static,
{
State::Some(Box::new(state))
}
/// Downcasts the [`State`] to `T` and returns a reference to it.
///
/// # Panics
/// This method will panic if the downcast fails or the [`State`] is [`State::None`].
pub fn downcast_ref<T>(&self) -> &T
where
T: 'static,
{
match self {
State::None => panic!("Downcast on stateless state"),
State::Some(state) => {
state.downcast_ref().expect("Downcast widget state")
}
}
}
/// Downcasts the [`State`] to `T` and returns a mutable reference to it.
///
/// # Panics
/// This method will panic if the downcast fails or the [`State`] is [`State::None`].
pub fn downcast_mut<T>(&mut self) -> &mut T
where
T: 'static,
{
match self {
State::None => panic!("Downcast on stateless state"),
State::Some(state) => {
state.downcast_mut().expect("Downcast widget state")
}
}
}
}
impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "State::None"),
Self::Some(_) => write!(f, "State::Some"),
}
}
}

10
core/src/window.rs Normal file
View file

@ -0,0 +1,10 @@
//! Build window-based GUI applications.
mod event;
mod mode;
mod redraw_request;
mod user_attention;
pub use event::Event;
pub use mode::Mode;
pub use redraw_request::RedrawRequest;
pub use user_attention::UserAttention;

58
core/src/window/event.rs Normal file
View file

@ -0,0 +1,58 @@
use crate::time::Instant;
use std::path::PathBuf;
/// A window-related event.
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Event {
/// A window was moved.
Moved {
/// The new logical x location of the window
x: i32,
/// The new logical y location of the window
y: i32,
},
/// A window was resized.
Resized {
/// The new logical width of the window
width: u32,
/// The new logical height of the window
height: u32,
},
/// A window redraw was requested.
///
/// The [`Instant`] contains the current time.
RedrawRequested(Instant),
/// The user has requested for the window to close.
///
/// Usually, you will want to terminate the execution whenever this event
/// occurs.
CloseRequested,
/// A window was focused.
Focused,
/// A window was unfocused.
Unfocused,
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted
/// for each file separately.
FileHovered(PathBuf),
/// A file has beend dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted
/// for each file separately.
FileDropped(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `FilesHoveredLeft` event triggered even if
/// multiple files were hovered.
FilesHoveredLeft,
}

12
core/src/window/mode.rs Normal file
View file

@ -0,0 +1,12 @@
/// The mode of a window-based application.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
/// The application appears in its own window.
Windowed,
/// The application takes the whole screen of its current monitor.
Fullscreen,
/// The application is hidden
Hidden,
}

View file

@ -0,0 +1,38 @@
use crate::time::Instant;
/// A request to redraw a window.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum RedrawRequest {
/// Redraw the next frame.
NextFrame,
/// Redraw at the given time.
At(Instant),
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
#[test]
fn ordering() {
let now = Instant::now();
let later = now + Duration::from_millis(10);
assert_eq!(RedrawRequest::NextFrame, RedrawRequest::NextFrame);
assert_eq!(RedrawRequest::At(now), RedrawRequest::At(now));
assert!(RedrawRequest::NextFrame < RedrawRequest::At(now));
assert!(RedrawRequest::At(now) > RedrawRequest::NextFrame);
assert!(RedrawRequest::At(now) < RedrawRequest::At(later));
assert!(RedrawRequest::At(later) > RedrawRequest::At(now));
assert!(RedrawRequest::NextFrame <= RedrawRequest::NextFrame);
assert!(RedrawRequest::NextFrame <= RedrawRequest::At(now));
assert!(RedrawRequest::At(now) >= RedrawRequest::NextFrame);
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
}
}

View file

@ -0,0 +1,21 @@
/// The type of user attention to request.
///
/// ## Platform-specific
///
/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`].
///
/// [`Critical`]: Self::Critical
/// [`Informational`]: Self::Informational
#[derive(Debug, Clone, Copy)]
pub enum UserAttention {
/// ## Platform-specific
///
/// - **macOS:** Bounces the dock icon until the application is in focus.
/// - **Windows:** Flashes both the window and the taskbar button until the application is in focus.
Critical,
/// ## Platform-specific
///
/// - **macOS:** Bounces the dock icon once.
/// - **Windows:** Flashes the taskbar button until the application is in focus.
Informational,
}