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

View file

@ -3,28 +3,6 @@ use iced_futures::MaybeSend;
use std::fmt;
/// 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) {}
}
/// A clipboard action to be performed by some [`Command`].
///
/// [`Command`]: crate::Command

View file

@ -28,7 +28,9 @@ impl<T> Command<T> {
}
/// Creates a [`Command`] that performs a [`widget::Operation`].
pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self {
pub fn widget(
operation: impl iced_core::widget::Operation<T> + 'static,
) -> Self {
Self(iced_futures::Command::single(Action::Widget(
widget::Action::new(operation),
)))

View file

@ -1,5 +1,5 @@
#![allow(missing_docs)]
use crate::time;
use crate::core::time;
use std::collections::VecDeque;

View file

@ -1,583 +0,0 @@
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:
///
/// ```
/// # mod counter {
/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn view(&mut self) -> Text {
/// # Text::new("")
/// # }
/// # }
/// # }
/// #
/// # mod iced_wgpu {
/// # pub use iced_native::renderer::Null as Renderer;
/// # }
/// #
/// # use counter::Counter;
/// #
/// # struct ManyCounters {
/// # counters: Vec<Counter>,
/// # }
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
/// use iced_native::Element;
/// use iced_native::widget::Row;
/// use iced_wgpu::Renderer;
///
/// impl ManyCounters {
/// pub fn view(&mut self) -> Row<Message, Renderer> {
/// // 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)
}
}

View file

@ -1,78 +0,0 @@
//! 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_native::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,
}
}
}

View file

@ -1,13 +0,0 @@
/// 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()
}
}

View file

@ -1,174 +0,0 @@
//! 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);
}

View file

@ -1,65 +0,0 @@
//! 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

@ -1,202 +0,0 @@
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.

View file

@ -1,232 +0,0 @@
//! 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)
}

View file

@ -1,163 +0,0 @@
#![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),
)
}
}

View file

@ -1,91 +0,0 @@
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

@ -42,32 +42,19 @@
clippy::useless_conversion
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod clipboard;
pub mod command;
pub mod event;
pub mod font;
pub mod image;
pub mod keyboard;
pub mod layout;
pub mod mouse;
pub mod overlay;
pub mod program;
pub mod renderer;
pub mod subscription;
pub mod svg;
pub mod system;
pub mod text;
pub mod touch;
pub mod user_interface;
pub mod widget;
pub mod window;
mod element;
mod hasher;
mod runtime;
mod shell;
// We disable debug capabilities on release builds unless the `debug` feature
// is explicitly enabled.
@ -78,35 +65,13 @@ mod debug;
#[path = "debug/null.rs"]
mod debug;
pub use iced_core::alignment;
pub use iced_core::gradient;
pub use iced_core::time;
pub use iced_core::{
color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
Point, Rectangle, Size, Vector,
};
pub use iced_futures::{executor, futures};
pub use iced_style::application;
pub use iced_style::theme;
pub use iced_core as core;
pub use iced_futures as futures;
#[doc(no_inline)]
pub use executor::Executor;
pub use clipboard::Clipboard;
pub use command::Command;
pub use debug::Debug;
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 overlay::Overlay;
pub use program::Program;
pub use renderer::Renderer;
pub use runtime::Runtime;
pub use shell::Shell;
pub use subscription::Subscription;
pub use theme::Theme;
pub use user_interface::UserInterface;
pub use widget::Widget;

View file

@ -1,6 +0,0 @@
//! Track mouse events.
pub mod click;
pub use click::Click;
pub use iced_core::mouse::*;

View file

@ -1,76 +0,0 @@
//! 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)
}
}

View file

@ -1,125 +0,0 @@
//! Display interactive elements on top of other widgets.
mod element;
mod group;
pub mod menu;
pub use element::Element;
pub use group::Group;
pub use menu::Menu;
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())
}

View file

@ -1,270 +0,0 @@
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)
}
}

View file

@ -1,174 +0,0 @@
use iced_core::{Point, Rectangle, Size};
use crate::event;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Event, Layout, Overlay, Shell};
/// 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()
}
}

View file

@ -1,519 +0,0 @@
//! Build and show dropdown menus.
use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
use crate::widget::container::{self, Container};
use crate::widget::scrollable::{self, Scrollable};
use crate::widget::Tree;
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point,
Rectangle, Shell, Size, Vector, Widget,
};
pub use iced_style::menu::{Appearance, StyleSheet};
/// A list of selectable options.
#[allow(missing_debug_implementations)]
pub struct Menu<'a, T, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
width: f32,
padding: Padding,
text_size: Option<f32>,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, T, Renderer> Menu<'a, T, Renderer>
where
T: ToString + Clone,
Renderer: text::Renderer + 'a,
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
{
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
/// the message to produced when an option is selected.
pub fn new(
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
) -> Self {
Menu {
state,
options,
hovered_option,
last_selection,
width: 0.0,
padding: Padding::ZERO,
text_size: None,
font: None,
style: Default::default(),
}
}
/// Sets the width of the [`Menu`].
pub fn width(mut self, width: f32) -> Self {
self.width = width;
self
}
/// Sets the [`Padding`] of the [`Menu`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the text size of the [`Menu`].
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
self.text_size = Some(text_size.into().0);
self
}
/// Sets the font of the [`Menu`].
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
/// Sets the style of the [`Menu`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
/// Turns the [`Menu`] into an overlay [`Element`] at the given target
/// position.
///
/// The `target_height` will be used to display the menu either on top
/// of the target or under it, depending on the screen position and the
/// dimensions of the [`Menu`].
pub fn overlay<Message: 'a>(
self,
position: Point,
target_height: f32,
) -> overlay::Element<'a, Message, Renderer> {
overlay::Element::new(
position,
Box::new(Overlay::new(self, target_height)),
)
}
}
/// The local state of a [`Menu`].
#[derive(Debug)]
pub struct State {
tree: Tree,
}
impl State {
/// Creates a new [`State`] for a [`Menu`].
pub fn new() -> Self {
Self {
tree: Tree::empty(),
}
}
}
impl Default for State {
fn default() -> Self {
Self::new()
}
}
struct Overlay<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
state: &'a mut Tree,
container: Container<'a, Message, Renderer>,
width: f32,
target_height: f32,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Message, Renderer> Overlay<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a,
Renderer: text::Renderer,
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
{
pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self
where
T: Clone + ToString,
{
let Menu {
state,
options,
hovered_option,
last_selection,
width,
padding,
font,
text_size,
style,
} = menu;
let container = Container::new(Scrollable::new(List {
options,
hovered_option,
last_selection,
font,
text_size,
padding,
style: style.clone(),
}));
state.tree.diff(&container as &dyn Widget<_, _>);
Self {
state: &mut state.tree,
container,
width,
target_height,
style,
}
}
}
impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
fn layout(
&self,
renderer: &Renderer,
bounds: Size,
position: Point,
) -> layout::Node {
let space_below = bounds.height - (position.y + self.target_height);
let space_above = position.y;
let limits = layout::Limits::new(
Size::ZERO,
Size::new(
bounds.width - position.x,
if space_below > space_above {
space_below
} else {
space_above
},
),
)
.width(self.width);
let mut node = self.container.layout(renderer, &limits);
node.move_to(if space_below > space_above {
position + Vector::new(0.0, self.target_height)
} else {
position - Vector::new(0.0, node.size().height)
});
node
}
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.container.on_event(
self.state,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.container.mouse_interaction(
self.state,
layout,
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
let appearance = theme.appearance(&self.style);
let bounds = layout.bounds();
renderer.fill_quad(
renderer::Quad {
bounds,
border_color: appearance.border_color,
border_width: appearance.border_width,
border_radius: appearance.border_radius.into(),
},
appearance.background,
);
self.container.draw(
self.state,
renderer,
theme,
style,
layout,
cursor_position,
&bounds,
);
}
}
struct List<'a, T, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
padding: Padding,
text_size: Option<f32>,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for List<'a, T, Renderer>
where
T: Clone + ToString,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
Length::Fill
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
use std::f32;
let limits = limits.width(Length::Fill).height(Length::Shrink);
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
let size = {
let intrinsic = Size::new(
0.0,
(text_size * 1.2 + self.padding.vertical())
* self.options.len() as f32,
);
limits.resolve(intrinsic)
};
layout::Node::new(size)
}
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 {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
*self.last_selection = Some(option.clone());
}
}
}
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
let text_size = self
.text_size
.unwrap_or_else(|| renderer.default_size());
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
/ (text_size * 1.2 + self.padding.vertical()))
as usize,
);
}
}
Event::Touch(touch::Event::FingerPressed { .. }) => {
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
let text_size = self
.text_size
.unwrap_or_else(|| renderer.default_size());
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
/ (text_size * 1.2 + self.padding.vertical()))
as usize,
);
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
*self.last_selection = Some(option.clone());
}
}
}
}
_ => {}
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let is_mouse_over = layout.bounds().contains(cursor_position);
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
viewport: &Rectangle,
) {
let appearance = theme.appearance(&self.style);
let bounds = layout.bounds();
let text_size =
self.text_size.unwrap_or_else(|| renderer.default_size());
let option_height =
(text_size * 1.2 + self.padding.vertical()) as usize;
let offset = viewport.y - bounds.y;
let start = (offset / option_height as f32) as usize;
let end =
((offset + viewport.height) / option_height as f32).ceil() as usize;
let visible_options = &self.options[start..end.min(self.options.len())];
for (i, option) in visible_options.iter().enumerate() {
let i = start + i;
let is_selected = *self.hovered_option == Some(i);
let bounds = Rectangle {
x: bounds.x,
y: bounds.y + (option_height * i) as f32,
width: bounds.width,
height: text_size * 1.2 + self.padding.vertical(),
};
if is_selected {
renderer.fill_quad(
renderer::Quad {
bounds,
border_color: Color::TRANSPARENT,
border_width: 0.0,
border_radius: appearance.border_radius.into(),
},
appearance.selected_background,
);
}
renderer.fill_text(Text {
content: &option.to_string(),
bounds: Rectangle {
x: bounds.x + self.padding.left,
y: bounds.center_y(),
width: f32::INFINITY,
..bounds
},
size: text_size,
font: self.font.unwrap_or_else(|| renderer.default_font()),
color: if is_selected {
appearance.selected_text_color
} else {
appearance.text_color
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
});
}
}
}
impl<'a, T, Message, Renderer> From<List<'a, T, Renderer>>
for Element<'a, Message, Renderer>
where
T: ToString + Clone,
Message: 'a,
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(list: List<'a, T, Renderer>) -> Self {
Element::new(list)
}
}

View file

@ -1,6 +1,8 @@
//! Build interactive programs using The Elm Architecture.
use crate::text;
use crate::{Command, Element, Renderer};
use crate::Command;
use iced_core::text;
use iced_core::{Element, Renderer};
mod state;

View file

@ -1,9 +1,9 @@
use crate::application;
use crate::event::{self, Event};
use crate::mouse;
use crate::renderer;
use crate::core::event::{self, Event};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::{Clipboard, Point, Size};
use crate::user_interface::{self, UserInterface};
use crate::{Clipboard, Command, Debug, Point, Program, Size};
use crate::{Command, Debug, Program};
/// The execution state of a [`Program`]. It leverages caching, event
/// processing, and rendering primitive storage.
@ -22,7 +22,6 @@ where
impl<P> State<P>
where
P: Program + 'static,
<P::Renderer as crate::Renderer>::Theme: application::StyleSheet,
{
/// Creates a new [`State`] with the provided [`Program`], initializing its
/// primitive with the given logical bounds and renderer.
@ -91,7 +90,7 @@ where
bounds: Size,
cursor_position: Point,
renderer: &mut P::Renderer,
theme: &<P::Renderer as crate::Renderer>::Theme,
theme: &<P::Renderer as iced_core::Renderer>::Theme,
style: &renderer::Style,
clipboard: &mut dyn Clipboard,
debug: &mut Debug,
@ -182,10 +181,7 @@ fn build_user_interface<'a, P: Program>(
renderer: &mut P::Renderer,
size: Size,
debug: &mut Debug,
) -> UserInterface<'a, P::Message, P::Renderer>
where
<P::Renderer as crate::Renderer>::Theme: application::StyleSheet,
{
) -> UserInterface<'a, P::Message, P::Renderer> {
debug.view_started();
let view = program.view();
debug.view_finished();

View file

@ -1,98 +0,0 @@
//! 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,
}
}
}

View file

@ -1,82 +0,0 @@
use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Theme, 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 = 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>) {}
}

View file

@ -1,6 +1,6 @@
//! Run commands and subscriptions.
use crate::event::{self, Event};
use crate::Hasher;
use iced_core::event::{self, Event};
use iced_core::Hasher;
/// A native runtime with a generic executor and receiver of results.
///

View file

@ -1,108 +0,0 @@
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;
}
}

View file

@ -1,10 +1,9 @@
//! Listen to external events in your application.
use crate::event::{self, Event};
use crate::window;
use crate::Hasher;
use iced_futures::futures::{self, Future, Stream};
use iced_futures::{BoxStream, MaybeSend};
use crate::core::event::{self, Event};
use crate::core::window;
use crate::core::Hasher;
use crate::futures::futures::{self, Future, Stream};
use crate::futures::{BoxStream, MaybeSend};
use std::hash::Hash;
@ -144,7 +143,9 @@ where
///
/// ```
/// use iced_native::subscription::{self, Subscription};
/// use iced_native::futures::channel::mpsc;
/// use iced_native::futures::futures;
///
/// use futures::channel::mpsc;
///
/// pub enum Event {
/// Ready(mpsc::Sender<Input>),
@ -174,7 +175,7 @@ where
/// (Some(Event::Ready(sender)), State::Ready(receiver))
/// }
/// State::Ready(mut receiver) => {
/// use iced_native::futures::StreamExt;
/// use futures::StreamExt;
///
/// // Read next input sent from `Application`
/// let input = receiver.select_next_some().await;

View file

@ -1,89 +0,0 @@
//! 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);
}

View file

@ -1,111 +0,0 @@
//! 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>);
}

View file

@ -1,23 +0,0 @@
//! 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);

View file

@ -1,14 +1,12 @@
//! Implement your own event loop to drive a user interface.
use crate::application;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::window;
use crate::{
Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
};
use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{Clipboard, Point, Rectangle, Size, Vector};
use crate::core::{Element, Layout, Shell};
/// A set of interactive graphical elements with a specific [`Layout`].
///
@ -34,8 +32,7 @@ pub struct UserInterface<'a, Message, Renderer> {
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: application::StyleSheet,
Renderer: iced_core::Renderer,
{
/// Builds a user interface for an [`Element`].
///
@ -48,24 +45,21 @@ where
/// is naive way to set up our application loop:
///
/// ```no_run
/// use iced_native::Size;
/// use iced_native::user_interface::{self, UserInterface};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
/// # pub use iced_native::renderer::Null as Renderer;
/// # pub use iced_native::core::renderer::Null as Renderer;
/// # }
/// #
/// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn new() -> Self { Counter }
/// # pub fn view(&self) -> Column<(), Renderer> {
/// # Column::new()
/// # }
/// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
/// # pub fn update(&mut self, _: ()) {}
/// # }
/// use iced_native::core::Size;
/// use iced_native::user_interface::{self, UserInterface};
/// use iced_wgpu::Renderer;
///
/// // Initialization
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
@ -124,25 +118,21 @@ where
/// completing [the previous example](#example):
///
/// ```no_run
/// use iced_native::{clipboard, Size, Point};
/// use iced_native::user_interface::{self, UserInterface};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
/// # pub use iced_native::renderer::Null as Renderer;
/// # pub use iced_native::core::renderer::Null as Renderer;
/// # }
/// #
/// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn new() -> Self { Counter }
/// # pub fn view(&self) -> Column<(), Renderer> {
/// # Column::new()
/// # }
/// # pub fn update(&mut self, message: ()) {}
/// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
/// # pub fn update(&mut self, _: ()) {}
/// # }
/// use iced_native::core::{clipboard, Size, Point};
/// use iced_native::user_interface::{self, UserInterface};
/// use iced_wgpu::Renderer;
///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
@ -357,27 +347,24 @@ where
/// [completing the last example](#example-1):
///
/// ```no_run
/// use iced_native::clipboard;
/// use iced_native::renderer;
/// use iced_native::user_interface::{self, UserInterface};
/// use iced_native::{Size, Point, Theme};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
/// # pub use iced_native::renderer::Null as Renderer;
/// # pub use iced_native::core::renderer::Null as Renderer;
/// # pub type Theme = ();
/// # }
/// #
/// # use iced_native::widget::Column;
/// #
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn new() -> Self { Counter }
/// # pub fn view(&self) -> Column<(), Renderer> {
/// # Column::new()
/// # }
/// # pub fn update(&mut self, message: ()) {}
/// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() }
/// # pub fn update(&mut self, _: ()) {}
/// # }
/// use iced_native::core::clipboard;
/// use iced_native::core::renderer;
/// use iced_native::core::{Element, Size, Point};
/// use iced_native::user_interface::{self, UserInterface};
/// use iced_wgpu::{Renderer, Theme};
///
/// let mut counter = Counter::new();
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
@ -386,6 +373,7 @@ where
/// let mut clipboard = clipboard::Null;
/// let mut events = Vec::new();
/// let mut messages = Vec::new();
/// let mut theme = Theme::default();
///
/// loop {
/// // Obtain system events...
@ -407,7 +395,7 @@ where
/// );
///
/// // Draw the user interface
/// let mouse_cursor = user_interface.draw(&mut renderer, &Theme::default(), &renderer::Style::default(), cursor_position);
/// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position);
///
/// cache = user_interface.into_cache();
///

View file

@ -11,212 +11,6 @@
//! source of inspiration.
//!
//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
pub mod helpers;
pub mod image;
pub mod operation;
pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
pub mod radio;
pub mod row;
pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
pub mod tree;
pub mod vertical_slider;
mod action;
mod id;
#[doc(no_inline)]
pub use button::Button;
#[doc(no_inline)]
pub use checkbox::Checkbox;
#[doc(no_inline)]
pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
pub use helpers::*;
#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
#[doc(no_inline)]
pub use row::Row;
#[doc(no_inline)]
pub use rule::Rule;
#[doc(no_inline)]
pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
#[doc(no_inline)]
pub use space::Space;
#[doc(no_inline)]
pub use svg::Svg;
#[doc(no_inline)]
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
#[doc(no_inline)]
pub use tree::Tree;
#[doc(no_inline)]
pub use vertical_slider::VerticalSlider;
pub use action::Action;
pub use id::Id;
pub use operation::Operation;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Layout, 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
}
}

View file

@ -1,8 +1,7 @@
use crate::widget::operation::{
use iced_core::widget::operation::{
self, Focusable, Operation, Scrollable, TextInput,
};
use crate::widget::Id;
use iced_core::widget::Id;
use iced_futures::MaybeSend;
use std::any::Any;

View file

@ -1,455 +0,0 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::tree::{self, Tree};
use crate::widget::Operation;
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Vector, Widget,
};
pub use iced_style::button::{Appearance, StyleSheet};
/// A generic widget that produces a message when pressed.
///
/// ```
/// # type Button<'a, Message> =
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
///
/// let button = Button::new("Press me!").on_press(Message::ButtonPressed);
/// ```
///
/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
/// be disabled:
///
/// ```
/// # type Button<'a, Message> =
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
///
/// fn disabled_button<'a>() -> Button<'a, Message> {
/// Button::new("I'm disabled!")
/// }
///
/// fn enabled_button<'a>() -> Button<'a, Message> {
/// disabled_button().on_press(Message::ButtonPressed)
/// }
/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
width: Length,
height: Length,
padding: Padding,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Button`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Button {
content: content.into(),
on_press: None,
width: Length::Shrink,
height: Length::Shrink,
padding: Padding::new(5.0),
style: <Renderer::Theme as StyleSheet>::Style::default(),
}
}
/// Sets the width of the [`Button`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Button`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the [`Padding`] of the [`Button`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
///
/// Unless `on_press` is called, the [`Button`] will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
}
/// Sets the style variant of this [`Button`].
pub fn style(
mut self,
style: <Renderer::Theme as StyleSheet>::Style,
) -> Self {
self.style = style;
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
}
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(std::slice::from_ref(&self.content))
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
self.width,
self.height,
self.padding,
|renderer, limits| {
self.content.as_widget().layout(renderer, limits)
},
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
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<'_, Message>,
) -> event::Status {
if let event::Status::Captured = self.content.as_widget_mut().on_event(
&mut tree.children[0],
event.clone(),
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
) {
return event::Status::Captured;
}
update(
event,
layout,
cursor_position,
shell,
&self.on_press,
|| tree.state.downcast_mut::<State>(),
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let styling = draw(
renderer,
bounds,
cursor_position,
self.on_press.is_some(),
theme,
&self.style,
|| tree.state.downcast_ref::<State>(),
);
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&renderer::Style {
text_color: styling.text_color,
},
content_layout,
cursor_position,
&bounds,
);
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position, self.on_press.is_some())
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
)
}
}
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: Clone + 'a,
Renderer: crate::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(button: Button<'a, Message, Renderer>) -> Self {
Self::new(button)
}
}
/// The local state of a [`Button`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_pressed: bool,
}
impl State {
/// Creates a new [`State`].
pub fn new() -> State {
State::default()
}
}
/// Processes the given [`Event`] and updates the [`State`] of a [`Button`]
/// accordingly.
pub fn update<'a, Message: Clone>(
event: Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
on_press: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if on_press.is_some() {
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
let state = state();
state.is_pressed = true;
return event::Status::Captured;
}
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = on_press.clone() {
let state = state();
if state.is_pressed {
state.is_pressed = false;
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
shell.publish(on_press);
}
return event::Status::Captured;
}
}
}
Event::Touch(touch::Event::FingerLost { .. }) => {
let state = state();
state.is_pressed = false;
}
_ => {}
}
event::Status::Ignored
}
/// Draws a [`Button`].
pub fn draw<'a, Renderer: crate::Renderer>(
renderer: &mut Renderer,
bounds: Rectangle,
cursor_position: Point,
is_enabled: bool,
style_sheet: &dyn StyleSheet<
Style = <Renderer::Theme as StyleSheet>::Style,
>,
style: &<Renderer::Theme as StyleSheet>::Style,
state: impl FnOnce() -> &'a State,
) -> Appearance
where
Renderer::Theme: StyleSheet,
{
let is_mouse_over = bounds.contains(cursor_position);
let styling = if !is_enabled {
style_sheet.disabled(style)
} else if is_mouse_over {
let state = state();
if state.is_pressed {
style_sheet.pressed(style)
} else {
style_sheet.hovered(style)
}
} else {
style_sheet.active(style)
};
if styling.background.is_some() || styling.border_width > 0.0 {
if styling.shadow_offset != Vector::default() {
// TODO: Implement proper shadow support
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x + styling.shadow_offset.x,
y: bounds.y + styling.shadow_offset.y,
..bounds
},
border_radius: styling.border_radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color([0.0, 0.0, 0.0, 0.5].into()),
);
}
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: styling.border_radius.into(),
border_width: styling.border_width,
border_color: styling.border_color,
},
styling
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
}
styling
}
/// Computes the layout of a [`Button`].
pub fn layout<Renderer>(
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
height: Length,
padding: Padding,
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
let mut content = layout_content(renderer, &limits.pad(padding));
let padding = padding.fit(content.size(), limits.max());
let size = limits.pad(padding).resolve(content.size()).pad(padding);
content.move_to(Point::new(padding.left, padding.top));
layout::Node::with_children(size, vec![content])
}
/// Returns the [`mouse::Interaction`] of a [`Button`].
pub fn mouse_interaction(
layout: Layout<'_>,
cursor_position: Point,
is_enabled: bool,
) -> mouse::Interaction {
let is_mouse_over = layout.bounds().contains(cursor_position);
if is_mouse_over && is_enabled {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}

View file

@ -1,321 +0,0 @@
//! Show toggle controls using checkboxes.
use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::touch;
use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle,
Shell, Widget,
};
pub use iced_style::checkbox::{Appearance, StyleSheet};
/// The icon in a [`Checkbox`].
#[derive(Debug, Clone, PartialEq)]
pub struct Icon<Font> {
/// Font that will be used to display the `code_point`,
pub font: Font,
/// The unicode code point that will be used as the icon.
pub code_point: char,
/// Font size of the content.
pub size: Option<f32>,
}
/// A box that can be checked.
///
/// # Example
///
/// ```
/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// CheckboxToggled(bool),
/// }
///
/// let is_checked = true;
///
/// Checkbox::new("Toggle me!", is_checked, Message::CheckboxToggled);
/// ```
///
/// ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Checkbox<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
label: String,
width: Length,
size: f32,
spacing: f32,
text_size: Option<f32>,
font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
/// The default size of a [`Checkbox`].
const DEFAULT_SIZE: f32 = 20.0;
/// The default spacing of a [`Checkbox`].
const DEFAULT_SPACING: f32 = 15.0;
/// Creates a new [`Checkbox`].
///
/// It expects:
/// * a boolean describing whether the [`Checkbox`] is checked or not
/// * the label of the [`Checkbox`]
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self
where
F: 'a + Fn(bool) -> Message,
{
Checkbox {
is_checked,
on_toggle: Box::new(f),
label: label.into(),
width: Length::Shrink,
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING,
text_size: None,
font: None,
icon: Icon {
font: Renderer::ICON_FONT,
code_point: Renderer::CHECKMARK_ICON,
size: None,
},
style: Default::default(),
}
}
/// Sets the size of the [`Checkbox`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = size.into().0;
self
}
/// Sets the width of the [`Checkbox`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the spacing between the [`Checkbox`] and the text.
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.spacing = spacing.into().0;
self
}
/// Sets the text size of the [`Checkbox`].
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
self.text_size = Some(text_size.into().0);
self
}
/// Sets the [`Font`] of the text of the [`Checkbox`].
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
/// Sets the [`Icon`] of the [`Checkbox`].
pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
self.icon = icon;
self
}
/// Sets the style of the [`Checkbox`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Checkbox<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
Row::<(), Renderer>::new()
.width(self.width)
.spacing(self.spacing)
.align_items(Alignment::Center)
.push(Row::new().width(self.size).height(self.size))
.push(
Text::new(&self.label)
.font(self.font.unwrap_or_else(|| renderer.default_font()))
.width(self.width)
.size(
self.text_size
.unwrap_or_else(|| renderer.default_size()),
),
)
.layout(renderer, limits)
}
fn on_event(
&mut self,
_tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
shell.publish((self.on_toggle)(!self.is_checked));
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
_tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let mut children = layout.children();
let custom_style = if is_mouse_over {
theme.hovered(&self.style, self.is_checked)
} else {
theme.active(&self.style, self.is_checked)
};
{
let layout = children.next().unwrap();
let bounds = layout.bounds();
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: custom_style.border_radius.into(),
border_width: custom_style.border_width,
border_color: custom_style.border_color,
},
custom_style.background,
);
let Icon {
font,
code_point,
size,
} = &self.icon;
let size = size.unwrap_or(bounds.height * 0.7);
if self.is_checked {
renderer.fill_text(text::Text {
content: &code_point.to_string(),
font: *font,
size,
bounds: Rectangle {
x: bounds.center_x(),
y: bounds.center_y(),
..bounds
},
color: custom_style.icon_color,
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
});
}
}
{
let label_layout = children.next().unwrap();
widget::text::draw(
renderer,
style,
label_layout,
&self.label,
self.text_size,
self.font,
widget::text::Appearance {
color: custom_style.text_color,
},
alignment::Horizontal::Left,
alignment::Vertical::Center,
);
}
}
}
impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
fn from(
checkbox: Checkbox<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(checkbox)
}
}

View file

@ -1,264 +0,0 @@
//! Distribute content vertically.
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::{Operation, Tree};
use crate::{
Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point,
Rectangle, Shell, Widget,
};
/// A container that distributes its contents vertically.
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message, Renderer> {
spacing: f32,
padding: Padding,
width: Length,
height: Length,
max_width: f32,
align_items: Alignment,
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Column {
spacing: 0.0,
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: f32::INFINITY,
align_items: Alignment::Start,
children,
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
self.spacing = amount.into().0;
self
}
/// Sets the [`Padding`] of the [`Column`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the width of the [`Column`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Column`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the maximum width of the [`Column`].
pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
self.max_width = max_width.into().0;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
pub fn push(
mut self,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
self
}
}
impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> {
fn default() -> Self {
Self::new()
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn children(&self) -> Vec<Tree> {
self.children.iter().map(Tree::new).collect()
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&self.children);
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits
.max_width(self.max_width)
.width(self.width)
.height(self.height);
layout::flex::resolve(
layout::flex::Axis::Vertical,
renderer,
&limits,
self.padding,
self.spacing,
self.align_items,
&self.children,
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.children
.iter()
.zip(&mut tree.children)
.zip(layout.children())
.for_each(|((child, state), layout)| {
child
.as_widget()
.operate(state, layout, renderer, 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<'_, Message>,
) -> event::Status {
self.children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor_position,
renderer,
clipboard,
shell,
)
})
.fold(event::Status::Ignored, event::Status::merge)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.children
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
state,
layout,
cursor_position,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child.as_widget().draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
overlay::from_children(&mut self.children, tree, layout, renderer)
}
}
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: crate::Renderer + 'a,
{
fn from(column: Column<'a, Message, Renderer>) -> Self {
Self::new(column)
}
}

View file

@ -1,368 +0,0 @@
//! Decorate content and apply alignment.
use crate::alignment::{self, Alignment};
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::{self, Operation, Tree};
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels,
Point, Rectangle, Shell, Widget,
};
pub use iced_style::container::{Appearance, StyleSheet};
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Container<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
id: Option<Id>,
padding: Padding,
width: Length,
height: Length,
max_width: f32,
max_height: f32,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
style: <Renderer::Theme as StyleSheet>::Style,
content: Element<'a, Message, Renderer>,
}
impl<'a, Message, Renderer> Container<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates an empty [`Container`].
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
Container {
id: None,
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: f32::INFINITY,
max_height: f32::INFINITY,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
style: Default::default(),
content: content.into(),
}
}
/// Sets the [`Id`] of the [`Container`].
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}
/// Sets the [`Padding`] of the [`Container`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the width of the [`Container`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Container`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the maximum width of the [`Container`].
pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
self.max_width = max_width.into().0;
self
}
/// Sets the maximum height of the [`Container`].
pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
self.max_height = max_height.into().0;
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self {
self.horizontal_alignment = alignment;
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
pub fn align_y(mut self, alignment: alignment::Vertical) -> Self {
self.vertical_alignment = alignment;
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
pub fn center_x(mut self) -> Self {
self.horizontal_alignment = alignment::Horizontal::Center;
self
}
/// Centers the contents in the vertical axis of the [`Container`].
pub fn center_y(mut self) -> Self {
self.vertical_alignment = alignment::Vertical::Center;
self
}
/// Sets the style of the [`Container`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(std::slice::from_ref(&self.content))
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
self.width,
self.height,
self.max_width,
self.max_height,
self.padding,
self.horizontal_alignment,
self.vertical_alignment,
|renderer, limits| {
self.content.as_widget().layout(renderer, limits)
},
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
operation.container(
self.id.as_ref().map(|id| &id.0),
&mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
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<'_, Message>,
) -> event::Status {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
let style = theme.appearance(&self.style);
draw_background(renderer, &style, layout.bounds());
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&renderer::Style {
text_color: style
.text_color
.unwrap_or(renderer_style.text_color),
},
layout.children().next().unwrap(),
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
)
}
}
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
column: Container<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(column)
}
}
/// Computes the layout of a [`Container`].
pub fn layout<Renderer>(
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
height: Length,
max_width: f32,
max_height: f32,
padding: Padding,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits
.loose()
.max_width(max_width)
.max_height(max_height)
.width(width)
.height(height);
let mut content = layout_content(renderer, &limits.pad(padding).loose());
let padding = padding.fit(content.size(), limits.max());
let size = limits.pad(padding).resolve(content.size());
content.move_to(Point::new(padding.left, padding.top));
content.align(
Alignment::from(horizontal_alignment),
Alignment::from(vertical_alignment),
size,
);
layout::Node::with_children(size.pad(padding), vec![content])
}
/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.
pub fn draw_background<Renderer>(
renderer: &mut Renderer,
appearance: &Appearance,
bounds: Rectangle,
) where
Renderer: crate::Renderer,
{
if appearance.background.is_some() || appearance.border_width > 0.0 {
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: appearance.border_radius.into(),
border_width: appearance.border_width,
border_color: appearance.border_color,
},
appearance
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
}
}
/// The identifier of a [`Container`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(widget::Id);
impl Id {
/// Creates a custom [`Id`].
pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
Self(widget::Id::new(id))
}
/// Creates a unique [`Id`].
///
/// This function produces a different [`Id`] every time it is called.
pub fn unique() -> Self {
Self(widget::Id::unique())
}
}
impl From<Id> for widget::Id {
fn from(id: Id) -> Self {
id.0
}
}

View file

@ -1,317 +0,0 @@
//! Helper functions to create pure widgets.
use crate::overlay;
use crate::widget;
use crate::{Element, Length, Pixels};
use std::borrow::Cow;
use std::ops::RangeInclusive;
/// Creates a [`Column`] with the given children.
///
/// [`Column`]: widget::Column
#[macro_export]
macro_rules! column {
() => (
$crate::widget::Column::new()
);
($($x:expr),+ $(,)?) => (
$crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+])
);
}
/// Creates a [`Row`] with the given children.
///
/// [`Row`]: widget::Row
#[macro_export]
macro_rules! row {
() => (
$crate::widget::Row::new()
);
($($x:expr),+ $(,)?) => (
$crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+])
);
}
/// Creates a new [`Container`] with the provided content.
///
/// [`Container`]: widget::Container
pub fn container<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
) -> widget::Container<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: widget::container::StyleSheet,
{
widget::Container::new(content)
}
/// Creates a new [`Column`] with the given children.
///
/// [`Column`]: widget::Column
pub fn column<Message, Renderer>(
children: Vec<Element<'_, Message, Renderer>>,
) -> widget::Column<'_, Message, Renderer> {
widget::Column::with_children(children)
}
/// Creates a new [`Row`] with the given children.
///
/// [`Row`]: widget::Row
pub fn row<Message, Renderer>(
children: Vec<Element<'_, Message, Renderer>>,
) -> widget::Row<'_, Message, Renderer> {
widget::Row::with_children(children)
}
/// Creates a new [`Scrollable`] with the provided content.
///
/// [`Scrollable`]: widget::Scrollable
pub fn scrollable<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
) -> widget::Scrollable<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: widget::scrollable::StyleSheet,
{
widget::Scrollable::new(content)
}
/// Creates a new [`Button`] with the provided content.
///
/// [`Button`]: widget::Button
pub fn button<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
) -> widget::Button<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: widget::button::StyleSheet,
<Renderer::Theme as widget::button::StyleSheet>::Style: Default,
{
widget::Button::new(content)
}
/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`].
///
/// [`Tooltip`]: widget::Tooltip
/// [`tooltip::Position`]: widget::tooltip::Position
pub fn tooltip<'a, Message, Renderer>(
content: impl Into<Element<'a, Message, Renderer>>,
tooltip: impl ToString,
position: widget::tooltip::Position,
) -> widget::Tooltip<'a, Message, Renderer>
where
Renderer: crate::text::Renderer,
Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet,
{
widget::Tooltip::new(content, tooltip.to_string(), position)
}
/// Creates a new [`Text`] widget with the provided content.
///
/// [`Text`]: widget::Text
pub fn text<'a, Renderer>(text: impl ToString) -> widget::Text<'a, Renderer>
where
Renderer: crate::text::Renderer,
Renderer::Theme: widget::text::StyleSheet,
{
widget::Text::new(text.to_string())
}
/// Creates a new [`Checkbox`].
///
/// [`Checkbox`]: widget::Checkbox
pub fn checkbox<'a, Message, Renderer>(
label: impl Into<String>,
is_checked: bool,
f: impl Fn(bool) -> Message + 'a,
) -> widget::Checkbox<'a, Message, Renderer>
where
Renderer: crate::text::Renderer,
Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet,
{
widget::Checkbox::new(label, is_checked, f)
}
/// Creates a new [`Radio`].
///
/// [`Radio`]: widget::Radio
pub fn radio<Message, Renderer, V>(
label: impl Into<String>,
value: V,
selected: Option<V>,
on_click: impl FnOnce(V) -> Message,
) -> widget::Radio<Message, Renderer>
where
Message: Clone,
Renderer: crate::text::Renderer,
Renderer::Theme: widget::radio::StyleSheet,
V: Copy + Eq,
{
widget::Radio::new(value, label, selected, on_click)
}
/// Creates a new [`Toggler`].
///
/// [`Toggler`]: widget::Toggler
pub fn toggler<'a, Message, Renderer>(
label: impl Into<Option<String>>,
is_checked: bool,
f: impl Fn(bool) -> Message + 'a,
) -> widget::Toggler<'a, Message, Renderer>
where
Renderer: crate::text::Renderer,
Renderer::Theme: widget::toggler::StyleSheet,
{
widget::Toggler::new(label, is_checked, f)
}
/// Creates a new [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn text_input<'a, Message, Renderer>(
placeholder: &str,
value: &str,
on_change: impl Fn(String) -> Message + 'a,
) -> widget::TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: crate::text::Renderer,
Renderer::Theme: widget::text_input::StyleSheet,
{
widget::TextInput::new(placeholder, value, on_change)
}
/// Creates a new [`Slider`].
///
/// [`Slider`]: widget::Slider
pub fn slider<'a, T, Message, Renderer>(
range: std::ops::RangeInclusive<T>,
value: T,
on_change: impl Fn(T) -> Message + 'a,
) -> widget::Slider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: widget::slider::StyleSheet,
{
widget::Slider::new(range, value, on_change)
}
/// Creates a new [`VerticalSlider`].
///
/// [`VerticalSlider`]: widget::VerticalSlider
pub fn vertical_slider<'a, T, Message, Renderer>(
range: std::ops::RangeInclusive<T>,
value: T,
on_change: impl Fn(T) -> Message + 'a,
) -> widget::VerticalSlider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: widget::slider::StyleSheet,
{
widget::VerticalSlider::new(range, value, on_change)
}
/// Creates a new [`PickList`].
///
/// [`PickList`]: widget::PickList
pub fn pick_list<'a, Message, Renderer, T>(
options: impl Into<Cow<'a, [T]>>,
selected: Option<T>,
on_selected: impl Fn(T) -> Message + 'a,
) -> widget::PickList<'a, T, Message, Renderer>
where
T: ToString + Eq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: crate::text::Renderer,
Renderer::Theme: widget::pick_list::StyleSheet
+ widget::scrollable::StyleSheet
+ overlay::menu::StyleSheet
+ widget::container::StyleSheet,
<Renderer::Theme as overlay::menu::StyleSheet>::Style:
From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>,
{
widget::PickList::new(options, selected, on_selected)
}
/// Creates a new [`Image`].
///
/// [`Image`]: widget::Image
pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> {
widget::Image::new(handle.into())
}
/// Creates a new horizontal [`Space`] with the given [`Length`].
///
/// [`Space`]: widget::Space
pub fn horizontal_space(width: impl Into<Length>) -> widget::Space {
widget::Space::with_width(width)
}
/// Creates a new vertical [`Space`] with the given [`Length`].
///
/// [`Space`]: widget::Space
pub fn vertical_space(height: impl Into<Length>) -> widget::Space {
widget::Space::with_height(height)
}
/// Creates a horizontal [`Rule`] with the given height.
///
/// [`Rule`]: widget::Rule
pub fn horizontal_rule<Renderer>(
height: impl Into<Pixels>,
) -> widget::Rule<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: widget::rule::StyleSheet,
{
widget::Rule::horizontal(height)
}
/// Creates a vertical [`Rule`] with the given width.
///
/// [`Rule`]: widget::Rule
pub fn vertical_rule<Renderer>(
width: impl Into<Pixels>,
) -> widget::Rule<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: widget::rule::StyleSheet,
{
widget::Rule::vertical(width)
}
/// Creates a new [`ProgressBar`].
///
/// It expects:
/// * an inclusive range of possible values, and
/// * the current value of the [`ProgressBar`].
///
/// [`ProgressBar`]: widget::ProgressBar
pub fn progress_bar<Renderer>(
range: RangeInclusive<f32>,
value: f32,
) -> widget::ProgressBar<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: widget::progress_bar::StyleSheet,
{
widget::ProgressBar::new(range, value)
}
/// Creates a new [`Svg`] widget from the given [`Handle`].
///
/// [`Svg`]: widget::Svg
/// [`Handle`]: widget::svg::Handle
pub fn svg<Renderer>(
handle: impl Into<widget::svg::Handle>,
) -> widget::Svg<Renderer>
where
Renderer: crate::svg::Renderer,
Renderer::Theme: widget::svg::StyleSheet,
{
widget::Svg::new(handle)
}

View file

@ -1,43 +0,0 @@
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)]
pub 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

@ -1,204 +0,0 @@
//! Display images in your user interface.
pub mod viewer;
pub use viewer::Viewer;
use crate::image;
use crate::layout;
use crate::renderer;
use crate::widget::Tree;
use crate::{
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use std::hash::Hash;
/// Creates a new [`Viewer`] with the given image `Handle`.
pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
Viewer::new(handle)
}
/// A frame that displays an image while keeping aspect ratio.
///
/// # Example
///
/// ```
/// # use iced_native::widget::Image;
/// # use iced_native::image;
/// #
/// let image = Image::<image::Handle>::new("resources/ferris.png");
/// ```
///
/// <img src="https://github.com/iced-rs/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300">
#[derive(Debug)]
pub struct Image<Handle> {
handle: Handle,
width: Length,
height: Length,
content_fit: ContentFit,
}
impl<Handle> Image<Handle> {
/// Creates a new [`Image`] with the given path.
pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image {
handle: handle.into(),
width: Length::Shrink,
height: Length::Shrink,
content_fit: ContentFit::Contain,
}
}
/// Sets the width of the [`Image`] boundaries.
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Image`] boundaries.
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the [`ContentFit`] of the [`Image`].
///
/// Defaults to [`ContentFit::Contain`]
pub fn content_fit(self, content_fit: ContentFit) -> Self {
Self {
content_fit,
..self
}
}
}
/// Computes the layout of an [`Image`].
pub fn layout<Renderer, Handle>(
renderer: &Renderer,
limits: &layout::Limits,
handle: &Handle,
width: Length,
height: Length,
content_fit: ContentFit,
) -> layout::Node
where
Renderer: image::Renderer<Handle = Handle>,
{
// The raw w/h of the underlying image
let image_size = {
let Size { width, height } = renderer.dimensions(handle);
Size::new(width as f32, height as f32)
};
// The size to be available to the widget prior to `Shrink`ing
let raw_size = limits.width(width).height(height).resolve(image_size);
// The uncropped size of the image when fit to the bounds above
let full_size = content_fit.fit(image_size, raw_size);
// Shrink the widget to fit the resized image, if requested
let final_size = Size {
width: match width {
Length::Shrink => f32::min(raw_size.width, full_size.width),
_ => raw_size.width,
},
height: match height {
Length::Shrink => f32::min(raw_size.height, full_size.height),
_ => raw_size.height,
},
};
layout::Node::new(final_size)
}
/// Draws an [`Image`]
pub fn draw<Renderer, Handle>(
renderer: &mut Renderer,
layout: Layout<'_>,
handle: &Handle,
content_fit: ContentFit,
) where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
let Size { width, height } = renderer.dimensions(handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
let adjusted_fit = content_fit.fit(image_size, bounds.size());
let render = |renderer: &mut Renderer| {
let offset = Vector::new(
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
);
let drawing_bounds = Rectangle {
width: adjusted_fit.width,
height: adjusted_fit.height,
..bounds
};
renderer.draw(handle.clone(), drawing_bounds + offset)
};
if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height
{
renderer.with_layer(bounds, render);
} else {
render(renderer)
}
}
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle>
where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
&self.handle,
self.width,
self.height,
self.content_fit,
)
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
draw(renderer, layout, &self.handle, self.content_fit)
}
}
impl<'a, Message, Renderer, Handle> From<Image<Handle>>
for Element<'a, Message, Renderer>
where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash + 'a,
{
fn from(image: Image<Handle>) -> Element<'a, Message, Renderer> {
Element::new(image)
}
}

View file

@ -1,428 +0,0 @@
//! Zoom and pan on an image.
use crate::event::{self, Event};
use crate::image;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size,
Vector, Widget,
};
use std::hash::Hash;
/// A frame that displays an image with the ability to zoom in/out and pan.
#[allow(missing_debug_implementations)]
pub struct Viewer<Handle> {
padding: f32,
width: Length,
height: Length,
min_scale: f32,
max_scale: f32,
scale_step: f32,
handle: Handle,
}
impl<Handle> Viewer<Handle> {
/// Creates a new [`Viewer`] with the given [`State`].
pub fn new(handle: Handle) -> Self {
Viewer {
padding: 0.0,
width: Length::Shrink,
height: Length::Shrink,
min_scale: 0.25,
max_scale: 10.0,
scale_step: 0.10,
handle,
}
}
/// Sets the padding of the [`Viewer`].
pub fn padding(mut self, padding: impl Into<Pixels>) -> Self {
self.padding = padding.into().0;
self
}
/// Sets the width of the [`Viewer`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Viewer`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the max scale applied to the image of the [`Viewer`].
///
/// Default is `10.0`
pub fn max_scale(mut self, max_scale: f32) -> Self {
self.max_scale = max_scale;
self
}
/// Sets the min scale applied to the image of the [`Viewer`].
///
/// Default is `0.25`
pub fn min_scale(mut self, min_scale: f32) -> Self {
self.min_scale = min_scale;
self
}
/// Sets the percentage the image of the [`Viewer`] will be scaled by
/// when zoomed in / out.
///
/// Default is `0.10`
pub fn scale_step(mut self, scale_step: f32) -> Self {
self.scale_step = scale_step;
self
}
}
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Viewer<Handle>
where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone + Hash,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let Size { width, height } = renderer.dimensions(&self.handle);
let mut size = limits
.width(self.width)
.height(self.height)
.resolve(Size::new(width as f32, height as f32));
let expansion_size = if height > width {
self.width
} else {
self.height
};
// Only calculate viewport sizes if the images are constrained to a limited space.
// If they are Fill|Portion let them expand within their alotted space.
match expansion_size {
Length::Shrink | Length::Fixed(_) => {
let aspect_ratio = width as f32 / height as f32;
let viewport_aspect_ratio = size.width / size.height;
if viewport_aspect_ratio > aspect_ratio {
size.width = width as f32 * size.height / height as f32;
} else {
size.height = height as f32 * size.width / width as f32;
}
}
Length::Fill | Length::FillPortion(_) => {}
}
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
match event {
Event::Mouse(mouse::Event::WheelScrolled { delta })
if is_mouse_over =>
{
match delta {
mouse::ScrollDelta::Lines { y, .. }
| mouse::ScrollDelta::Pixels { y, .. } => {
let state = tree.state.downcast_mut::<State>();
let previous_scale = state.scale;
if y < 0.0 && previous_scale > self.min_scale
|| y > 0.0 && previous_scale < self.max_scale
{
state.scale = (if y > 0.0 {
state.scale * (1.0 + self.scale_step)
} else {
state.scale / (1.0 + self.scale_step)
})
.clamp(self.min_scale, self.max_scale);
let image_size = image_size(
renderer,
&self.handle,
state,
bounds.size(),
);
let factor = state.scale / previous_scale - 1.0;
let cursor_to_center =
cursor_position - bounds.center();
let adjustment = cursor_to_center * factor
+ state.current_offset * factor;
state.current_offset = Vector::new(
if image_size.width > bounds.width {
state.current_offset.x + adjustment.x
} else {
0.0
},
if image_size.height > bounds.height {
state.current_offset.y + adjustment.y
} else {
0.0
},
);
}
}
}
event::Status::Captured
}
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
if is_mouse_over =>
{
let state = tree.state.downcast_mut::<State>();
state.cursor_grabbed_at = Some(cursor_position);
state.starting_offset = state.current_offset;
event::Status::Captured
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
let state = tree.state.downcast_mut::<State>();
if state.cursor_grabbed_at.is_some() {
state.cursor_grabbed_at = None;
event::Status::Captured
} else {
event::Status::Ignored
}
}
Event::Mouse(mouse::Event::CursorMoved { position }) => {
let state = tree.state.downcast_mut::<State>();
if let Some(origin) = state.cursor_grabbed_at {
let image_size = image_size(
renderer,
&self.handle,
state,
bounds.size(),
);
let hidden_width = (image_size.width - bounds.width / 2.0)
.max(0.0)
.round();
let hidden_height = (image_size.height
- bounds.height / 2.0)
.max(0.0)
.round();
let delta = position - origin;
let x = if bounds.width < image_size.width {
(state.starting_offset.x - delta.x)
.clamp(-hidden_width, hidden_width)
} else {
0.0
};
let y = if bounds.height < image_size.height {
(state.starting_offset.y - delta.y)
.clamp(-hidden_height, hidden_height)
} else {
0.0
};
state.current_offset = Vector::new(x, y);
event::Status::Captured
} else {
event::Status::Ignored
}
}
_ => event::Status::Ignored,
}
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
if state.is_cursor_grabbed() {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
mouse::Interaction::Idle
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let image_size =
image_size(renderer, &self.handle, state, bounds.size());
let translation = {
let image_top_left = Vector::new(
bounds.width / 2.0 - image_size.width / 2.0,
bounds.height / 2.0 - image_size.height / 2.0,
);
image_top_left - state.offset(bounds, image_size)
};
renderer.with_layer(bounds, |renderer| {
renderer.with_translation(translation, |renderer| {
image::Renderer::draw(
renderer,
self.handle.clone(),
Rectangle {
x: bounds.x,
y: bounds.y,
..Rectangle::with_size(image_size)
},
)
});
});
}
}
/// The local state of a [`Viewer`].
#[derive(Debug, Clone, Copy)]
pub struct State {
scale: f32,
starting_offset: Vector,
current_offset: Vector,
cursor_grabbed_at: Option<Point>,
}
impl Default for State {
fn default() -> Self {
Self {
scale: 1.0,
starting_offset: Vector::default(),
current_offset: Vector::default(),
cursor_grabbed_at: None,
}
}
}
impl State {
/// Creates a new [`State`].
pub fn new() -> Self {
State::default()
}
/// Returns the current offset of the [`State`], given the bounds
/// of the [`Viewer`] and its image.
fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector {
let hidden_width =
(image_size.width - bounds.width / 2.0).max(0.0).round();
let hidden_height =
(image_size.height - bounds.height / 2.0).max(0.0).round();
Vector::new(
self.current_offset.x.clamp(-hidden_width, hidden_width),
self.current_offset.y.clamp(-hidden_height, hidden_height),
)
}
/// Returns if the cursor is currently grabbed by the [`Viewer`].
pub fn is_cursor_grabbed(&self) -> bool {
self.cursor_grabbed_at.is_some()
}
}
impl<'a, Message, Renderer, Handle> From<Viewer<Handle>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + image::Renderer<Handle = Handle>,
Message: 'a,
Handle: Clone + Hash + 'a,
{
fn from(viewer: Viewer<Handle>) -> Element<'a, Message, Renderer> {
Element::new(viewer)
}
}
/// Returns the bounds of the underlying image, given the bounds of
/// the [`Viewer`]. Scaling will be applied and original aspect ratio
/// will be respected.
pub fn image_size<Renderer>(
renderer: &Renderer,
handle: &<Renderer as image::Renderer>::Handle,
state: &State,
bounds: Size,
) -> Size
where
Renderer: image::Renderer,
{
let Size { width, height } = renderer.dimensions(handle);
let (width, height) = {
let dimensions = (width as f32, height as f32);
let width_ratio = bounds.width / dimensions.0;
let height_ratio = bounds.height / dimensions.1;
let ratio = width_ratio.min(height_ratio);
let scale = state.scale;
if ratio < 1.0 {
(dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
} else {
(dimensions.0 * scale, dimensions.1 * scale)
}
};
Size::new(width, height)
}

View file

@ -1,112 +0,0 @@
//! 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

@ -1,203 +0,0 @@
//! 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

@ -1,54 +0,0 @@
//! 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

@ -1,131 +0,0 @@
//! 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 }
}

View file

@ -1,991 +0,0 @@
//! Let your users split regions of your application and organize layout dynamically.
//!
//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
//!
//! # Example
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid
mod axis;
mod configuration;
mod content;
mod direction;
mod draggable;
mod node;
mod pane;
mod split;
mod title_bar;
pub mod state;
pub use axis::Axis;
pub use configuration::Configuration;
pub use content::Content;
pub use direction::Direction;
pub use draggable::Draggable;
pub use node::Node;
pub use pane::Pane;
pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
pub use iced_style::pane_grid::{Line, StyleSheet};
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay::{self, Group};
use crate::renderer;
use crate::touch;
use crate::widget;
use crate::widget::container;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
Size, Vector, Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](https://gfycat.com/frailfreshairedaleterrier)
///
/// This distribution of space is common in tiling window managers (like
/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even
/// [`tmux`](https://github.com/tmux/tmux)).
///
/// A [`PaneGrid`] supports:
///
/// * Vertical and horizontal splits
/// * Tracking of the last active pane
/// * Mouse-based resizing
/// * Drag and drop to reorganize panes
/// * Hotkey support
/// * Configurable modifier keys
/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.)
///
/// ## Example
///
/// ```
/// # use iced_native::widget::{pane_grid, text};
/// #
/// # type PaneGrid<'a, Message> =
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
/// #
/// enum PaneState {
/// SomePane,
/// AnotherKindOfPane,
/// }
///
/// enum Message {
/// PaneDragged(pane_grid::DragEvent),
/// PaneResized(pane_grid::ResizeEvent),
/// }
///
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
/// PaneGrid::new(&state, |pane, state, is_maximized| {
/// pane_grid::Content::new(match state {
/// PaneState::SomePane => text("This is some pane"),
/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
/// })
/// })
/// .on_drag(Message::PaneDragged)
/// .on_resize(10, Message::PaneResized);
/// ```
#[allow(missing_debug_implementations)]
pub struct PaneGrid<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
contents: Contents<'a, Content<'a, Message, Renderer>>,
width: Length,
height: Length,
spacing: f32,
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
/// The view function will be called to display each [`Pane`] present in the
/// [`State`]. [`bool`] is set if the pane is maximized.
pub fn new<T>(
state: &'a State<T>,
view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>,
) -> Self {
let contents = if let Some((pane, pane_state)) =
state.maximized.and_then(|pane| {
state.panes.get(&pane).map(|pane_state| (pane, pane_state))
}) {
Contents::Maximized(
pane,
view(pane, pane_state, true),
Node::Pane(pane),
)
} else {
Contents::All(
state
.panes
.iter()
.map(|(pane, pane_state)| {
(*pane, view(*pane, pane_state, false))
})
.collect(),
&state.internal,
)
};
Self {
contents,
width: Length::Fill,
height: Length::Fill,
spacing: 0.0,
on_click: None,
on_drag: None,
on_resize: None,
style: Default::default(),
}
}
/// Sets the width of the [`PaneGrid`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`PaneGrid`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the spacing _between_ the panes of the [`PaneGrid`].
pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
self.spacing = amount.into().0;
self
}
/// Sets the message that will be produced when a [`Pane`] of the
/// [`PaneGrid`] is clicked.
pub fn on_click<F>(mut self, f: F) -> Self
where
F: 'a + Fn(Pane) -> Message,
{
self.on_click = Some(Box::new(f));
self
}
/// Enables the drag and drop interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
pub fn on_drag<F>(mut self, f: F) -> Self
where
F: 'a + Fn(DragEvent) -> Message,
{
self.on_drag = Some(Box::new(f));
self
}
/// Enables the resize interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
///
/// The `leeway` describes the amount of space around a split that can be
/// used to grab it.
///
/// The grabbable area of a split will have a length of `spacing + leeway`,
/// properly centered. In other words, a length of
/// `(spacing + leeway) / 2.0` on either side of the split line.
pub fn on_resize<F>(mut self, leeway: impl Into<Pixels>, f: F) -> Self
where
F: 'a + Fn(ResizeEvent) -> Message,
{
self.on_resize = Some((leeway.into().0, Box::new(f)));
self
}
/// Sets the style of the [`PaneGrid`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
fn drag_enabled(&self) -> bool {
(!self.contents.is_maximized())
.then(|| self.on_drag.is_some())
.unwrap_or_default()
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<state::Action>()
}
fn state(&self) -> tree::State {
tree::State::new(state::Action::Idle)
}
fn children(&self) -> Vec<Tree> {
self.contents
.iter()
.map(|(_, content)| content.state())
.collect()
}
fn diff(&self, tree: &mut Tree) {
match &self.contents {
Contents::All(contents, _) => tree.diff_children_custom(
contents,
|state, (_, content)| content.diff(state),
|(_, content)| content.state(),
),
Contents::Maximized(_, content, _) => tree.diff_children_custom(
&[content],
|state, content| content.diff(state),
|content| content.state(),
),
}
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
self.contents.layout(),
self.width,
self.height,
self.spacing,
self.contents.iter(),
|content, renderer, limits| content.layout(renderer, limits),
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.contents
.iter()
.zip(&mut tree.children)
.zip(layout.children())
.for_each(|(((_pane, content), state), layout)| {
content.operate(state, layout, renderer, 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<'_, Message>,
) -> event::Status {
let action = tree.state.downcast_mut::<state::Action>();
let on_drag = if self.drag_enabled() {
&self.on_drag
} else {
&None
};
let event_status = update(
action,
self.contents.layout(),
&event,
layout,
cursor_position,
shell,
self.spacing,
self.contents.iter(),
&self.on_click,
on_drag,
&self.on_resize,
);
let picked_pane = action.picked_pane().map(|(pane, _)| pane);
self.contents
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|(((pane, content), tree), layout)| {
let is_picked = picked_pane == Some(pane);
content.on_event(
tree,
event.clone(),
layout,
cursor_position,
renderer,
clipboard,
shell,
is_picked,
)
})
.fold(event_status, event::Status::merge)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
tree.state.downcast_ref(),
self.contents.layout(),
layout,
cursor_position,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
.unwrap_or_else(|| {
self.contents
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|(((_pane, content), tree), layout)| {
content.mouse_interaction(
tree,
layout,
cursor_position,
viewport,
renderer,
self.drag_enabled(),
)
})
.max()
.unwrap_or_default()
})
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
draw(
tree.state.downcast_ref(),
self.contents.layout(),
layout,
cursor_position,
renderer,
theme,
style,
viewport,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
&self.style,
self.contents
.iter()
.zip(&tree.children)
.map(|((pane, content), tree)| (pane, (content, tree))),
|(content, tree),
renderer,
style,
layout,
cursor_position,
rectangle| {
content.draw(
tree,
renderer,
theme,
style,
layout,
cursor_position,
rectangle,
);
},
)
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
let children = self
.contents
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|(((_, content), state), layout)| {
content.overlay(state, layout, renderer)
})
.collect::<Vec<_>>();
(!children.is_empty()).then(|| Group::with_children(children).overlay())
}
}
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
fn from(
pane_grid: PaneGrid<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(pane_grid)
}
}
/// Calculates the [`Layout`] of a [`PaneGrid`].
pub fn layout<Renderer, T>(
renderer: &Renderer,
limits: &layout::Limits,
node: &Node,
width: Length,
height: Length,
spacing: f32,
contents: impl Iterator<Item = (Pane, T)>,
layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
let size = limits.resolve(Size::ZERO);
let regions = node.pane_regions(spacing, size);
let children = contents
.filter_map(|(pane, content)| {
let region = regions.get(&pane)?;
let size = Size::new(region.width, region.height);
let mut node = layout_content(
content,
renderer,
&layout::Limits::new(size, size),
);
node.move_to(Point::new(region.x, region.y));
Some(node)
})
.collect();
layout::Node::with_children(size, children)
}
/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`]
/// accordingly.
pub fn update<'a, Message, T: Draggable>(
action: &mut state::Action,
node: &Node,
event: &Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
spacing: f32,
contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
) -> event::Status {
let mut event_status = event::Status::Ignored;
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
event_status = event::Status::Captured;
match on_resize {
Some((leeway, _)) => {
let relative_cursor = Point::new(
cursor_position.x - bounds.x,
cursor_position.y - bounds.y,
);
let splits = node.split_regions(
spacing,
Size::new(bounds.width, bounds.height),
);
let clicked_split = hovered_split(
splits.iter(),
spacing + leeway,
relative_cursor,
);
if let Some((split, axis, _)) = clicked_split {
if action.picked_pane().is_none() {
*action =
state::Action::Resizing { split, axis };
}
} else {
click_pane(
action,
layout,
cursor_position,
shell,
contents,
on_click,
on_drag,
);
}
}
None => {
click_pane(
action,
layout,
cursor_position,
shell,
contents,
on_click,
on_drag,
);
}
}
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. })
| Event::Touch(touch::Event::FingerLost { .. }) => {
if let Some((pane, _)) = action.picked_pane() {
if let Some(on_drag) = on_drag {
let mut dropped_region = contents
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
});
let event = match dropped_region.next() {
Some(((target, _), _)) if pane != target => {
DragEvent::Dropped { pane, target }
}
_ => DragEvent::Canceled { pane },
};
shell.publish(on_drag(event));
}
*action = state::Action::Idle;
event_status = event::Status::Captured;
} else if action.picked_split().is_some() {
*action = state::Action::Idle;
event_status = event::Status::Captured;
}
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if let Some((_, on_resize)) = on_resize {
if let Some((split, _)) = action.picked_split() {
let bounds = layout.bounds();
let splits = node.split_regions(
spacing,
Size::new(bounds.width, bounds.height),
);
if let Some((axis, rectangle, _)) = splits.get(&split) {
let ratio = match axis {
Axis::Horizontal => {
let position =
cursor_position.y - bounds.y - rectangle.y;
(position / rectangle.height).clamp(0.1, 0.9)
}
Axis::Vertical => {
let position =
cursor_position.x - bounds.x - rectangle.x;
(position / rectangle.width).clamp(0.1, 0.9)
}
};
shell.publish(on_resize(ResizeEvent { split, ratio }));
event_status = event::Status::Captured;
}
}
}
}
_ => {}
}
event_status
}
fn click_pane<'a, Message, T>(
action: &mut state::Action,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
) where
T: Draggable,
{
let mut clicked_region = contents
.zip(layout.children())
.filter(|(_, layout)| layout.bounds().contains(cursor_position));
if let Some(((pane, content), layout)) = clicked_region.next() {
if let Some(on_click) = &on_click {
shell.publish(on_click(pane));
}
if let Some(on_drag) = &on_drag {
if content.can_be_dragged_at(layout, cursor_position) {
let pane_position = layout.position();
let origin = cursor_position
- Vector::new(pane_position.x, pane_position.y);
*action = state::Action::Dragging { pane, origin };
shell.publish(on_drag(DragEvent::Picked { pane }));
}
}
}
}
/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`].
pub fn mouse_interaction(
action: &state::Action,
node: &Node,
layout: Layout<'_>,
cursor_position: Point,
spacing: f32,
resize_leeway: Option<f32>,
) -> Option<mouse::Interaction> {
if action.picked_pane().is_some() {
return Some(mouse::Interaction::Grabbing);
}
let resize_axis =
action.picked_split().map(|(_, axis)| axis).or_else(|| {
resize_leeway.and_then(|leeway| {
let bounds = layout.bounds();
let splits = node.split_regions(spacing, bounds.size());
let relative_cursor = Point::new(
cursor_position.x - bounds.x,
cursor_position.y - bounds.y,
);
hovered_split(splits.iter(), spacing + leeway, relative_cursor)
.map(|(_, axis, _)| axis)
})
});
if let Some(resize_axis) = resize_axis {
return Some(match resize_axis {
Axis::Horizontal => mouse::Interaction::ResizingVertically,
Axis::Vertical => mouse::Interaction::ResizingHorizontally,
});
}
None
}
/// Draws a [`PaneGrid`].
pub fn draw<Renderer, T>(
action: &state::Action,
node: &Node,
layout: Layout<'_>,
cursor_position: Point,
renderer: &mut Renderer,
theme: &Renderer::Theme,
default_style: &renderer::Style,
viewport: &Rectangle,
spacing: f32,
resize_leeway: Option<f32>,
style: &<Renderer::Theme as StyleSheet>::Style,
contents: impl Iterator<Item = (Pane, T)>,
draw_pane: impl Fn(
T,
&mut Renderer,
&renderer::Style,
Layout<'_>,
Point,
&Rectangle,
),
) where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
let picked_pane = action.picked_pane();
let picked_split = action
.picked_split()
.and_then(|(split, axis)| {
let bounds = layout.bounds();
let splits = node.split_regions(spacing, bounds.size());
let (_axis, region, ratio) = splits.get(&split)?;
let region = axis.split_line_bounds(*region, *ratio, spacing);
Some((axis, region + Vector::new(bounds.x, bounds.y), true))
})
.or_else(|| match resize_leeway {
Some(leeway) => {
let bounds = layout.bounds();
let relative_cursor = Point::new(
cursor_position.x - bounds.x,
cursor_position.y - bounds.y,
);
let splits = node.split_regions(spacing, bounds.size());
let (_split, axis, region) = hovered_split(
splits.iter(),
spacing + leeway,
relative_cursor,
)?;
Some((axis, region + Vector::new(bounds.x, bounds.y), false))
}
None => None,
});
let pane_cursor_position = if picked_pane.is_some() {
// TODO: Remove once cursor availability is encoded in the type
// system
Point::new(-1.0, -1.0)
} else {
cursor_position
};
let mut render_picked_pane = None;
for ((id, pane), layout) in contents.zip(layout.children()) {
match picked_pane {
Some((dragging, origin)) if id == dragging => {
render_picked_pane = Some((pane, origin, layout));
}
_ => {
draw_pane(
pane,
renderer,
default_style,
layout,
pane_cursor_position,
viewport,
);
}
}
}
// Render picked pane last
if let Some((pane, origin, layout)) = render_picked_pane {
let bounds = layout.bounds();
renderer.with_translation(
cursor_position
- Point::new(bounds.x + origin.x, bounds.y + origin.y),
|renderer| {
renderer.with_layer(bounds, |renderer| {
draw_pane(
pane,
renderer,
default_style,
layout,
pane_cursor_position,
viewport,
);
});
},
);
};
if let Some((axis, split_region, is_picked)) = picked_split {
let highlight = if is_picked {
theme.picked_split(style)
} else {
theme.hovered_split(style)
};
if let Some(highlight) = highlight {
renderer.fill_quad(
renderer::Quad {
bounds: match axis {
Axis::Horizontal => Rectangle {
x: split_region.x,
y: (split_region.y
+ (split_region.height - highlight.width)
/ 2.0)
.round(),
width: split_region.width,
height: highlight.width,
},
Axis::Vertical => Rectangle {
x: (split_region.x
+ (split_region.width - highlight.width) / 2.0)
.round(),
y: split_region.y,
width: highlight.width,
height: split_region.height,
},
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
highlight.color,
);
}
}
}
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
/// A [`Pane`] was picked for dragging.
Picked {
/// The picked [`Pane`].
pane: Pane,
},
/// A [`Pane`] was dropped on top of another [`Pane`].
Dropped {
/// The picked [`Pane`].
pane: Pane,
/// The [`Pane`] where the picked one was dropped on.
target: Pane,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
/// boundaries.
Canceled {
/// The picked [`Pane`].
pane: Pane,
},
}
/// An event produced during a resize interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
/// The [`Split`] that is being dragged for resizing.
pub split: Split,
/// The new ratio of the [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
pub ratio: f32,
}
/*
* Helpers
*/
fn hovered_split<'a>(
splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>,
spacing: f32,
cursor_position: Point,
) -> Option<(Split, Axis, Rectangle)> {
splits
.filter_map(|(split, (axis, region, ratio))| {
let bounds = axis.split_line_bounds(*region, *ratio, spacing);
if bounds.contains(cursor_position) {
Some((*split, *axis, bounds))
} else {
None
}
})
.next()
}
/// The visible contents of the [`PaneGrid`]
#[derive(Debug)]
pub enum Contents<'a, T> {
/// All panes are visible
All(Vec<(Pane, T)>, &'a state::Internal),
/// A maximized pane is visible
Maximized(Pane, T, Node),
}
impl<'a, T> Contents<'a, T> {
/// Returns the layout [`Node`] of the [`Contents`]
pub fn layout(&self) -> &Node {
match self {
Contents::All(_, state) => state.layout(),
Contents::Maximized(_, _, layout) => layout,
}
}
/// Returns an iterator over the values of the [`Contents`]
pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> {
match self {
Contents::All(contents, _) => Box::new(
contents.iter().map(|(pane, content)| (*pane, content)),
),
Contents::Maximized(pane, content, _) => {
Box::new(std::iter::once((*pane, content)))
}
}
}
fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> {
match self {
Contents::All(contents, _) => Box::new(
contents.iter_mut().map(|(pane, content)| (*pane, content)),
),
Contents::Maximized(pane, content, _) => {
Box::new(std::iter::once((*pane, content)))
}
}
}
fn is_maximized(&self) -> bool {
matches!(self, Self::Maximized(..))
}
}

View file

@ -1,241 +0,0 @@
use crate::Rectangle;
/// A fixed reference line for the measurement of coordinates.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Axis {
/// The horizontal axis: —
Horizontal,
/// The vertical axis: |
Vertical,
}
impl Axis {
/// Splits the provided [`Rectangle`] on the current [`Axis`] with the
/// given `ratio` and `spacing`.
pub fn split(
&self,
rectangle: &Rectangle,
ratio: f32,
spacing: f32,
) -> (Rectangle, Rectangle) {
match self {
Axis::Horizontal => {
let height_top =
(rectangle.height * ratio - spacing / 2.0).round();
let height_bottom = rectangle.height - height_top - spacing;
(
Rectangle {
height: height_top,
..*rectangle
},
Rectangle {
y: rectangle.y + height_top + spacing,
height: height_bottom,
..*rectangle
},
)
}
Axis::Vertical => {
let width_left =
(rectangle.width * ratio - spacing / 2.0).round();
let width_right = rectangle.width - width_left - spacing;
(
Rectangle {
width: width_left,
..*rectangle
},
Rectangle {
x: rectangle.x + width_left + spacing,
width: width_right,
..*rectangle
},
)
}
}
}
/// Calculates the bounds of the split line in a [`Rectangle`] region.
pub fn split_line_bounds(
&self,
rectangle: Rectangle,
ratio: f32,
spacing: f32,
) -> Rectangle {
match self {
Axis::Horizontal => Rectangle {
x: rectangle.x,
y: (rectangle.y + rectangle.height * ratio - spacing / 2.0)
.round(),
width: rectangle.width,
height: spacing,
},
Axis::Vertical => Rectangle {
x: (rectangle.x + rectangle.width * ratio - spacing / 2.0)
.round(),
y: rectangle.y,
width: spacing,
height: rectangle.height,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
enum Case {
Horizontal {
overall_height: f32,
spacing: f32,
top_height: f32,
bottom_y: f32,
bottom_height: f32,
},
Vertical {
overall_width: f32,
spacing: f32,
left_width: f32,
right_x: f32,
right_width: f32,
},
}
#[test]
fn split() {
let cases = vec![
// Even height, even spacing
Case::Horizontal {
overall_height: 10.0,
spacing: 2.0,
top_height: 4.0,
bottom_y: 6.0,
bottom_height: 4.0,
},
// Odd height, even spacing
Case::Horizontal {
overall_height: 9.0,
spacing: 2.0,
top_height: 4.0,
bottom_y: 6.0,
bottom_height: 3.0,
},
// Even height, odd spacing
Case::Horizontal {
overall_height: 10.0,
spacing: 1.0,
top_height: 5.0,
bottom_y: 6.0,
bottom_height: 4.0,
},
// Odd height, odd spacing
Case::Horizontal {
overall_height: 9.0,
spacing: 1.0,
top_height: 4.0,
bottom_y: 5.0,
bottom_height: 4.0,
},
// Even width, even spacing
Case::Vertical {
overall_width: 10.0,
spacing: 2.0,
left_width: 4.0,
right_x: 6.0,
right_width: 4.0,
},
// Odd width, even spacing
Case::Vertical {
overall_width: 9.0,
spacing: 2.0,
left_width: 4.0,
right_x: 6.0,
right_width: 3.0,
},
// Even width, odd spacing
Case::Vertical {
overall_width: 10.0,
spacing: 1.0,
left_width: 5.0,
right_x: 6.0,
right_width: 4.0,
},
// Odd width, odd spacing
Case::Vertical {
overall_width: 9.0,
spacing: 1.0,
left_width: 4.0,
right_x: 5.0,
right_width: 4.0,
},
];
for case in cases {
match case {
Case::Horizontal {
overall_height,
spacing,
top_height,
bottom_y,
bottom_height,
} => {
let a = Axis::Horizontal;
let r = Rectangle {
x: 0.0,
y: 0.0,
width: 10.0,
height: overall_height,
};
let (top, bottom) = a.split(&r, 0.5, spacing);
assert_eq!(
top,
Rectangle {
height: top_height,
..r
}
);
assert_eq!(
bottom,
Rectangle {
y: bottom_y,
height: bottom_height,
..r
}
);
}
Case::Vertical {
overall_width,
spacing,
left_width,
right_x,
right_width,
} => {
let a = Axis::Vertical;
let r = Rectangle {
x: 0.0,
y: 0.0,
width: overall_width,
height: 10.0,
};
let (left, right) = a.split(&r, 0.5, spacing);
assert_eq!(
left,
Rectangle {
width: left_width,
..r
}
);
assert_eq!(
right,
Rectangle {
x: right_x,
width: right_width,
..r
}
);
}
}
}
}
}

View file

@ -1,26 +0,0 @@
use crate::widget::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone)]
pub enum Configuration<T> {
/// A split of the available space.
Split {
/// The direction of the split.
axis: Axis,
/// The ratio of the split in [0.0, 1.0].
ratio: f32,
/// The left/top [`Configuration`] of the split.
a: Box<Configuration<T>>,
/// The right/bottom [`Configuration`] of the split.
b: Box<Configuration<T>>,
},
/// A [`Pane`].
///
/// [`Pane`]: crate::widget::pane_grid::Pane
Pane(T),
}

View file

@ -1,373 +0,0 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::pane_grid::{Draggable, TitleBar};
use crate::widget::{self, Tree};
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// The content of a [`Pane`].
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
pub struct Content<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
title_bar: Option<TitleBar<'a, Message, Renderer>>,
body: Element<'a, Message, Renderer>,
style: <Renderer::Theme as container::StyleSheet>::Style,
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
/// Creates a new [`Content`] with the provided body.
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
Self {
title_bar: None,
body: body.into(),
style: Default::default(),
}
}
/// Sets the [`TitleBar`] of this [`Content`].
pub fn title_bar(
mut self,
title_bar: TitleBar<'a, Message, Renderer>,
) -> Self {
self.title_bar = Some(title_bar);
self
}
/// Sets the style of the [`Content`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
pub(super) fn state(&self) -> Tree {
let children = if let Some(title_bar) = self.title_bar.as_ref() {
vec![Tree::new(&self.body), title_bar.state()]
} else {
vec![Tree::new(&self.body), Tree::empty()]
};
Tree {
children,
..Tree::empty()
}
}
pub(super) fn diff(&self, tree: &mut Tree) {
if tree.children.len() == 2 {
if let Some(title_bar) = self.title_bar.as_ref() {
title_bar.diff(&mut tree.children[1]);
}
tree.children[0].diff(&self.body);
} else {
*tree = self.state();
}
}
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
/// [`Renderer`]: crate::Renderer
pub fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
use container::StyleSheet;
let bounds = layout.bounds();
{
let style = theme.appearance(&self.style);
container::draw_background(renderer, &style, bounds);
}
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
let title_bar_layout = children.next().unwrap();
let body_layout = children.next().unwrap();
let show_controls = bounds.contains(cursor_position);
self.body.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
body_layout,
cursor_position,
viewport,
);
title_bar.draw(
&tree.children[1],
renderer,
theme,
style,
title_bar_layout,
cursor_position,
viewport,
show_controls,
);
} else {
self.body.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
}
pub(crate) fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
if let Some(title_bar) = &self.title_bar {
let max_size = limits.max();
let title_bar_layout = title_bar
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
let title_bar_size = title_bar_layout.size();
let mut body_layout = self.body.as_widget().layout(
renderer,
&layout::Limits::new(
Size::ZERO,
Size::new(
max_size.width,
max_size.height - title_bar_size.height,
),
),
);
body_layout.move_to(Point::new(0.0, title_bar_size.height));
layout::Node::with_children(
max_size,
vec![title_bar_layout, body_layout],
)
} else {
self.body.as_widget().layout(renderer, limits)
}
}
pub(crate) fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
let body_layout = if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
title_bar.operate(
&mut tree.children[1],
children.next().unwrap(),
renderer,
operation,
);
children.next().unwrap()
} else {
layout
};
self.body.as_widget().operate(
&mut tree.children[0],
body_layout,
renderer,
operation,
);
}
pub(crate) fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
is_picked: bool,
) -> event::Status {
let mut event_status = event::Status::Ignored;
let body_layout = if let Some(title_bar) = &mut self.title_bar {
let mut children = layout.children();
event_status = title_bar.on_event(
&mut tree.children[1],
event.clone(),
children.next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
);
children.next().unwrap()
} else {
layout
};
let body_status = if is_picked {
event::Status::Ignored
} else {
self.body.as_widget_mut().on_event(
&mut tree.children[0],
event,
body_layout,
cursor_position,
renderer,
clipboard,
shell,
)
};
event_status.merge(body_status)
}
pub(crate) fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
drag_enabled: bool,
) -> mouse::Interaction {
let (body_layout, title_bar_interaction) =
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
let title_bar_layout = children.next().unwrap();
let is_over_pick_area = title_bar
.is_over_pick_area(title_bar_layout, cursor_position);
if is_over_pick_area && drag_enabled {
return mouse::Interaction::Grab;
}
let mouse_interaction = title_bar.mouse_interaction(
&tree.children[1],
title_bar_layout,
cursor_position,
viewport,
renderer,
);
(children.next().unwrap(), mouse_interaction)
} else {
(layout, mouse::Interaction::default())
};
self.body
.as_widget()
.mouse_interaction(
&tree.children[0],
body_layout,
cursor_position,
viewport,
renderer,
)
.max(title_bar_interaction)
}
pub(crate) fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
if let Some(title_bar) = self.title_bar.as_mut() {
let mut children = layout.children();
let title_bar_layout = children.next()?;
let mut states = tree.children.iter_mut();
let body_state = states.next().unwrap();
let title_bar_state = states.next().unwrap();
match title_bar.overlay(title_bar_state, title_bar_layout, renderer)
{
Some(overlay) => Some(overlay),
None => self.body.as_widget_mut().overlay(
body_state,
children.next()?,
renderer,
),
}
} else {
self.body.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
)
}
}
}
impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
fn can_be_dragged_at(
&self,
layout: Layout<'_>,
cursor_position: Point,
) -> bool {
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
let title_bar_layout = children.next().unwrap();
title_bar.is_over_pick_area(title_bar_layout, cursor_position)
} else {
false
}
}
}
impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
where
T: Into<Element<'a, Message, Renderer>>,
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
fn from(element: T) -> Self {
Self::new(element)
}
}

View file

@ -1,12 +0,0 @@
/// A four cardinal direction.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
/// ↑
Up,
/// ↓
Down,
/// ←
Left,
/// →
Right,
}

View file

@ -1,12 +0,0 @@
use crate::{Layout, Point};
/// A pane that can be dragged.
pub trait Draggable {
/// Returns whether the [`Draggable`] with the given [`Layout`] can be picked
/// at the provided cursor position.
fn can_be_dragged_at(
&self,
layout: Layout<'_>,
cursor_position: Point,
) -> bool;
}

View file

@ -1,250 +0,0 @@
use crate::widget::pane_grid::{Axis, Pane, Split};
use crate::{Rectangle, Size};
use std::collections::BTreeMap;
/// A layout node of a [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone)]
pub enum Node {
/// The region of this [`Node`] is split into two.
Split {
/// The [`Split`] of this [`Node`].
id: Split,
/// The direction of the split.
axis: Axis,
/// The ratio of the split in [0.0, 1.0].
ratio: f32,
/// The left/top [`Node`] of the split.
a: Box<Node>,
/// The right/bottom [`Node`] of the split.
b: Box<Node>,
},
/// The region of this [`Node`] is taken by a [`Pane`].
Pane(Pane),
}
impl Node {
/// Returns an iterator over each [`Split`] in this [`Node`].
pub fn splits(&self) -> impl Iterator<Item = &Split> {
let mut unvisited_nodes = vec![self];
std::iter::from_fn(move || {
while let Some(node) = unvisited_nodes.pop() {
if let Node::Split { id, a, b, .. } = node {
unvisited_nodes.push(a);
unvisited_nodes.push(b);
return Some(id);
}
}
None
})
}
/// Returns the rectangular region for each [`Pane`] in the [`Node`] given
/// the spacing between panes and the total available space.
pub fn pane_regions(
&self,
spacing: f32,
size: Size,
) -> BTreeMap<Pane, Rectangle> {
let mut regions = BTreeMap::new();
self.compute_regions(
spacing,
&Rectangle {
x: 0.0,
y: 0.0,
width: size.width,
height: size.height,
},
&mut regions,
);
regions
}
/// Returns the axis, rectangular region, and ratio for each [`Split`] in
/// the [`Node`] given the spacing between panes and the total available
/// space.
pub fn split_regions(
&self,
spacing: f32,
size: Size,
) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
let mut splits = BTreeMap::new();
self.compute_splits(
spacing,
&Rectangle {
x: 0.0,
y: 0.0,
width: size.width,
height: size.height,
},
&mut splits,
);
splits
}
pub(crate) fn find(&mut self, pane: &Pane) -> Option<&mut Node> {
match self {
Node::Split { a, b, .. } => {
a.find(pane).or_else(move || b.find(pane))
}
Node::Pane(p) => {
if p == pane {
Some(self)
} else {
None
}
}
}
}
pub(crate) fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) {
*self = Node::Split {
id,
axis,
ratio: 0.5,
a: Box::new(self.clone()),
b: Box::new(Node::Pane(new_pane)),
};
}
pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
if let Node::Split { a, b, .. } = self {
a.update(f);
b.update(f);
}
f(self);
}
pub(crate) fn resize(&mut self, split: &Split, percentage: f32) -> bool {
match self {
Node::Split {
id, ratio, a, b, ..
} => {
if id == split {
*ratio = percentage;
true
} else if a.resize(split, percentage) {
true
} else {
b.resize(split, percentage)
}
}
Node::Pane(_) => false,
}
}
pub(crate) fn remove(&mut self, pane: &Pane) -> Option<Pane> {
match self {
Node::Split { a, b, .. } => {
if a.pane() == Some(*pane) {
*self = *b.clone();
Some(self.first_pane())
} else if b.pane() == Some(*pane) {
*self = *a.clone();
Some(self.first_pane())
} else {
a.remove(pane).or_else(|| b.remove(pane))
}
}
Node::Pane(_) => None,
}
}
fn pane(&self) -> Option<Pane> {
match self {
Node::Split { .. } => None,
Node::Pane(pane) => Some(*pane),
}
}
fn first_pane(&self) -> Pane {
match self {
Node::Split { a, .. } => a.first_pane(),
Node::Pane(pane) => *pane,
}
}
fn compute_regions(
&self,
spacing: f32,
current: &Rectangle,
regions: &mut BTreeMap<Pane, Rectangle>,
) {
match self {
Node::Split {
axis, ratio, a, b, ..
} => {
let (region_a, region_b) = axis.split(current, *ratio, spacing);
a.compute_regions(spacing, &region_a, regions);
b.compute_regions(spacing, &region_b, regions);
}
Node::Pane(pane) => {
let _ = regions.insert(*pane, *current);
}
}
}
fn compute_splits(
&self,
spacing: f32,
current: &Rectangle,
splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
) {
match self {
Node::Split {
axis,
ratio,
a,
b,
id,
} => {
let (region_a, region_b) = axis.split(current, *ratio, spacing);
let _ = splits.insert(*id, (*axis, *current, *ratio));
a.compute_splits(spacing, &region_a, splits);
b.compute_splits(spacing, &region_b, splits);
}
Node::Pane(_) => {}
}
}
}
impl std::hash::Hash for Node {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Node::Split {
id,
axis,
ratio,
a,
b,
} => {
id.hash(state);
axis.hash(state);
((ratio * 100_000.0) as u32).hash(state);
a.hash(state);
b.hash(state);
}
Node::Pane(pane) => {
pane.hash(state);
}
}
}
}

View file

@ -1,5 +0,0 @@
/// A rectangular region in a [`PaneGrid`] used to display widgets.
///
/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Pane(pub(super) usize);

View file

@ -1,5 +0,0 @@
/// A divider that splits a region in a [`PaneGrid`] into two different panes.
///
/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Split(pub(super) usize);

View file

@ -1,350 +0,0 @@
//! The state of a [`PaneGrid`].
//!
//! [`PaneGrid`]: crate::widget::PaneGrid
use crate::widget::pane_grid::{
Axis, Configuration, Direction, Node, Pane, Split,
};
use crate::{Point, Size};
use std::collections::HashMap;
/// The state of a [`PaneGrid`].
///
/// It keeps track of the state of each [`Pane`] and the position of each
/// [`Split`].
///
/// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is
/// why this struct is generic over the type `T`. Values of this type are
/// provided to the view function of [`PaneGrid::new`] for displaying each
/// [`Pane`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
/// [`PaneGrid::new`]: crate::widget::PaneGrid::new
#[derive(Debug, Clone)]
pub struct State<T> {
/// The panes of the [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub panes: HashMap<Pane, T>,
/// The internal state of the [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub internal: Internal,
/// The maximized [`Pane`] of the [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub(super) maximized: Option<Pane>,
}
impl<T> State<T> {
/// Creates a new [`State`], initializing the first pane with the provided
/// state.
///
/// Alongside the [`State`], it returns the first [`Pane`] identifier.
pub fn new(first_pane_state: T) -> (Self, Pane) {
(
Self::with_configuration(Configuration::Pane(first_pane_state)),
Pane(0),
)
}
/// Creates a new [`State`] with the given [`Configuration`].
pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
let mut panes = HashMap::new();
let internal =
Internal::from_configuration(&mut panes, config.into(), 0);
State {
panes,
internal,
maximized: None,
}
}
/// Returns the total amount of panes in the [`State`].
pub fn len(&self) -> usize {
self.panes.len()
}
/// Returns `true` if the amount of panes in the [`State`] is 0.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the internal state of the given [`Pane`], if it exists.
pub fn get(&self, pane: &Pane) -> Option<&T> {
self.panes.get(pane)
}
/// Returns the internal state of the given [`Pane`] with mutability, if it
/// exists.
pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> {
self.panes.get_mut(pane)
}
/// Returns an iterator over all the panes of the [`State`], alongside its
/// internal state.
pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> {
self.panes.iter()
}
/// Returns a mutable iterator over all the panes of the [`State`],
/// alongside its internal state.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> {
self.panes.iter_mut()
}
/// Returns the layout of the [`State`].
pub fn layout(&self) -> &Node {
&self.internal.layout
}
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one.
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
let regions = self
.internal
.layout
.pane_regions(0.0, Size::new(4096.0, 4096.0));
let current_region = regions.get(pane)?;
let target = match direction {
Direction::Left => {
Point::new(current_region.x - 1.0, current_region.y + 1.0)
}
Direction::Right => Point::new(
current_region.x + current_region.width + 1.0,
current_region.y + 1.0,
),
Direction::Up => {
Point::new(current_region.x + 1.0, current_region.y - 1.0)
}
Direction::Down => Point::new(
current_region.x + 1.0,
current_region.y + current_region.height + 1.0,
),
};
let mut colliding_regions =
regions.iter().filter(|(_, region)| region.contains(target));
let (pane, _) = colliding_regions.next()?;
Some(*pane)
}
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
pub fn split(
&mut self,
axis: Axis,
pane: &Pane,
state: T,
) -> Option<(Pane, Split)> {
let node = self.internal.layout.find(pane)?;
let new_pane = {
self.internal.last_id = self.internal.last_id.checked_add(1)?;
Pane(self.internal.last_id)
};
let new_split = {
self.internal.last_id = self.internal.last_id.checked_add(1)?;
Split(self.internal.last_id)
};
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
let _ = self.maximized.take();
Some((new_pane, new_split))
}
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
/// will need to call this method when handling a [`DragEvent`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
/// [`DragEvent`]: crate::widget::pane_grid::DragEvent
pub fn swap(&mut self, a: &Pane, b: &Pane) {
self.internal.layout.update(&|node| match node {
Node::Split { .. } => {}
Node::Pane(pane) => {
if pane == a {
*node = Node::Pane(*b);
} else if pane == b {
*node = Node::Pane(*a);
}
}
});
}
/// Resizes two panes by setting the position of the provided [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
///
/// If you want to enable resize interactions in your [`PaneGrid`], you will
/// need to call this method when handling a [`ResizeEvent`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
/// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent
pub fn resize(&mut self, split: &Split, ratio: f32) {
let _ = self.internal.layout.resize(split, ratio);
}
/// Closes the given [`Pane`] and returns its internal state and its closest
/// sibling, if it exists.
pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
if self.maximized == Some(*pane) {
let _ = self.maximized.take();
}
if let Some(sibling) = self.internal.layout.remove(pane) {
self.panes.remove(pane).map(|state| (state, sibling))
} else {
None
}
}
/// Maximize the given [`Pane`]. Only this pane will be rendered by the
/// [`PaneGrid`] until [`Self::restore()`] is called.
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub fn maximize(&mut self, pane: &Pane) {
self.maximized = Some(*pane);
}
/// Restore the currently maximized [`Pane`] to it's normal size. All panes
/// will be rendered by the [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub fn restore(&mut self) {
let _ = self.maximized.take();
}
/// Returns the maximized [`Pane`] of the [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub fn maximized(&self) -> Option<Pane> {
self.maximized
}
}
/// The internal state of a [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone)]
pub struct Internal {
layout: Node,
last_id: usize,
}
impl Internal {
/// Initializes the [`Internal`] state of a [`PaneGrid`] from a
/// [`Configuration`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub fn from_configuration<T>(
panes: &mut HashMap<Pane, T>,
content: Configuration<T>,
next_id: usize,
) -> Self {
let (layout, last_id) = match content {
Configuration::Split { axis, ratio, a, b } => {
let Internal {
layout: a,
last_id: next_id,
..
} = Self::from_configuration(panes, *a, next_id);
let Internal {
layout: b,
last_id: next_id,
..
} = Self::from_configuration(panes, *b, next_id);
(
Node::Split {
id: Split(next_id),
axis,
ratio,
a: Box::new(a),
b: Box::new(b),
},
next_id + 1,
)
}
Configuration::Pane(state) => {
let id = Pane(next_id);
let _ = panes.insert(id, state);
(Node::Pane(id), next_id + 1)
}
};
Self { layout, last_id }
}
}
/// The current action of a [`PaneGrid`].
///
/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
/// The [`PaneGrid`] is idle.
///
/// [`PaneGrid`]: crate::widget::PaneGrid
Idle,
/// A [`Pane`] in the [`PaneGrid`] is being dragged.
///
/// [`PaneGrid`]: crate::widget::PaneGrid
Dragging {
/// The [`Pane`] being dragged.
pane: Pane,
/// The starting [`Point`] of the drag interaction.
origin: Point,
},
/// A [`Split`] in the [`PaneGrid`] is being dragged.
///
/// [`PaneGrid`]: crate::widget::PaneGrid
Resizing {
/// The [`Split`] being dragged.
split: Split,
/// The [`Axis`] of the [`Split`].
axis: Axis,
},
}
impl Action {
/// Returns the current [`Pane`] that is being dragged, if any.
pub fn picked_pane(&self) -> Option<(Pane, Point)> {
match *self {
Action::Dragging { pane, origin, .. } => Some((pane, origin)),
_ => None,
}
}
/// Returns the current [`Split`] that is being dragged, if any.
pub fn picked_split(&self) -> Option<(Split, Axis)> {
match *self {
Action::Resizing { split, axis, .. } => Some((split, axis)),
_ => None,
}
}
}
impl Internal {
/// The layout [`Node`] of the [`Internal`] state
pub fn layout(&self) -> &Node {
&self.layout
}
}

View file

@ -1,432 +0,0 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::{self, Tree};
use crate::{
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
};
/// The title bar of a [`Pane`].
///
/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
pub struct TitleBar<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
padding: Padding,
always_show_controls: bool,
style: <Renderer::Theme as container::StyleSheet>::Style,
}
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
/// Creates a new [`TitleBar`] with the given content.
pub fn new<E>(content: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
Self {
content: content.into(),
controls: None,
padding: Padding::ZERO,
always_show_controls: false,
style: Default::default(),
}
}
/// Sets the controls of the [`TitleBar`].
pub fn controls(
mut self,
controls: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.controls = Some(controls.into());
self
}
/// Sets the [`Padding`] of the [`TitleBar`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the style of the [`TitleBar`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
/// Sets whether or not the [`controls`] attached to this [`TitleBar`] are
/// always visible.
///
/// By default, the controls are only visible when the [`Pane`] of this
/// [`TitleBar`] is hovered.
///
/// [`controls`]: Self::controls
/// [`Pane`]: crate::widget::pane_grid::Pane
pub fn always_show_controls(mut self) -> Self {
self.always_show_controls = true;
self
}
}
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
pub(super) fn state(&self) -> Tree {
let children = if let Some(controls) = self.controls.as_ref() {
vec![Tree::new(&self.content), Tree::new(controls)]
} else {
vec![Tree::new(&self.content), Tree::empty()]
};
Tree {
children,
..Tree::empty()
}
}
pub(super) fn diff(&self, tree: &mut Tree) {
if tree.children.len() == 2 {
if let Some(controls) = self.controls.as_ref() {
tree.children[1].diff(controls);
}
tree.children[0].diff(&self.content);
} else {
*tree = self.state();
}
}
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
/// [`Renderer`]: crate::Renderer
pub fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
show_controls: bool,
) {
use container::StyleSheet;
let bounds = layout.bounds();
let style = theme.appearance(&self.style);
let inherited_style = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
};
container::draw_background(renderer, &style, bounds);
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
let mut show_title = true;
if let Some(controls) = &self.controls {
if show_controls || self.always_show_controls {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.as_widget().draw(
&tree.children[1],
renderer,
theme,
&inherited_style,
controls_layout,
cursor_position,
viewport,
);
}
}
if show_title {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&inherited_style,
title_layout,
cursor_position,
viewport,
);
}
}
/// Returns whether the mouse cursor is over the pick area of the
/// [`TitleBar`] or not.
///
/// The whole [`TitleBar`] is a pick area, except its controls.
pub fn is_over_pick_area(
&self,
layout: Layout<'_>,
cursor_position: Point,
) -> bool {
if layout.bounds().contains(cursor_position) {
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
if self.controls.is_some() {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
!controls_layout.bounds().contains(cursor_position)
} else {
!controls_layout.bounds().contains(cursor_position)
&& !title_layout.bounds().contains(cursor_position)
}
} else {
!title_layout.bounds().contains(cursor_position)
}
} else {
false
}
}
pub(crate) fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.pad(self.padding);
let max_size = limits.max();
let title_layout = self
.content
.as_widget()
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
let title_size = title_layout.size();
let mut node = if let Some(controls) = &self.controls {
let mut controls_layout = controls
.as_widget()
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
let controls_size = controls_layout.size();
let space_before_controls = max_size.width - controls_size.width;
let height = title_size.height.max(controls_size.height);
controls_layout.move_to(Point::new(space_before_controls, 0.0));
layout::Node::with_children(
Size::new(max_size.width, height),
vec![title_layout, controls_layout],
)
} else {
layout::Node::with_children(
Size::new(max_size.width, title_size.height),
vec![title_layout],
)
};
node.move_to(Point::new(self.padding.left, self.padding.top));
layout::Node::with_children(node.size().pad(self.padding), vec![node])
}
pub(crate) fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
let mut show_title = true;
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.as_widget().operate(
&mut tree.children[1],
controls_layout,
renderer,
operation,
)
};
if show_title {
self.content.as_widget().operate(
&mut tree.children[0],
title_layout,
renderer,
operation,
)
}
}
pub(crate) fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
let mut show_title = true;
let control_status = if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.as_widget_mut().on_event(
&mut tree.children[1],
event.clone(),
controls_layout,
cursor_position,
renderer,
clipboard,
shell,
)
} else {
event::Status::Ignored
};
let title_status = if show_title {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
title_layout,
cursor_position,
renderer,
clipboard,
shell,
)
} else {
event::Status::Ignored
};
control_status.merge(title_status)
}
pub(crate) fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
let mut children = layout.children();
let padded = children.next().unwrap();
let mut children = padded.children();
let title_layout = children.next().unwrap();
let title_interaction = self.content.as_widget().mouse_interaction(
&tree.children[0],
title_layout,
cursor_position,
viewport,
renderer,
);
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
let controls_interaction = controls.as_widget().mouse_interaction(
&tree.children[1],
controls_layout,
cursor_position,
viewport,
renderer,
);
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
controls_interaction
} else {
controls_interaction.max(title_interaction)
}
} else {
title_interaction
}
}
pub(crate) fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
let mut children = layout.children();
let padded = children.next()?;
let mut children = padded.children();
let title_layout = children.next()?;
let Self {
content, controls, ..
} = self;
let mut states = tree.children.iter_mut();
let title_state = states.next().unwrap();
let controls_state = states.next().unwrap();
content
.as_widget_mut()
.overlay(title_state, title_layout, renderer)
.or_else(move || {
controls.as_mut().and_then(|controls| {
let controls_layout = children.next()?;
controls.as_widget_mut().overlay(
controls_state,
controls_layout,
renderer,
)
})
})
}
}

View file

@ -1,657 +0,0 @@
//! Display a dropdown list of selectable values.
use crate::alignment;
use crate::event::{self, Event};
use crate::keyboard;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::overlay::menu::{self, Menu};
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
use crate::widget::container;
use crate::widget::scrollable;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
Shell, Size, Widget,
};
use std::borrow::Cow;
pub use iced_style::pick_list::{Appearance, StyleSheet};
/// A widget for selecting a single value from a list of options.
#[allow(missing_debug_implementations)]
pub struct PickList<'a, T, Message, Renderer>
where
[T]: ToOwned<Owned = Vec<T>>,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
on_selected: Box<dyn Fn(T) -> Message + 'a>,
options: Cow<'a, [T]>,
placeholder: Option<String>,
selected: Option<T>,
width: Length,
padding: Padding,
text_size: Option<f32>,
font: Option<Renderer::Font>,
handle: Handle<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet,
<Renderer::Theme as menu::StyleSheet>::Style:
From<<Renderer::Theme as StyleSheet>::Style>,
{
/// The default padding of a [`PickList`].
pub const DEFAULT_PADDING: Padding = Padding::new(5.0);
/// Creates a new [`PickList`] with the given list of options, the current
/// selected value, and the message to produce when an option is selected.
pub fn new(
options: impl Into<Cow<'a, [T]>>,
selected: Option<T>,
on_selected: impl Fn(T) -> Message + 'a,
) -> Self {
Self {
on_selected: Box::new(on_selected),
options: options.into(),
placeholder: None,
selected,
width: Length::Shrink,
padding: Self::DEFAULT_PADDING,
text_size: None,
font: None,
handle: Default::default(),
style: Default::default(),
}
}
/// Sets the placeholder of the [`PickList`].
pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
self.placeholder = Some(placeholder.into());
self
}
/// Sets the width of the [`PickList`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the [`Padding`] of the [`PickList`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the text size of the [`PickList`].
pub fn text_size(mut self, size: impl Into<Pixels>) -> Self {
self.text_size = Some(size.into().0);
self
}
/// Sets the font of the [`PickList`].
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
/// Sets the [`Handle`] of the [`PickList`].
pub fn handle(mut self, handle: Handle<Renderer::Font>) -> Self {
self.handle = handle;
self
}
/// Sets the style of the [`PickList`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
for PickList<'a, T, Message, Renderer>
where
T: Clone + ToString + Eq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet,
<Renderer::Theme as menu::StyleSheet>::Style:
From<<Renderer::Theme as StyleSheet>::Style>,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State<T>>()
}
fn state(&self) -> tree::State {
tree::State::new(State::<T>::new())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
self.width,
self.padding,
self.text_size,
self.font,
self.placeholder.as_deref(),
&self.options,
)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
update(
event,
layout,
cursor_position,
shell,
self.on_selected.as_ref(),
self.selected.as_ref(),
&self.options,
|| tree.state.downcast_mut::<State<T>>(),
)
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
draw(
renderer,
theme,
layout,
cursor_position,
self.padding,
self.text_size,
font,
self.placeholder.as_deref(),
self.selected.as_ref(),
&self.handle,
&self.style,
|| tree.state.downcast_ref::<State<T>>(),
)
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
let state = tree.state.downcast_mut::<State<T>>();
overlay(
layout,
state,
self.padding,
self.text_size,
self.font.unwrap_or_else(|| renderer.default_font()),
&self.options,
self.style.clone(),
)
}
}
impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: Clone + ToString + Eq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet,
<Renderer::Theme as menu::StyleSheet>::Style:
From<<Renderer::Theme as StyleSheet>::Style>,
{
fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
Self::new(pick_list)
}
}
/// The local state of a [`PickList`].
#[derive(Debug)]
pub struct State<T> {
menu: menu::State,
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<usize>,
last_selection: Option<T>,
}
impl<T> State<T> {
/// Creates a new [`State`] for a [`PickList`].
pub fn new() -> Self {
Self {
menu: menu::State::default(),
keyboard_modifiers: keyboard::Modifiers::default(),
is_open: bool::default(),
hovered_option: Option::default(),
last_selection: Option::default(),
}
}
}
impl<T> Default for State<T> {
fn default() -> Self {
Self::new()
}
}
/// The handle to the right side of the [`PickList`].
#[derive(Debug, Clone, PartialEq)]
pub enum Handle<Font> {
/// Displays an arrow icon (▼).
///
/// This is the default.
Arrow {
/// Font size of the content.
size: Option<f32>,
},
/// A custom static handle.
Static(Icon<Font>),
/// A custom dynamic handle.
Dynamic {
/// The [`Icon`] used when [`PickList`] is closed.
closed: Icon<Font>,
/// The [`Icon`] used when [`PickList`] is open.
open: Icon<Font>,
},
/// No handle will be shown.
None,
}
impl<Font> Default for Handle<Font> {
fn default() -> Self {
Self::Arrow { size: None }
}
}
/// The icon of a [`Handle`].
#[derive(Debug, Clone, PartialEq)]
pub struct Icon<Font> {
/// Font that will be used to display the `code_point`,
pub font: Font,
/// The unicode code point that will be used as the icon.
pub code_point: char,
/// Font size of the content.
pub size: Option<f32>,
}
/// Computes the layout of a [`PickList`].
pub fn layout<Renderer, T>(
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
padding: Padding,
text_size: Option<f32>,
font: Option<Renderer::Font>,
placeholder: Option<&str>,
options: &[T],
) -> layout::Node
where
Renderer: text::Renderer,
T: ToString,
{
use std::f32;
let limits = limits.width(width).height(Length::Shrink).pad(padding);
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
let max_width = match width {
Length::Shrink => {
let measure = |label: &str| -> f32 {
let (width, _) = renderer.measure(
label,
text_size,
font.unwrap_or_else(|| renderer.default_font()),
Size::new(f32::INFINITY, f32::INFINITY),
);
width.round()
};
let labels = options.iter().map(ToString::to_string);
let labels_width = labels
.map(|label| measure(&label))
.fold(100.0, |candidate, current| current.max(candidate));
let placeholder_width = placeholder.map(measure).unwrap_or(100.0);
labels_width.max(placeholder_width)
}
_ => 0.0,
};
let size = {
let intrinsic =
Size::new(max_width + text_size + padding.left, text_size * 1.2);
limits.resolve(intrinsic).pad(padding)
};
layout::Node::new(size)
}
/// Processes an [`Event`] and updates the [`State`] of a [`PickList`]
/// accordingly.
pub fn update<'a, T, Message>(
event: Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
on_selected: &dyn Fn(T) -> Message,
selected: Option<&T>,
options: &[T],
state: impl FnOnce() -> &'a mut State<T>,
) -> event::Status
where
T: PartialEq + Clone + 'a,
{
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
let event_status = if state.is_open {
// Event wasn't processed by overlay, so cursor was clicked either outside it's
// bounds or on the drop-down, either way we close the overlay.
state.is_open = false;
event::Status::Captured
} else if layout.bounds().contains(cursor_position) {
state.is_open = true;
state.hovered_option =
options.iter().position(|option| Some(option) == selected);
event::Status::Captured
} else {
event::Status::Ignored
};
if let Some(last_selection) = state.last_selection.take() {
shell.publish((on_selected)(last_selection));
state.is_open = false;
event::Status::Captured
} else {
event_status
}
}
Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Lines { y, .. },
}) => {
let state = state();
if state.keyboard_modifiers.command()
&& layout.bounds().contains(cursor_position)
&& !state.is_open
{
fn find_next<'a, T: PartialEq>(
selected: &'a T,
mut options: impl Iterator<Item = &'a T>,
) -> Option<&'a T> {
let _ = options.find(|&option| option == selected);
options.next()
}
let next_option = if y < 0.0 {
if let Some(selected) = selected {
find_next(selected, options.iter())
} else {
options.first()
}
} else if y > 0.0 {
if let Some(selected) = selected {
find_next(selected, options.iter().rev())
} else {
options.last()
}
} else {
None
};
if let Some(next_option) = next_option {
shell.publish((on_selected)(next_option.clone()));
}
event::Status::Captured
} else {
event::Status::Ignored
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
let state = state();
state.keyboard_modifiers = modifiers;
event::Status::Ignored
}
_ => event::Status::Ignored,
}
}
/// Returns the current [`mouse::Interaction`] of a [`PickList`].
pub fn mouse_interaction(
layout: Layout<'_>,
cursor_position: Point,
) -> mouse::Interaction {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
if is_mouse_over {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
/// Returns the current overlay of a [`PickList`].
pub fn overlay<'a, T, Message, Renderer>(
layout: Layout<'_>,
state: &'a mut State<T>,
padding: Padding,
text_size: Option<f32>,
font: Renderer::Font,
options: &'a [T],
style: <Renderer::Theme as StyleSheet>::Style,
) -> Option<overlay::Element<'a, Message, Renderer>>
where
T: Clone + ToString,
Message: 'a,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet
+ container::StyleSheet,
<Renderer::Theme as menu::StyleSheet>::Style:
From<<Renderer::Theme as StyleSheet>::Style>,
{
if state.is_open {
let bounds = layout.bounds();
let mut menu = Menu::new(
&mut state.menu,
options,
&mut state.hovered_option,
&mut state.last_selection,
)
.width(bounds.width)
.padding(padding)
.font(font)
.style(style);
if let Some(text_size) = text_size {
menu = menu.text_size(text_size);
}
Some(menu.overlay(layout.position(), bounds.height))
} else {
None
}
}
/// Draws a [`PickList`].
pub fn draw<'a, T, Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
cursor_position: Point,
padding: Padding,
text_size: Option<f32>,
font: Renderer::Font,
placeholder: Option<&str>,
selected: Option<&T>,
handle: &Handle<Renderer::Font>,
style: &<Renderer::Theme as StyleSheet>::Style,
state: impl FnOnce() -> &'a State<T>,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
T: ToString + 'a,
{
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let is_selected = selected.is_some();
let style = if is_mouse_over {
theme.hovered(style)
} else {
theme.active(style)
};
renderer.fill_quad(
renderer::Quad {
bounds,
border_color: style.border_color,
border_width: style.border_width,
border_radius: style.border_radius.into(),
},
style.background,
);
let handle = match handle {
Handle::Arrow { size } => {
Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size))
}
Handle::Static(Icon {
font,
code_point,
size,
}) => Some((*font, *code_point, *size)),
Handle::Dynamic { open, closed } => {
if state().is_open {
Some((open.font, open.code_point, open.size))
} else {
Some((closed.font, closed.code_point, closed.size))
}
}
Handle::None => None,
};
if let Some((font, code_point, size)) = handle {
let size = size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(Text {
content: &code_point.to_string(),
size,
font,
color: style.handle_color,
bounds: Rectangle {
x: bounds.x + bounds.width - padding.horizontal(),
y: bounds.center_y(),
height: size * 1.2,
..bounds
},
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Center,
});
}
let label = selected.map(ToString::to_string);
if let Some(label) = label.as_deref().or(placeholder) {
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(Text {
content: label,
size: text_size,
font,
color: if is_selected {
style.text_color
} else {
style.placeholder_color
},
bounds: Rectangle {
x: bounds.x + padding.left,
y: bounds.center_y(),
width: bounds.width - padding.horizontal(),
height: text_size * 1.2,
},
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
});
}
}

View file

@ -1,168 +0,0 @@
//! Provide progress feedback to your users.
use crate::layout;
use crate::renderer;
use crate::widget::Tree;
use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget};
use std::ops::RangeInclusive;
pub use iced_style::progress_bar::{Appearance, StyleSheet};
/// A bar that displays progress.
///
/// # Example
/// ```
/// # type ProgressBar = iced_native::widget::ProgressBar<iced_native::renderer::Null>;
/// let value = 50.0;
///
/// ProgressBar::new(0.0..=100.0, value);
/// ```
///
/// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png)
#[allow(missing_debug_implementations)]
pub struct ProgressBar<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
range: RangeInclusive<f32>,
value: f32,
width: Length,
height: Option<Length>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<Renderer> ProgressBar<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default height of a [`ProgressBar`].
pub const DEFAULT_HEIGHT: f32 = 30.0;
/// Creates a new [`ProgressBar`].
///
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
ProgressBar {
value: value.clamp(*range.start(), *range.end()),
range,
width: Length::Fill,
height: None,
style: Default::default(),
}
}
/// Sets the width of the [`ProgressBar`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`ProgressBar`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = Some(height.into());
self
}
/// Sets the style of the [`ProgressBar`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT))
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits
.width(self.width)
.height(self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)));
let size = limits.resolve(Size::ZERO);
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,
) {
let bounds = layout.bounds();
let (range_start, range_end) = self.range.clone().into_inner();
let active_progress_width = if range_start >= range_end {
0.0
} else {
bounds.width * (self.value - range_start)
/ (range_end - range_start)
};
let style = theme.appearance(&self.style);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle { ..bounds },
border_radius: style.border_radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.background,
);
if active_progress_width > 0.0 {
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
width: active_progress_width,
..bounds
},
border_radius: style.border_radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.bar,
);
}
}
}
impl<'a, Message, Renderer> From<ProgressBar<Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
progress_bar: ProgressBar<Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(progress_bar)
}
}

View file

@ -1,299 +0,0 @@
//! Create choices using radio buttons.
use crate::alignment;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::touch;
use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point,
Rectangle, Shell, Widget,
};
pub use iced_style::radio::{Appearance, StyleSheet};
/// A circular button representing a choice.
///
/// # Example
/// ```
/// # type Radio<Message> =
/// # iced_native::widget::Radio<Message, iced_native::renderer::Null>;
/// #
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
/// A,
/// B,
/// }
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// RadioSelected(Choice),
/// }
///
/// let selected_choice = Some(Choice::A);
///
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
///
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
/// ```
///
/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Radio<Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
is_selected: bool,
on_click: Message,
label: String,
width: Length,
size: f32,
spacing: f32,
text_size: Option<f32>,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<Message, Renderer> Radio<Message, Renderer>
where
Message: Clone,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default size of a [`Radio`] button.
pub const DEFAULT_SIZE: f32 = 28.0;
/// The default spacing of a [`Radio`] button.
pub const DEFAULT_SPACING: f32 = 15.0;
/// Creates a new [`Radio`] button.
///
/// It expects:
/// * the value related to the [`Radio`] button
/// * the label of the [`Radio`] button
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
pub fn new<F, V>(
value: V,
label: impl Into<String>,
selected: Option<V>,
f: F,
) -> Self
where
V: Eq + Copy,
F: FnOnce(V) -> Message,
{
Radio {
is_selected: Some(value) == selected,
on_click: f(value),
label: label.into(),
width: Length::Shrink,
size: Self::DEFAULT_SIZE,
spacing: Self::DEFAULT_SPACING, //15
text_size: None,
font: None,
style: Default::default(),
}
}
/// Sets the size of the [`Radio`] button.
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = size.into().0;
self
}
/// Sets the width of the [`Radio`] button.
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the spacing between the [`Radio`] button and the text.
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.spacing = spacing.into().0;
self
}
/// Sets the text size of the [`Radio`] button.
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
self.text_size = Some(text_size.into().0);
self
}
/// Sets the text font of the [`Radio`] button.
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = Some(font.into());
self
}
/// Sets the style of the [`Radio`] button.
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
where
Message: Clone,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
Row::<(), Renderer>::new()
.width(self.width)
.spacing(self.spacing)
.align_items(Alignment::Center)
.push(Row::new().width(self.size).height(self.size))
.push(Text::new(&self.label).width(self.width).size(
self.text_size.unwrap_or_else(|| renderer.default_size()),
))
.layout(renderer, limits)
}
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 {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
shell.publish(self.on_click.clone());
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let mut children = layout.children();
let custom_style = if is_mouse_over {
theme.hovered(&self.style, self.is_selected)
} else {
theme.active(&self.style, self.is_selected)
};
{
let layout = children.next().unwrap();
let bounds = layout.bounds();
let size = bounds.width;
let dot_size = size / 2.0;
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: (size / 2.0).into(),
border_width: custom_style.border_width,
border_color: custom_style.border_color,
},
custom_style.background,
);
if self.is_selected {
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x + dot_size / 2.0,
y: bounds.y + dot_size / 2.0,
width: bounds.width - dot_size,
height: bounds.height - dot_size,
},
border_radius: (dot_size / 2.0).into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
custom_style.dot_color,
);
}
}
{
let label_layout = children.next().unwrap();
widget::text::draw(
renderer,
style,
label_layout,
&self.label,
self.text_size,
self.font,
widget::text::Appearance {
color: custom_style.text_color,
},
alignment::Horizontal::Left,
alignment::Vertical::Center,
);
}
}
}
impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(radio)
}
}

View file

@ -1,253 +0,0 @@
//! Distribute content horizontally.
use crate::event::{self, Event};
use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::{Operation, Tree};
use crate::{
Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle,
Shell, Widget,
};
/// A container that distributes its contents horizontally.
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
spacing: f32,
padding: Padding,
width: Length,
height: Length,
align_items: Alignment,
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Row {
spacing: 0.0,
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
align_items: Alignment::Start,
children,
}
}
/// Sets the horizontal spacing _between_ elements.
///
/// Custom margins per element do not exist in iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
self.spacing = amount.into().0;
self
}
/// Sets the [`Padding`] of the [`Row`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.padding = padding.into();
self
}
/// Sets the width of the [`Row`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Row`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
pub fn push(
mut self,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
self
}
}
impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> {
fn default() -> Self {
Self::new()
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn children(&self) -> Vec<Tree> {
self.children.iter().map(Tree::new).collect()
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&self.children)
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
layout::flex::resolve(
layout::flex::Axis::Horizontal,
renderer,
&limits,
self.padding,
self.spacing,
self.align_items,
&self.children,
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.children
.iter()
.zip(&mut tree.children)
.zip(layout.children())
.for_each(|((child, state), layout)| {
child
.as_widget()
.operate(state, layout, renderer, 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<'_, Message>,
) -> event::Status {
self.children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor_position,
renderer,
clipboard,
shell,
)
})
.fold(event::Status::Ignored, event::Status::merge)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.children
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
state,
layout,
cursor_position,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child.as_widget().draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
overlay::from_children(&mut self.children, tree, layout, renderer)
}
}
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: crate::Renderer + 'a,
{
fn from(row: Row<'a, Message, Renderer>) -> Self {
Self::new(row)
}
}

View file

@ -1,147 +0,0 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::layout;
use crate::renderer;
use crate::widget::Tree;
use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
};
pub use iced_style::rule::{Appearance, FillMode, StyleSheet};
/// Display a horizontal or vertical rule for dividing content.
#[allow(missing_debug_implementations)]
pub struct Rule<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
width: Length,
height: Length,
is_horizontal: bool,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<Renderer> Rule<Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a horizontal [`Rule`] with the given height.
pub fn horizontal(height: impl Into<Pixels>) -> Self {
Rule {
width: Length::Fill,
height: Length::Fixed(height.into().0),
is_horizontal: true,
style: Default::default(),
}
}
/// Creates a vertical [`Rule`] with the given width.
pub fn vertical(width: impl Into<Pixels>) -> Self {
Rule {
width: Length::Fixed(width.into().0),
height: Length::Fill,
is_horizontal: false,
style: Default::default(),
}
}
/// Sets the style of the [`Rule`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
where
Renderer: crate::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);
layout::Node::new(limits.resolve(Size::ZERO))
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
let style = theme.appearance(&self.style);
let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
- (style.width as f32 / 2.0))
.round();
let (offset, line_width) = style.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset;
Rectangle {
x: line_x,
y: line_y,
width: line_width,
height: style.width as f32,
}
} else {
let line_x = (bounds.x + (bounds.width / 2.0)
- (style.width as f32 / 2.0))
.round();
let (offset, line_height) = style.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset;
Rectangle {
x: line_x,
y: line_y,
width: style.width as f32,
height: line_height,
}
};
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: style.radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.color,
);
}
}
impl<'a, Message, Renderer> From<Rule<Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(rule)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,473 +0,0 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::touch;
use crate::widget::tree::{self, Tree};
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
Rectangle, Shell, Size, Widget,
};
use std::ops::RangeInclusive;
pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
/// The [`Slider`] range of numeric values is generic and its step size defaults
/// to 1 unit.
///
/// # Example
/// ```
/// # use iced_native::widget::slider;
/// # use iced_native::renderer::Null;
/// #
/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
///
/// let value = 50.0;
///
/// Slider::new(0.0..=100.0, value, Message::SliderChanged);
/// ```
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Slider<'a, T, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
range: RangeInclusive<T>,
step: T,
value: T,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
width: Length,
height: f32,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default height of a [`Slider`].
pub const DEFAULT_HEIGHT: f32 = 22.0;
/// Creates a new [`Slider`].
///
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`Slider`]
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
value
} else {
*range.start()
};
let value = if value <= *range.end() {
value
} else {
*range.end()
};
Slider {
value,
range,
step: T::from(1),
on_change: Box::new(on_change),
on_release: None,
width: Length::Fill,
height: Self::DEFAULT_HEIGHT,
style: Default::default(),
}
}
/// Sets the release message of the [`Slider`].
/// This is called when the mouse is released from the slider.
///
/// Typically, the user's interaction with the slider is finished when this message is produced.
/// This is useful if you need to spawn a long-running task from the slider's result, where
/// the default on_change message could create too many events.
pub fn on_release(mut self, on_release: Message) -> Self {
self.on_release = Some(on_release);
self
}
/// Sets the width of the [`Slider`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Slider`].
pub fn height(mut self, height: impl Into<Pixels>) -> Self {
self.height = height.into().0;
self
}
/// Sets the style of the [`Slider`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
/// Sets the step size of the [`Slider`].
pub fn step(mut self, step: T) -> Self {
self.step = step;
self
}
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for Slider<'a, T, Message, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
update(
event,
layout,
cursor_position,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
&self.range,
self.step,
self.on_change.as_ref(),
&self.on_release,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
cursor_position,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
theme,
&self.style,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
layout,
cursor_position,
tree.state.downcast_ref::<State>(),
)
}
}
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
}
/// Processes an [`Event`] and updates the [`State`] of a [`Slider`]
/// accordingly.
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
range: &RangeInclusive<T>,
step: T,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
) -> event::Status
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
{
let is_dragging = state.is_dragging;
let mut change = || {
let bounds = layout.bounds();
let new_value = if cursor_position.x <= bounds.x {
*range.start()
} else if cursor_position.x >= bounds.x + bounds.width {
*range.end()
} else {
let step = step.into();
let start = (*range.start()).into();
let end = (*range.end()).into();
let percent = f64::from(cursor_position.x - bounds.x)
/ f64::from(bounds.width);
let steps = (percent * (end - start) / step).round();
let value = steps * step + start;
if let Some(value) = T::from_f64(value) {
value
} else {
return;
}
};
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
shell.publish((on_change)(new_value));
*value = new_value;
}
};
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
change();
state.is_dragging = true;
return event::Status::Captured;
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. })
| Event::Touch(touch::Event::FingerLost { .. }) => {
if is_dragging {
if let Some(on_release) = on_release.clone() {
shell.publish(on_release);
}
state.is_dragging = false;
return event::Status::Captured;
}
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if is_dragging {
change();
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
/// Draws a [`Slider`].
pub fn draw<T, R>(
renderer: &mut R,
layout: Layout<'_>,
cursor_position: Point,
state: &State,
value: T,
range: &RangeInclusive<T>,
style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
style: &<R::Theme as StyleSheet>::Style,
) where
T: Into<f64> + Copy,
R: crate::Renderer,
R::Theme: StyleSheet,
{
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let style = if state.is_dragging {
style_sheet.dragging(style)
} else if is_mouse_over {
style_sheet.hovered(style)
} else {
style_sheet.active(style)
};
let rail_y = bounds.y + (bounds.height / 2.0).round();
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y - 1.0,
width: bounds.width,
height: 2.0,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.rail_colors.0,
);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: rail_y + 1.0,
width: bounds.width,
height: 2.0,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color(style.rail_colors.1),
);
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
{
HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
HandleShape::Rectangle {
width,
border_radius,
} => (f32::from(width), bounds.height, border_radius),
};
let value = value.into() as f32;
let (range_start, range_end) = {
let (start, end) = range.clone().into_inner();
(start.into() as f32, end.into() as f32)
};
let handle_offset = if range_start >= range_end {
0.0
} else {
(bounds.width - handle_width) * (value - range_start)
/ (range_end - range_start)
};
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x + handle_offset.round(),
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
},
border_radius: handle_border_radius.into(),
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
style.handle.color,
);
}
/// Computes the current [`mouse::Interaction`] of a [`Slider`].
pub fn mouse_interaction(
layout: Layout<'_>,
cursor_position: Point,
state: &State,
) -> mouse::Interaction {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
if state.is_dragging {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
mouse::Interaction::default()
}
}
/// The local state of a [`Slider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_dragging: bool,
}
impl State {
/// Creates a new [`State`].
pub fn new() -> State {
State::default()
}
}

View file

@ -1,85 +0,0 @@
//! Distribute content vertically.
use crate::layout;
use crate::renderer;
use crate::widget::Tree;
use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
/// An amount of empty space.
///
/// It can be useful if you want to fill some space with nothing.
#[derive(Debug)]
pub struct Space {
width: Length,
height: Length,
}
impl Space {
/// Creates an amount of empty [`Space`] with the given width and height.
pub fn new(width: impl Into<Length>, height: impl Into<Length>) -> Self {
Space {
width: width.into(),
height: height.into(),
}
}
/// Creates an amount of horizontal [`Space`].
pub fn with_width(width: impl Into<Length>) -> Self {
Space {
width: width.into(),
height: Length::Shrink,
}
}
/// Creates an amount of vertical [`Space`].
pub fn with_height(height: impl Into<Length>) -> Self {
Space {
width: Length::Shrink,
height: height.into(),
}
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Space
where
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
layout::Node::new(limits.resolve(Size::ZERO))
}
fn draw(
&self,
_state: &Tree,
_renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
}
}
impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Message: 'a,
{
fn from(space: Space) -> Element<'a, Message, Renderer> {
Element::new(space)
}
}

View file

@ -1,195 +0,0 @@
//! Display vector graphics in your application.
use crate::layout;
use crate::renderer;
use crate::svg;
use crate::widget::Tree;
use crate::{
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use std::path::PathBuf;
pub use iced_style::svg::{Appearance, StyleSheet};
pub use svg::Handle;
/// A vector graphics image.
///
/// An [`Svg`] image resizes smoothly without losing any quality.
///
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
#[allow(missing_debug_implementations)]
pub struct Svg<Renderer>
where
Renderer: svg::Renderer,
Renderer::Theme: StyleSheet,
{
handle: Handle,
width: Length,
height: Length,
content_fit: ContentFit,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<Renderer> Svg<Renderer>
where
Renderer: svg::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Svg`] from the given [`Handle`].
pub fn new(handle: impl Into<Handle>) -> Self {
Svg {
handle: handle.into(),
width: Length::Fill,
height: Length::Shrink,
content_fit: ContentFit::Contain,
style: Default::default(),
}
}
/// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path.
#[must_use]
pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self::new(Handle::from_path(path))
}
/// Sets the width of the [`Svg`].
#[must_use]
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the height of the [`Svg`].
#[must_use]
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the [`ContentFit`] of the [`Svg`].
///
/// Defaults to [`ContentFit::Contain`]
#[must_use]
pub fn content_fit(self, content_fit: ContentFit) -> Self {
Self {
content_fit,
..self
}
}
/// Sets the style variant of this [`Svg`].
#[must_use]
pub fn style(
mut self,
style: <Renderer::Theme as StyleSheet>::Style,
) -> Self {
self.style = style;
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer>
where
Renderer: svg::Renderer,
Renderer::Theme: iced_style::svg::StyleSheet,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
// The raw w/h of the underlying image
let Size { width, height } = renderer.dimensions(&self.handle);
let image_size = Size::new(width as f32, height as f32);
// The size to be available to the widget prior to `Shrink`ing
let raw_size = limits
.width(self.width)
.height(self.height)
.resolve(image_size);
// The uncropped size of the image when fit to the bounds above
let full_size = self.content_fit.fit(image_size, raw_size);
// Shrink the widget to fit the resized image, if requested
let final_size = Size {
width: match self.width {
Length::Shrink => f32::min(raw_size.width, full_size.width),
_ => raw_size.width,
},
height: match self.height {
Length::Shrink => f32::min(raw_size.height, full_size.height),
_ => raw_size.height,
},
};
layout::Node::new(final_size)
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
let Size { width, height } = renderer.dimensions(&self.handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
let render = |renderer: &mut Renderer| {
let offset = Vector::new(
(bounds.width - adjusted_fit.width).max(0.0) / 2.0,
(bounds.height - adjusted_fit.height).max(0.0) / 2.0,
);
let drawing_bounds = Rectangle {
width: adjusted_fit.width,
height: adjusted_fit.height,
..bounds
};
let appearance = theme.appearance(&self.style);
renderer.draw(
self.handle.clone(),
appearance.color,
drawing_bounds + offset,
);
};
if adjusted_fit.width > bounds.width
|| adjusted_fit.height > bounds.height
{
renderer.with_layer(bounds, render);
} else {
render(renderer);
}
}
}
impl<'a, Message, Renderer> From<Svg<Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: svg::Renderer + 'a,
Renderer::Theme: iced_style::svg::StyleSheet,
{
fn from(icon: Svg<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(icon)
}
}

View file

@ -1,263 +0,0 @@
//! 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::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget};
use std::borrow::Cow;
pub use iced_style::text::{Appearance, StyleSheet};
/// A paragraph of text.
///
/// # Example
///
/// ```
/// # use iced_native::Color;
/// #
/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .size(40)
/// .style(Color::from([0.0, 0.0, 1.0]));
/// ```
///
/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[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::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, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
where
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(contents: &'a str) -> Self {
Text::new(contents).into()
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,189 +0,0 @@
//! Track the cursor of a text input.
use crate::widget::text_input::Value;
/// The cursor of a text input.
#[derive(Debug, Copy, Clone)]
pub struct Cursor {
state: State,
}
/// The state of a [`Cursor`].
#[derive(Debug, Copy, Clone)]
pub enum State {
/// Cursor without a selection
Index(usize),
/// Cursor selecting a range of text
Selection {
/// The start of the selection
start: usize,
/// The end of the selection
end: usize,
},
}
impl Default for Cursor {
fn default() -> Self {
Cursor {
state: State::Index(0),
}
}
}
impl Cursor {
/// Returns the [`State`] of the [`Cursor`].
pub fn state(&self, value: &Value) -> State {
match self.state {
State::Index(index) => State::Index(index.min(value.len())),
State::Selection { start, end } => {
let start = start.min(value.len());
let end = end.min(value.len());
if start == end {
State::Index(start)
} else {
State::Selection { start, end }
}
}
}
}
/// Returns the current selection of the [`Cursor`] for the given [`Value`].
///
/// `start` is guaranteed to be <= than `end`.
pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
match self.state(value) {
State::Selection { start, end } => {
Some((start.min(end), start.max(end)))
}
_ => None,
}
}
pub(crate) fn move_to(&mut self, position: usize) {
self.state = State::Index(position);
}
pub(crate) fn move_right(&mut self, value: &Value) {
self.move_right_by_amount(value, 1)
}
pub(crate) fn move_right_by_words(&mut self, value: &Value) {
self.move_to(value.next_end_of_word(self.right(value)))
}
pub(crate) fn move_right_by_amount(
&mut self,
value: &Value,
amount: usize,
) {
match self.state(value) {
State::Index(index) => {
self.move_to(index.saturating_add(amount).min(value.len()))
}
State::Selection { start, end } => self.move_to(end.max(start)),
}
}
pub(crate) fn move_left(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) if index > 0 => self.move_to(index - 1),
State::Selection { start, end } => self.move_to(start.min(end)),
_ => self.move_to(0),
}
}
pub(crate) fn move_left_by_words(&mut self, value: &Value) {
self.move_to(value.previous_start_of_word(self.left(value)));
}
pub(crate) fn select_range(&mut self, start: usize, end: usize) {
if start == end {
self.state = State::Index(start);
} else {
self.state = State::Selection { start, end };
}
}
pub(crate) fn select_left(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) if index > 0 => {
self.select_range(index, index - 1)
}
State::Selection { start, end } if end > 0 => {
self.select_range(start, end - 1)
}
_ => {}
}
}
pub(crate) fn select_right(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) if index < value.len() => {
self.select_range(index, index + 1)
}
State::Selection { start, end } if end < value.len() => {
self.select_range(start, end + 1)
}
_ => {}
}
}
pub(crate) fn select_left_by_words(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) => {
self.select_range(index, value.previous_start_of_word(index))
}
State::Selection { start, end } => {
self.select_range(start, value.previous_start_of_word(end))
}
}
}
pub(crate) fn select_right_by_words(&mut self, value: &Value) {
match self.state(value) {
State::Index(index) => {
self.select_range(index, value.next_end_of_word(index))
}
State::Selection { start, end } => {
self.select_range(start, value.next_end_of_word(end))
}
}
}
pub(crate) fn select_all(&mut self, value: &Value) {
self.select_range(0, value.len());
}
pub(crate) fn start(&self, value: &Value) -> usize {
let start = match self.state {
State::Index(index) => index,
State::Selection { start, .. } => start,
};
start.min(value.len())
}
pub(crate) fn end(&self, value: &Value) -> usize {
let end = match self.state {
State::Index(index) => index,
State::Selection { end, .. } => end,
};
end.min(value.len())
}
fn left(&self, value: &Value) -> usize {
match self.state(value) {
State::Index(index) => index,
State::Selection { start, end } => start.min(end),
}
}
fn right(&self, value: &Value) -> usize {
match self.state(value) {
State::Index(index) => index,
State::Selection { start, end } => start.max(end),
}
}
}

View file

@ -1,70 +0,0 @@
use crate::widget::text_input::{Cursor, Value};
pub struct Editor<'a> {
value: &'a mut Value,
cursor: &'a mut Cursor,
}
impl<'a> Editor<'a> {
pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> {
Editor { value, cursor }
}
pub fn contents(&self) -> String {
self.value.to_string()
}
pub fn insert(&mut self, character: char) {
if let Some((left, right)) = self.cursor.selection(self.value) {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
self.value.insert(self.cursor.end(self.value), character);
self.cursor.move_right(self.value);
}
pub fn paste(&mut self, content: Value) {
let length = content.len();
if let Some((left, right)) = self.cursor.selection(self.value) {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
self.value.insert_many(self.cursor.end(self.value), content);
self.cursor.move_right_by_amount(self.value, length);
}
pub fn backspace(&mut self) {
match self.cursor.selection(self.value) {
Some((start, end)) => {
self.cursor.move_left(self.value);
self.value.remove_many(start, end);
}
None => {
let start = self.cursor.start(self.value);
if start > 0 {
self.cursor.move_left(self.value);
self.value.remove(start - 1);
}
}
}
}
pub fn delete(&mut self) {
match self.cursor.selection(self.value) {
Some(_) => {
self.backspace();
}
None => {
let end = self.cursor.end(self.value);
if end < self.value.len() {
self.value.remove(end);
}
}
}
}
}

View file

@ -1,133 +0,0 @@
use unicode_segmentation::UnicodeSegmentation;
/// The value of a [`TextInput`].
///
/// [`TextInput`]: crate::widget::TextInput
// TODO: Reduce allocations, cache results (?)
#[derive(Debug, Clone)]
pub struct Value {
graphemes: Vec<String>,
}
impl Value {
/// Creates a new [`Value`] from a string slice.
pub fn new(string: &str) -> Self {
let graphemes = UnicodeSegmentation::graphemes(string, true)
.map(String::from)
.collect();
Self { graphemes }
}
/// Returns whether the [`Value`] is empty or not.
///
/// A [`Value`] is empty when it contains no graphemes.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the total amount of graphemes in the [`Value`].
pub fn len(&self) -> usize {
self.graphemes.len()
}
/// Returns the position of the previous start of a word from the given
/// grapheme `index`.
pub fn previous_start_of_word(&self, index: usize) -> usize {
let previous_string =
&self.graphemes[..index.min(self.graphemes.len())].concat();
UnicodeSegmentation::split_word_bound_indices(previous_string as &str)
.filter(|(_, word)| !word.trim_start().is_empty())
.next_back()
.map(|(i, previous_word)| {
index
- UnicodeSegmentation::graphemes(previous_word, true)
.count()
- UnicodeSegmentation::graphemes(
&previous_string[i + previous_word.len()..] as &str,
true,
)
.count()
})
.unwrap_or(0)
}
/// Returns the position of the next end of a word from the given grapheme
/// `index`.
pub fn next_end_of_word(&self, index: usize) -> usize {
let next_string = &self.graphemes[index..].concat();
UnicodeSegmentation::split_word_bound_indices(next_string as &str)
.find(|(_, word)| !word.trim_start().is_empty())
.map(|(i, next_word)| {
index
+ UnicodeSegmentation::graphemes(next_word, true).count()
+ UnicodeSegmentation::graphemes(
&next_string[..i] as &str,
true,
)
.count()
})
.unwrap_or(self.len())
}
/// Returns a new [`Value`] containing the graphemes from `start` until the
/// given `end`.
pub fn select(&self, start: usize, end: usize) -> Self {
let graphemes =
self.graphemes[start.min(self.len())..end.min(self.len())].to_vec();
Self { graphemes }
}
/// Returns a new [`Value`] containing the graphemes until the given
/// `index`.
pub fn until(&self, index: usize) -> Self {
let graphemes = self.graphemes[..index.min(self.len())].to_vec();
Self { graphemes }
}
/// Converts the [`Value`] into a `String`.
pub fn to_string(&self) -> String {
self.graphemes.concat()
}
/// Inserts a new `char` at the given grapheme `index`.
pub fn insert(&mut self, index: usize, c: char) {
self.graphemes.insert(index, c.to_string());
self.graphemes =
UnicodeSegmentation::graphemes(&self.to_string() as &str, true)
.map(String::from)
.collect();
}
/// Inserts a bunch of graphemes at the given grapheme `index`.
pub fn insert_many(&mut self, index: usize, mut value: Value) {
let _ = self
.graphemes
.splice(index..index, value.graphemes.drain(..));
}
/// Removes the grapheme at the given `index`.
pub fn remove(&mut self, index: usize) {
let _ = self.graphemes.remove(index);
}
/// Removes the graphemes from `start` to `end`.
pub fn remove_many(&mut self, start: usize, end: usize) {
let _ = self.graphemes.splice(start..end, std::iter::empty());
}
/// Returns a new [`Value`] with all its graphemes replaced with the
/// dot ('•') character.
pub fn secure(&self) -> Self {
Self {
graphemes: std::iter::repeat(String::from(""))
.take(self.graphemes.len())
.collect(),
}
}
}

View file

@ -1,324 +0,0 @@
//! Show toggle controls using togglers.
use crate::alignment;
use crate::event;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point,
Rectangle, Shell, Widget,
};
pub use iced_style::toggler::{Appearance, StyleSheet};
/// A toggler widget.
///
/// # Example
///
/// ```
/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>;
/// #
/// pub enum Message {
/// TogglerToggled(bool),
/// }
///
/// let is_toggled = true;
///
/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b));
/// ```
#[allow(missing_debug_implementations)]
pub struct Toggler<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
is_toggled: bool,
on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
label: Option<String>,
width: Length,
size: f32,
text_size: Option<f32>,
text_alignment: alignment::Horizontal,
spacing: f32,
font: Option<Renderer::Font>,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, Message, Renderer> Toggler<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default size of a [`Toggler`].
pub const DEFAULT_SIZE: f32 = 20.0;
/// Creates a new [`Toggler`].
///
/// It expects:
/// * a boolean describing whether the [`Toggler`] is checked or not
/// * An optional label for the [`Toggler`]
/// * a function that will be called when the [`Toggler`] is toggled. It
/// will receive the new state of the [`Toggler`] and must produce a
/// `Message`.
pub fn new<F>(
label: impl Into<Option<String>>,
is_toggled: bool,
f: F,
) -> Self
where
F: 'a + Fn(bool) -> Message,
{
Toggler {
is_toggled,
on_toggle: Box::new(f),
label: label.into(),
width: Length::Fill,
size: Self::DEFAULT_SIZE,
text_size: None,
text_alignment: alignment::Horizontal::Left,
spacing: 0.0,
font: None,
style: Default::default(),
}
}
/// Sets the size of the [`Toggler`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = size.into().0;
self
}
/// Sets the width of the [`Toggler`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
/// Sets the text size o the [`Toggler`].
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
self.text_size = Some(text_size.into().0);
self
}
/// Sets the horizontal alignment of the text of the [`Toggler`]
pub fn text_alignment(mut self, alignment: alignment::Horizontal) -> Self {
self.text_alignment = alignment;
self
}
/// Sets the spacing between the [`Toggler`] and the text.
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.spacing = spacing.into().0;
self
}
/// Sets the [`Font`] of the text of the [`Toggler`]
///
/// [`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 [`Toggler`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Toggler<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let mut row = Row::<(), Renderer>::new()
.width(self.width)
.spacing(self.spacing)
.align_items(Alignment::Center);
if let Some(label) = &self.label {
row = row.push(
Text::new(label)
.horizontal_alignment(self.text_alignment)
.font(self.font.unwrap_or_else(|| renderer.default_font()))
.width(self.width)
.size(
self.text_size
.unwrap_or_else(|| renderer.default_size()),
),
);
}
row = row.push(Row::new().width(2.0 * self.size).height(self.size));
row.layout(renderer, limits)
}
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 {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
shell.publish((self.on_toggle)(!self.is_toggled));
event::Status::Captured
} else {
event::Status::Ignored
}
}
_ => event::Status::Ignored,
}
}
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
}
}
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
/// Makes sure that the border radius of the toggler looks good at every size.
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
/// The space ratio between the background Quad and the Toggler bounds, and
/// between the background Quad and foreground Quad.
const SPACE_RATIO: f32 = 0.05;
let mut children = layout.children();
if let Some(label) = &self.label {
let label_layout = children.next().unwrap();
crate::widget::text::draw(
renderer,
style,
label_layout,
label,
self.text_size,
self.font,
Default::default(),
self.text_alignment,
alignment::Vertical::Center,
);
}
let toggler_layout = children.next().unwrap();
let bounds = toggler_layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let style = if is_mouse_over {
theme.hovered(&self.style, self.is_toggled)
} else {
theme.active(&self.style, self.is_toggled)
};
let border_radius = bounds.height / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height;
let toggler_background_bounds = Rectangle {
x: bounds.x + space,
y: bounds.y + space,
width: bounds.width - (2.0 * space),
height: bounds.height - (2.0 * space),
};
renderer.fill_quad(
renderer::Quad {
bounds: toggler_background_bounds,
border_radius: border_radius.into(),
border_width: 1.0,
border_color: style
.background_border
.unwrap_or(style.background),
},
style.background,
);
let toggler_foreground_bounds = Rectangle {
x: bounds.x
+ if self.is_toggled {
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
} else {
2.0 * space
},
y: bounds.y + (2.0 * space),
width: bounds.height - (4.0 * space),
height: bounds.height - (4.0 * space),
};
renderer.fill_quad(
renderer::Quad {
bounds: toggler_foreground_bounds,
border_radius: border_radius.into(),
border_width: 1.0,
border_color: style
.foreground_border
.unwrap_or(style.foreground),
},
style.foreground,
);
}
}
impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet + widget::text::StyleSheet,
{
fn from(
toggler: Toggler<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(toggler)
}
}

View file

@ -1,387 +0,0 @@
//! Display a widget over another.
use crate::event;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget;
use crate::widget::container;
use crate::widget::overlay;
use crate::widget::{Text, Tree};
use crate::{
Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point,
Rectangle, Shell, Size, Vector, Widget,
};
use std::borrow::Cow;
/// An element to display a widget over another.
#[allow(missing_debug_implementations)]
pub struct Tooltip<'a, Message, Renderer: text::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{
content: Element<'a, Message, Renderer>,
tooltip: Text<'a, Renderer>,
position: Position,
gap: f32,
padding: f32,
snap_within_viewport: bool,
style: <Renderer::Theme as container::StyleSheet>::Style,
}
impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{
/// The default padding of a [`Tooltip`] drawn by this renderer.
const DEFAULT_PADDING: f32 = 5.0;
/// Creates a new [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
pub fn new(
content: impl Into<Element<'a, Message, Renderer>>,
tooltip: impl Into<Cow<'a, str>>,
position: Position,
) -> Self {
Tooltip {
content: content.into(),
tooltip: Text::new(tooltip),
position,
gap: 0.0,
padding: Self::DEFAULT_PADDING,
snap_within_viewport: true,
style: Default::default(),
}
}
/// Sets the size of the text of the [`Tooltip`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.tooltip = self.tooltip.size(size);
self
}
/// Sets the font of the [`Tooltip`].
///
/// [`Font`]: Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.tooltip = self.tooltip.font(font);
self
}
/// Sets the gap between the content and its [`Tooltip`].
pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
self.gap = gap.into().0;
self
}
/// Sets the padding of the [`Tooltip`].
pub fn padding(mut self, padding: impl Into<Pixels>) -> Self {
self.padding = padding.into().0;
self
}
/// Sets whether the [`Tooltip`] is snapped within the viewport.
pub fn snap_within_viewport(mut self, snap: bool) -> Self {
self.snap_within_viewport = snap;
self
}
/// Sets the style of the [`Tooltip`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Tooltip<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.content)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(std::slice::from_ref(&self.content))
}
fn width(&self) -> Length {
self.content.as_widget().width()
}
fn height(&self) -> Length {
self.content.as_widget().height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.content.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
inherited_style,
layout,
cursor_position,
viewport,
);
let tooltip = &self.tooltip;
draw(
renderer,
theme,
inherited_style,
layout,
cursor_position,
viewport,
self.position,
self.gap,
self.padding,
self.snap_within_viewport,
&self.style,
|renderer, limits| {
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
},
|renderer, defaults, layout, cursor_position, viewport| {
Widget::<(), Renderer>::draw(
tooltip,
&Tree::empty(),
renderer,
theme,
defaults,
layout,
cursor_position,
viewport,
);
},
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{
fn from(
tooltip: Tooltip<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(tooltip)
}
}
/// The position of the tooltip. Defaults to following the cursor.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Position {
/// The tooltip will follow the cursor.
FollowCursor,
/// The tooltip will appear on the top of the widget.
Top,
/// The tooltip will appear on the bottom of the widget.
Bottom,
/// The tooltip will appear on the left of the widget.
Left,
/// The tooltip will appear on the right of the widget.
Right,
}
/// Draws a [`Tooltip`].
pub fn draw<Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
position: Position,
gap: f32,
padding: f32,
snap_within_viewport: bool,
style: &<Renderer::Theme as container::StyleSheet>::Style,
layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
draw_text: impl FnOnce(
&mut Renderer,
&renderer::Style,
Layout<'_>,
Point,
&Rectangle,
),
) where
Renderer: crate::Renderer,
Renderer::Theme: container::StyleSheet,
{
use container::StyleSheet;
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
let style = theme.appearance(style);
let defaults = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
};
let text_layout = layout_text(
renderer,
&layout::Limits::new(
Size::ZERO,
snap_within_viewport
.then(|| viewport.size())
.unwrap_or(Size::INFINITY),
)
.pad(Padding::new(padding)),
);
let text_bounds = text_layout.bounds();
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0;
let mut tooltip_bounds = {
let offset = match position {
Position::Top => Vector::new(
x_center,
bounds.y - text_bounds.height - gap - padding,
),
Position::Bottom => Vector::new(
x_center,
bounds.y + bounds.height + gap + padding,
),
Position::Left => Vector::new(
bounds.x - text_bounds.width - gap - padding,
y_center,
),
Position::Right => Vector::new(
bounds.x + bounds.width + gap + padding,
y_center,
),
Position::FollowCursor => Vector::new(
cursor_position.x,
cursor_position.y - text_bounds.height,
),
};
Rectangle {
x: offset.x - padding,
y: offset.y - padding,
width: text_bounds.width + padding * 2.0,
height: text_bounds.height + padding * 2.0,
}
};
if snap_within_viewport {
if tooltip_bounds.x < viewport.x {
tooltip_bounds.x = viewport.x;
} else if viewport.x + viewport.width
< tooltip_bounds.x + tooltip_bounds.width
{
tooltip_bounds.x =
viewport.x + viewport.width - tooltip_bounds.width;
}
if tooltip_bounds.y < viewport.y {
tooltip_bounds.y = viewport.y;
} else if viewport.y + viewport.height
< tooltip_bounds.y + tooltip_bounds.height
{
tooltip_bounds.y =
viewport.y + viewport.height - tooltip_bounds.height;
}
}
renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| {
container::draw_background(renderer, &style, tooltip_bounds);
draw_text(
renderer,
&defaults,
Layout::with_offset(
Vector::new(
tooltip_bounds.x + padding,
tooltip_bounds.y + padding,
),
&text_layout,
),
cursor_position,
viewport,
)
});
}
}

View file

@ -1,187 +0,0 @@
//! 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"),
}
}
}

View file

@ -1,468 +0,0 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`VerticalSlider`] has some local [`State`].
use std::ops::RangeInclusive;
pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
use crate::event::{self, Event};
use crate::widget::tree::{self, Tree};
use crate::{
layout, mouse, renderer, touch, Background, Clipboard, Color, Element,
Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget,
};
/// An vertical bar and a handle that selects a single value from a range of
/// values.
///
/// A [`VerticalSlider`] will try to fill the vertical space of its container.
///
/// The [`VerticalSlider`] range of numeric values is generic and its step size defaults
/// to 1 unit.
///
/// # Example
/// ```
/// # use iced_native::widget::vertical_slider;
/// # use iced_native::renderer::Null;
/// #
/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
///
/// let value = 50.0;
///
/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged);
/// ```
#[allow(missing_debug_implementations)]
pub struct VerticalSlider<'a, T, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
range: RangeInclusive<T>,
step: T,
value: T,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
width: f32,
height: Length,
style: <Renderer::Theme as StyleSheet>::Style,
}
impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// The default width of a [`VerticalSlider`].
pub const DEFAULT_WIDTH: f32 = 22.0;
/// Creates a new [`VerticalSlider`].
///
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`VerticalSlider`]
/// * a function that will be called when the [`VerticalSlider`] is dragged.
/// It receives the new value of the [`VerticalSlider`] and must produce a
/// `Message`.
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
value
} else {
*range.start()
};
let value = if value <= *range.end() {
value
} else {
*range.end()
};
VerticalSlider {
value,
range,
step: T::from(1),
on_change: Box::new(on_change),
on_release: None,
width: Self::DEFAULT_WIDTH,
height: Length::Fill,
style: Default::default(),
}
}
/// Sets the release message of the [`VerticalSlider`].
/// This is called when the mouse is released from the slider.
///
/// Typically, the user's interaction with the slider is finished when this message is produced.
/// This is useful if you need to spawn a long-running task from the slider's result, where
/// the default on_change message could create too many events.
pub fn on_release(mut self, on_release: Message) -> Self {
self.on_release = Some(on_release);
self
}
/// Sets the width of the [`VerticalSlider`].
pub fn width(mut self, width: impl Into<Pixels>) -> Self {
self.width = width.into().0;
self
}
/// Sets the height of the [`VerticalSlider`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
/// Sets the style of the [`VerticalSlider`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
) -> Self {
self.style = style.into();
self
}
/// Sets the step size of the [`VerticalSlider`].
pub fn step(mut self, step: T) -> Self {
self.step = step;
self
}
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for VerticalSlider<'a, T, Message, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State::new())
}
fn width(&self) -> Length {
Length::Shrink
}
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 = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
update(
event,
layout,
cursor_position,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
&self.range,
self.step,
self.on_change.as_ref(),
&self.on_release,
)
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
cursor_position,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
theme,
&self.style,
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
layout,
cursor_position,
tree.state.downcast_ref::<State>(),
)
}
}
impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'a + Clone,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
slider: VerticalSlider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
}
/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`]
/// accordingly.
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
range: &RangeInclusive<T>,
step: T,
on_change: &dyn Fn(T) -> Message,
on_release: &Option<Message>,
) -> event::Status
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
{
let is_dragging = state.is_dragging;
let mut change = || {
let bounds = layout.bounds();
let new_value = if cursor_position.y >= bounds.y + bounds.height {
*range.start()
} else if cursor_position.y <= bounds.y {
*range.end()
} else {
let step = step.into();
let start = (*range.start()).into();
let end = (*range.end()).into();
let percent = 1.0
- f64::from(cursor_position.y - bounds.y)
/ f64::from(bounds.height);
let steps = (percent * (end - start) / step).round();
let value = steps * step + start;
if let Some(value) = T::from_f64(value) {
value
} else {
return;
}
};
if ((*value).into() - new_value.into()).abs() > f64::EPSILON {
shell.publish((on_change)(new_value));
*value = new_value;
}
};
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
change();
state.is_dragging = true;
return event::Status::Captured;
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. })
| Event::Touch(touch::Event::FingerLost { .. }) => {
if is_dragging {
if let Some(on_release) = on_release.clone() {
shell.publish(on_release);
}
state.is_dragging = false;
return event::Status::Captured;
}
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if is_dragging {
change();
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
/// Draws a [`VerticalSlider`].
pub fn draw<T, R>(
renderer: &mut R,
layout: Layout<'_>,
cursor_position: Point,
state: &State,
value: T,
range: &RangeInclusive<T>,
style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
style: &<R::Theme as StyleSheet>::Style,
) where
T: Into<f64> + Copy,
R: crate::Renderer,
R::Theme: StyleSheet,
{
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let style = if state.is_dragging {
style_sheet.dragging(style)
} else if is_mouse_over {
style_sheet.hovered(style)
} else {
style_sheet.active(style)
};
let rail_x = bounds.x + (bounds.width / 2.0).round();
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: rail_x - 1.0,
y: bounds.y,
width: 2.0,
height: bounds.height,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
style.rail_colors.0,
);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: rail_x + 1.0,
y: bounds.y,
width: 2.0,
height: bounds.height,
},
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Background::Color(style.rail_colors.1),
);
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
{
HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
HandleShape::Rectangle {
width,
border_radius,
} => (f32::from(width), bounds.width, border_radius),
};
let value = value.into() as f32;
let (range_start, range_end) = {
let (start, end) = range.clone().into_inner();
(start.into() as f32, end.into() as f32)
};
let handle_offset = if range_start >= range_end {
0.0
} else {
(bounds.height - handle_width) * (value - range_end)
/ (range_start - range_end)
};
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: rail_x - (handle_height / 2.0),
y: bounds.y + handle_offset.round(),
width: handle_height,
height: handle_width,
},
border_radius: handle_border_radius.into(),
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
style.handle.color,
);
}
/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`].
pub fn mouse_interaction(
layout: Layout<'_>,
cursor_position: Point,
state: &State,
) -> mouse::Interaction {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
if state.is_dragging {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
} else {
mouse::Interaction::default()
}
}
/// The local state of a [`VerticalSlider`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_dragging: bool,
}
impl State {
/// Creates a new [`State`].
pub fn new() -> State {
State::default()
}
}

View file

@ -1,18 +1,11 @@
//! Build window-based GUI applications.
mod action;
mod event;
mod mode;
mod redraw_request;
mod user_attention;
pub use action::Action;
pub use event::Event;
pub use mode::Mode;
pub use redraw_request::RedrawRequest;
pub use user_attention::UserAttention;
use crate::core::time::Instant;
use crate::core::window::Event;
use crate::subscription::{self, Subscription};
use crate::time::Instant;
/// Subscribes to the frames of the window of the running application.
///
@ -24,7 +17,7 @@ use crate::time::Instant;
/// animations without missing any frames.
pub fn frames() -> Subscription<Instant> {
subscription::raw_events(|event, _status| match event {
crate::Event::Window(Event::RedrawRequested(at)) => Some(at),
iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at),
_ => None,
})
}

View file

@ -1,6 +1,6 @@
use crate::window::{Mode, UserAttention};
use crate::core::window::{Mode, UserAttention};
use crate::futures::MaybeSend;
use iced_futures::MaybeSend;
use std::fmt;
/// An operation to be performed on some window.

View file

@ -1,58 +0,0 @@
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,
}

View file

@ -1,12 +0,0 @@
/// 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

@ -1,38 +0,0 @@
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

@ -1,21 +0,0 @@
/// 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,
}