Merge pull request #1393 from iced-rs/deprecate-stateful-widgets

Replace stateful widgets with the new `iced_pure` API
This commit is contained in:
Héctor Ramón 2022-08-06 00:32:57 +02:00 committed by GitHub
commit 1923dbf7f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
160 changed files with 4612 additions and 14928 deletions

View file

@ -3,6 +3,8 @@ mod action;
pub use action::Action;
use crate::widget;
use iced_futures::MaybeSend;
use std::fmt;
@ -24,6 +26,13 @@ impl<T> Command<T> {
Self(iced_futures::Command::single(action))
}
/// Creates a [`Command`] that performs a [`widget::Operation`].
pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self {
Self(iced_futures::Command::single(Action::Widget(
widget::Action::new(operation),
)))
}
/// Creates a [`Command`] that performs the action of the given future.
pub fn perform<A>(
future: impl Future<Output = T> + 'static + MaybeSend,
@ -51,6 +60,7 @@ impl<T> Command<T> {
) -> Command<A>
where
T: 'static,
A: 'static,
{
let Command(command) = self;

View file

@ -1,5 +1,6 @@
use crate::clipboard;
use crate::system;
use crate::widget;
use crate::window;
use iced_futures::MaybeSend;
@ -23,6 +24,9 @@ pub enum Action<T> {
/// Run a system action.
System(system::Action<T>),
/// Run a widget action.
Widget(widget::Action<T>),
}
impl<T> Action<T> {
@ -34,6 +38,7 @@ impl<T> Action<T> {
f: impl Fn(T) -> A + 'static + MaybeSend + Sync,
) -> Action<A>
where
A: 'static,
T: 'static,
{
use iced_futures::futures::FutureExt;
@ -43,6 +48,7 @@ impl<T> Action<T> {
Self::Clipboard(action) => Action::Clipboard(action.map(f)),
Self::Window(window) => Action::Window(window),
Self::System(system) => Action::System(system.map(f)),
Self::Widget(widget) => Action::Widget(widget.map(f)),
}
}
}
@ -56,6 +62,7 @@ impl<T> fmt::Debug for Action<T> {
}
Self::Window(action) => write!(f, "Action::Window({:?})", action),
Self::System(action) => write!(f, "Action::System({:?})", action),
Self::Widget(_action) => write!(f, "Action::Widget"),
}
}
}

View file

@ -3,9 +3,11 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
};
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell, Widget};
use std::borrow::Borrow;
/// A generic [`Widget`].
///
@ -15,25 +17,33 @@ use crate::{
/// If you have a [built-in widget], you should be able to use `Into<Element>`
/// to turn it into an [`Element`].
///
/// [built-in widget]: widget/index.html#built-in-widgets
/// [built-in widget]: crate::widget
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>,
widget: Box<dyn Widget<Message, Renderer> + 'a>,
}
impl<'a, Message, Renderer> Element<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
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,
) -> Element<'a, Message, Renderer> {
Element {
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
@ -168,127 +178,22 @@ where
/// }
/// }
/// ```
pub fn map<F, B>(self, f: F) -> Element<'a, B, Renderer>
where
Message: 'static,
Renderer: 'a,
B: 'static,
F: 'static + Fn(Message) -> B,
{
Element {
widget: Box::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>>(
pub fn map<B>(
self,
color: C,
) -> Element<'a, Message, Renderer>
f: impl Fn(Message) -> B + 'a,
) -> Element<'a, B, Renderer>
where
Message: 'static,
Renderer: 'a,
Message: 'a,
Renderer: crate::Renderer + 'a,
B: 'a,
{
Element {
widget: Box::new(Explain::new(self, color.into())),
}
}
/// Returns the width of the [`Element`].
pub fn width(&self) -> Length {
self.widget.width()
}
/// Returns the height of the [`Element`].
pub fn height(&self) -> Length {
self.widget.height()
}
/// Computes the layout of the [`Element`] in the given [`Limits`].
///
/// [`Limits`]: layout::Limits
pub fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.widget.layout(renderer, limits)
}
/// 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.widget.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
/// 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,
viewport: &Rectangle,
) {
self.widget.draw(
renderer,
theme,
style,
layout,
cursor_position,
viewport,
)
}
/// 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.widget.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
}
/// Returns the overlay of the [`Element`], if there is any.
pub fn overlay<'b>(
&'b mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.widget.overlay(layout, renderer)
Element::new(Map::new(self.widget, f))
}
}
struct Map<'a, A, B, Renderer> {
widget: Box<dyn Widget<A, Renderer> + 'a>,
mapper: Box<dyn Fn(A) -> B>,
mapper: Box<dyn Fn(A) -> B + 'a>,
}
impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
@ -297,7 +202,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
mapper: F,
) -> Map<'a, A, B, Renderer>
where
F: 'static + Fn(A) -> B,
F: 'a + Fn(A) -> B,
{
Map {
widget,
@ -309,9 +214,25 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
Renderer: crate::Renderer + 'a,
A: 'static,
B: 'static,
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()
}
@ -328,8 +249,45 @@ where
self.widget.layout(renderer, limits)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
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);
}
}
self.widget
.operate(tree, layout, &mut MapOperation { operation });
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -341,6 +299,7 @@ where
let mut local_shell = Shell::new(&mut local_messages);
let status = self.widget.on_event(
tree,
event,
layout,
cursor_position,
@ -356,6 +315,7 @@ where
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -364,6 +324,7 @@ where
viewport: &Rectangle,
) {
self.widget.draw(
tree,
renderer,
theme,
style,
@ -375,12 +336,14 @@ where
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,
@ -388,134 +351,32 @@ where
)
}
fn overlay(
&mut self,
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, B, Renderer>> {
) -> Option<overlay::Element<'b, B, Renderer>> {
let mapper = &self.mapper;
self.widget
.overlay(layout, renderer)
.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,
impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
for Element<'a, Message, Renderer>
{
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
Explain { element, color }
fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
self.widget.borrow()
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
Renderer: crate::Renderer,
impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
for &Element<'a, Message, Renderer>
{
fn width(&self) -> Length {
self.element.widget.width()
}
fn height(&self) -> Length {
self.element.widget.height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.element.widget.layout(renderer, limits)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.element.widget.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
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,
},
Color::TRANSPARENT,
);
for child in layout.children() {
explain_layout(renderer, color, child);
}
}
self.element.widget.draw(
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
explain_layout(renderer, self.color, layout);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.element.widget.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
}
fn overlay(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.element.overlay(layout, renderer)
fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
self.widget.borrow()
}
}

View file

@ -16,8 +16,10 @@
// 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, Element, Padding, Point, Size};
use crate::{Alignment, Padding, Point, Size};
/// The main axis of a flex layout.
#[derive(Debug)]
@ -84,8 +86,8 @@ where
items.iter().for_each(|child| {
let cross_fill_factor = match axis {
Axis::Horizontal => child.height(),
Axis::Vertical => child.width(),
Axis::Horizontal => child.as_widget().height(),
Axis::Vertical => child.as_widget().width(),
}
.fill_factor();
@ -95,7 +97,7 @@ where
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout = child.layout(renderer, &child_limits);
let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size();
fill_cross = fill_cross.max(axis.cross(size));
@ -107,8 +109,8 @@ where
for (i, child) in items.iter().enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.width(),
Axis::Vertical => child.height(),
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
@ -130,7 +132,7 @@ where
Size::new(max_width, max_height),
);
let layout = child.layout(renderer, &child_limits);
let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
@ -149,8 +151,8 @@ where
for (i, child) in items.iter().enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.width(),
Axis::Vertical => child.height(),
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
@ -179,7 +181,7 @@ where
Size::new(max_width, max_height),
);
let layout = child.layout(renderer, &child_limits);
let layout = child.as_widget().layout(renderer, &child_limits);
if align_items != Alignment::Fill {
cross = cross.max(axis.cross(layout.size()));

View file

@ -10,6 +10,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
/// An interactive component that can be displayed on top of other widgets.
@ -40,6 +42,36 @@ where
cursor_position: Point,
);
/// 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,
_layout: Layout<'_>,
_operation: &mut dyn widget::Operation<Message>,
) {
}
/// Processes a runtime [`Event`].
///
/// It receives:
@ -77,3 +109,26 @@ where
mouse::Interaction::Idle
}
}
/// Obtains the first overlay [`Element`] found in the given 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 [crate::Element<'_, Message, Renderer>],
tree: &'a mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<Element<'a, Message, Renderer>>
where
Renderer: crate::Renderer,
{
children
.iter()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|((child, state), layout)| {
child.as_widget().overlay(state, layout, renderer)
})
.next()
}

View file

@ -4,6 +4,7 @@ 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};
/// A generic [`Overlay`].
@ -102,6 +103,15 @@ where
self.overlay
.draw(renderer, theme, style, layout, cursor_position)
}
/// Applies an [`Operation`] to the [`Element`].
pub fn operate(
&self,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.overlay.operate(layout, operation);
}
}
struct Map<'a, A, B, Renderer> {

View file

@ -9,6 +9,7 @@ use crate::text::{self, Text};
use crate::touch;
use crate::widget::container::{self, Container};
use crate::widget::scrollable::{self, Scrollable};
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@ -114,15 +115,23 @@ where
}
/// The local state of a [`Menu`].
#[derive(Debug, Clone, Default)]
#[derive(Debug)]
pub struct State {
scrollable: scrollable::State,
tree: Tree,
}
impl State {
/// Creates a new [`State`] for a [`Menu`].
pub fn new() -> Self {
Self::default()
Self {
tree: Tree::empty(),
}
}
}
impl Default for State {
fn default() -> Self {
Self::new()
}
}
@ -131,6 +140,7 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
state: &'a mut Tree,
container: Container<'a, Message, Renderer>,
width: u16,
target_height: f32,
@ -161,18 +171,20 @@ where
style,
} = menu;
let container =
Container::new(Scrollable::new(&mut state.scrollable).push(List {
options,
hovered_option,
last_selection,
font,
text_size,
padding,
style,
}));
let container = Container::new(Scrollable::new(List {
options,
hovered_option,
last_selection,
font,
text_size,
padding,
style,
}));
state.tree.diff(&container as &dyn Widget<_, _>);
Self {
state: &mut state.tree,
container,
width,
target_height,
@ -187,6 +199,18 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
fn tag(&self) -> tree::Tag {
self.container.tag()
}
fn state(&self) -> tree::State {
self.container.state()
}
fn children(&self) -> Vec<Tree> {
self.container.children()
}
fn layout(
&self,
renderer: &Renderer,
@ -230,6 +254,7 @@ where
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
self.state,
event,
layout,
cursor_position,
@ -247,6 +272,7 @@ where
renderer: &Renderer,
) -> mouse::Interaction {
self.container.mouse_interaction(
self.state,
layout,
cursor_position,
viewport,
@ -279,6 +305,7 @@ where
);
self.container.draw(
self.state,
renderer,
theme,
style,
@ -344,6 +371,7 @@ where
fn on_event(
&mut self,
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -407,6 +435,7 @@ where
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@ -423,6 +452,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,

View file

@ -26,5 +26,5 @@ pub trait Program: Sized {
/// Returns the widgets to display in the [`Program`].
///
/// These widgets can produce __messages__ based on user interaction.
fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
fn view(&self) -> Element<'_, Self::Message, Self::Renderer>;
}

View file

@ -21,7 +21,7 @@ pub trait Renderer: Sized {
element: &Element<'a, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.layout(self, limits)
element.as_widget().layout(self, limits)
}
/// Draws the primitives recorded in the given closure in a new layer.

View file

@ -4,6 +4,7 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// A set of interactive graphical elements with a specific [`Layout`].
@ -22,6 +23,7 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
base: layout::Node,
state: widget::Tree,
overlay: Option<layout::Node>,
bounds: Size,
}
@ -88,17 +90,21 @@ where
pub fn build<E: Into<Element<'a, Message, Renderer>>>(
root: E,
bounds: Size,
_cache: Cache,
cache: Cache,
renderer: &mut Renderer,
) -> Self {
let root = root.into();
let Cache { mut state } = cache;
state.diff(root.as_widget());
let base =
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
UserInterface {
root,
base,
state,
overlay: None,
bounds,
}
@ -182,9 +188,12 @@ where
use std::mem::ManuallyDrop;
let mut state = State::Updated;
let mut manual_overlay = ManuallyDrop::new(
self.root.overlay(Layout::new(&self.base), renderer),
);
let mut manual_overlay =
ManuallyDrop::new(self.root.as_widget().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
));
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
let bounds = self.bounds;
@ -215,9 +224,12 @@ where
&layout::Limits::new(Size::ZERO, self.bounds),
);
manual_overlay = ManuallyDrop::new(
self.root.overlay(Layout::new(&self.base), renderer),
);
manual_overlay =
ManuallyDrop::new(self.root.as_widget().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
));
if manual_overlay.is_none() {
break;
@ -262,7 +274,8 @@ where
let mut shell = Shell::new(messages);
let event_status = self.root.widget.on_event(
let event_status = self.root.as_widget_mut().on_event(
&mut self.state,
event,
Layout::new(&self.base),
base_cursor,
@ -377,9 +390,11 @@ where
let viewport = Rectangle::with_size(self.bounds);
let base_cursor = if let Some(overlay) =
self.root.overlay(Layout::new(&self.base), renderer)
{
let base_cursor = if let Some(overlay) = self.root.as_widget().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
) {
let overlay_layout = self
.overlay
.take()
@ -399,7 +414,8 @@ where
cursor_position
};
self.root.widget.draw(
self.root.as_widget().draw(
&self.state,
renderer,
theme,
style,
@ -408,7 +424,8 @@ where
&viewport,
);
let base_interaction = self.root.widget.mouse_interaction(
let base_interaction = self.root.as_widget().mouse_interaction(
&self.state,
Layout::new(&self.base),
cursor_position,
&viewport,
@ -430,52 +447,79 @@ where
overlay
.as_ref()
.and_then(|layout| {
root.overlay(Layout::new(base), renderer).map(|overlay| {
let overlay_interaction = overlay.mouse_interaction(
Layout::new(layout),
cursor_position,
&viewport,
renderer,
);
let overlay_bounds = layout.bounds();
renderer.with_layer(overlay_bounds, |renderer| {
overlay.draw(
renderer,
theme,
style,
root.as_widget()
.overlay(&mut self.state, Layout::new(base), renderer)
.map(|overlay| {
let overlay_interaction = overlay.mouse_interaction(
Layout::new(layout),
cursor_position,
&viewport,
renderer,
);
});
if overlay_bounds.contains(cursor_position) {
overlay_interaction
} else {
base_interaction
}
})
let overlay_bounds = layout.bounds();
renderer.with_layer(overlay_bounds, |renderer| {
overlay.draw(
renderer,
theme,
style,
Layout::new(layout),
cursor_position,
);
});
if overlay_bounds.contains(cursor_position) {
overlay_interaction
} else {
base_interaction
}
})
})
.unwrap_or(base_interaction)
}
/// Applies a [`widget::Operation`] to the [`UserInterface`].
pub fn operate(
&mut self,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
self.root.as_widget().operate(
&mut self.state,
Layout::new(&self.base),
operation,
);
if let Some(layout) = self.overlay.as_ref() {
if let Some(overlay) = self.root.as_widget().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
) {
overlay.operate(Layout::new(layout), operation);
}
}
}
/// Relayouts and returns a new [`UserInterface`] using the provided
/// bounds.
pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
Self::build(self.root, bounds, Cache, renderer)
Self::build(self.root, bounds, Cache { state: self.state }, renderer)
}
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
/// process.
pub fn into_cache(self) -> Cache {
Cache
Cache { state: self.state }
}
}
/// Reusable data of a specific [`UserInterface`].
#[derive(Debug, Clone)]
pub struct Cache;
#[derive(Debug)]
pub struct Cache {
state: widget::Tree,
}
impl Cache {
/// Creates an empty [`Cache`].
@ -483,7 +527,9 @@ impl Cache {
/// You should use this to initialize a [`Cache`] before building your first
/// [`UserInterface`].
pub fn new() -> Cache {
Cache
Cache {
state: widget::Tree::empty(),
}
}
}

View file

@ -15,7 +15,9 @@ 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;
@ -30,6 +32,10 @@ pub mod text;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
pub mod tree;
mod action;
mod id;
#[doc(no_inline)]
pub use button::Button;
@ -40,6 +46,8 @@ 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;
@ -69,6 +77,12 @@ pub use text_input::TextInput;
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
#[doc(no_inline)]
pub use tree::Tree;
pub use action::Action;
pub use id::Id;
pub use operation::Operation;
use crate::event::{self, Event};
use crate::layout;
@ -109,12 +123,10 @@ where
/// Returns the height of the [`Widget`].
fn height(&self) -> Length;
/// Returns the [`Node`] of the [`Widget`].
/// Returns the [`layout::Node`] of the [`Widget`].
///
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
/// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
///
/// [`Node`]: layout::Node
fn layout(
&self,
renderer: &Renderer,
@ -124,6 +136,7 @@ where
/// Draws the [`Widget`] using the associated `Renderer`.
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -132,20 +145,43 @@ where
viewport: &Rectangle,
);
/// Processes a runtime [`Event`].
/// Returns the [`Tag`] of the [`Widget`].
///
/// It receives:
/// * an [`Event`] describing user interaction
/// * the computed [`Layout`] of the [`Widget`]
/// * the current cursor position
/// * a mutable `Message` list, allowing the [`Widget`] to produce
/// new messages based on user interaction.
/// * the `Renderer`
/// * a [`Clipboard`], if available
/// [`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<'_>,
_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,
@ -161,6 +197,7 @@ where
/// By default, it returns [`mouse::Interaction::Idle`].
fn mouse_interaction(
&self,
_state: &Tree,
_layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
@ -170,11 +207,12 @@ where
}
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay(
&mut self,
fn overlay<'a>(
&'a self,
_state: &'a mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
) -> Option<overlay::Element<'a, Message, Renderer>> {
None
}
}

View file

@ -0,0 +1,88 @@
use crate::widget::operation::{self, Operation};
use crate::widget::Id;
use iced_futures::MaybeSend;
/// An operation to be performed on the widget tree.
#[allow(missing_debug_implementations)]
pub struct Action<T>(Box<dyn Operation<T>>);
impl<T> Action<T> {
/// Creates a new [`Action`] with the given [`Operation`].
pub fn new(operation: impl Operation<T> + 'static) -> Self {
Self(Box::new(operation))
}
/// Maps the output of an [`Action`] using the given function.
pub fn map<A>(
self,
f: impl Fn(T) -> A + 'static + MaybeSend + Sync,
) -> Action<A>
where
T: 'static,
A: 'static,
{
Action(Box::new(Map {
operation: self.0,
f: Box::new(f),
}))
}
/// Consumes the [`Action`] and returns the internal [`Operation`].
pub fn into_operation(self) -> Box<dyn Operation<T>> {
self.0
}
}
#[allow(missing_debug_implementations)]
struct Map<A, B> {
operation: Box<dyn Operation<A>>,
f: Box<dyn Fn(A) -> B>,
}
impl<A, B> Operation<B> for Map<A, B>
where
A: 'static,
B: 'static,
{
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
struct MapRef<'a, A, B> {
operation: &'a mut dyn Operation<A>,
f: &'a dyn Fn(A) -> B,
}
impl<'a, A, B> Operation<B> for MapRef<'a, A, B> {
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
let Self { operation, f } = self;
operation.container(id, &mut |operation| {
operate_on_children(&mut MapRef { operation, f });
});
}
}
let Self { operation, f } = self;
MapRef {
operation: operation.as_mut(),
f,
}
.container(id, operate_on_children);
}
fn focusable(
&mut self,
state: &mut dyn operation::Focusable,
id: Option<&Id>,
) {
self.operation.focusable(state, id);
}
}

View file

@ -7,6 +7,8 @@ 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,
@ -17,8 +19,6 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// A generic widget that produces a message when pressed.
///
/// ```
/// # use iced_native::widget::{button, Text};
/// #
/// # type Button<'a, Message> =
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
@ -27,17 +27,13 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// ButtonPressed,
/// }
///
/// let mut state = button::State::new();
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(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:
///
/// ```
/// # use iced_native::widget::{button, Text};
/// #
/// # type Button<'a, Message> =
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
/// #
@ -46,12 +42,12 @@ pub use iced_style::button::{Appearance, StyleSheet};
/// ButtonPressed,
/// }
///
/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
/// Button::new(state, Text::new("I'm disabled!"))
/// fn disabled_button<'a>() -> Button<'a, Message> {
/// Button::new("I'm disabled!")
/// }
///
/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
/// disabled_button(state).on_press(Message::ButtonPressed)
/// fn enabled_button<'a>() -> Button<'a, Message> {
/// disabled_button().on_press(Message::ButtonPressed)
/// }
/// ```
#[allow(missing_debug_implementations)]
@ -60,7 +56,6 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
content: Element<'a, Message, Renderer>,
on_press: Option<Message>,
width: Length,
@ -71,24 +66,18 @@ where
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
Message: Clone,
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
pub fn new<E>(state: &'a mut State, content: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
/// Creates a new [`Button`] with the given content.
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Button {
state,
content: content.into(),
on_press: None,
width: Length::Shrink,
height: Length::Shrink,
padding: Padding::new(5),
style: Default::default(),
style: <Renderer::Theme as StyleSheet>::Style::default(),
}
}
@ -111,13 +100,14 @@ where
}
/// Sets the message that will be produced when the [`Button`] is pressed.
/// If on_press isn't set, button will be disabled.
///
/// 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 of this [`Button`].
/// Sets the style variant of this [`Button`].
pub fn style(
mut self,
style: <Renderer::Theme as StyleSheet>::Style,
@ -127,6 +117,174 @@ where
}
}
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<'_>,
operation: &mut dyn Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
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 self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget().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 {
@ -292,131 +450,3 @@ pub fn mouse_interaction(
mouse::Interaction::default()
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
Message: Clone,
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 {
layout(
renderer,
limits,
self.width,
self.height,
self.padding,
|renderer, limits| self.content.layout(renderer, limits),
)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
if let event::Status::Captured = self.content.on_event(
event.clone(),
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
) {
return event::Status::Captured;
}
update(
event,
layout,
cursor_position,
shell,
&self.on_press,
|| &mut self.state,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position, self.on_press.is_some())
}
fn draw(
&self,
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,
|| self.state,
);
self.content.draw(
renderer,
theme,
&renderer::Style {
text_color: styling.text_color,
},
content_layout,
cursor_position,
&bounds,
);
}
fn overlay(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.content
.overlay(layout.children().next().unwrap(), renderer)
}
}
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
button: Button<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(button)
}
}

View file

@ -6,7 +6,7 @@ use crate::mouse;
use crate::renderer;
use crate::text;
use crate::touch;
use crate::widget::{self, Row, Text};
use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Element, Layout, Length, Point, Rectangle, Shell,
Widget,
@ -168,6 +168,7 @@ where
fn on_event(
&mut self,
_tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -194,6 +195,7 @@ where
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@ -208,6 +210,7 @@ where
fn draw(
&self,
_tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,

View file

@ -4,6 +4,7 @@ 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, Point, Rectangle,
Shell, Widget,
@ -19,7 +20,6 @@ pub struct Column<'a, Message, Renderer> {
width: Length,
height: Length,
max_width: u32,
max_height: u32,
align_items: Alignment,
children: Vec<Element<'a, Message, Renderer>>,
}
@ -40,7 +40,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Alignment::Start,
children,
}
@ -80,12 +79,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
/// Sets the maximum height of the [`Column`] in pixels.
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
@ -93,10 +86,10 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
}
/// Adds an element to the [`Column`].
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
pub fn push(
mut self,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
self
}
@ -113,6 +106,14 @@ impl<'a, Message, Renderer> Widget<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
}
@ -128,7 +129,6 @@ where
) -> layout::Node {
let limits = limits
.max_width(self.max_width)
.max_height(self.max_height)
.width(self.width)
.height(self.height);
@ -143,8 +143,26 @@ where
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
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, operation);
})
});
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -154,9 +172,11 @@ where
) -> event::Status {
self.children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|(child, layout)| {
child.widget.on_event(
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor_position,
@ -170,6 +190,7 @@ where
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@ -177,9 +198,11 @@ where
) -> mouse::Interaction {
self.children
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|(child, layout)| {
child.widget.mouse_interaction(
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
state,
layout,
cursor_position,
viewport,
@ -192,6 +215,7 @@ where
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -199,8 +223,14 @@ where
cursor_position: Point,
viewport: &Rectangle,
) {
for (child, layout) in self.children.iter().zip(layout.children()) {
child.draw(
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child.as_widget().draw(
state,
renderer,
theme,
style,
@ -211,30 +241,23 @@ where
}
}
fn overlay(
&mut self,
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.children
.iter_mut()
.zip(layout.children())
.filter_map(|(child, layout)| {
child.widget.overlay(layout, renderer)
})
.next()
) -> Option<overlay::Element<'b, Message, Renderer>> {
overlay::from_children(&self.children, tree, layout, renderer)
}
}
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + crate::Renderer,
Message: 'a,
Renderer: crate::Renderer + 'a,
{
fn from(
column: Column<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(column)
fn from(column: Column<'a, Message, Renderer>) -> Self {
Self::new(column)
}
}

View file

@ -5,6 +5,7 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::{Operation, Tree};
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Widget,
@ -121,6 +122,159 @@ where
}
}
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<'_>,
operation: &mut dyn Operation<Message>,
) {
operation.container(None, &mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
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 self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget().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,
@ -155,110 +309,6 @@ pub fn layout<Renderer>(
layout::Node::with_children(size.pad(padding), vec![content])
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, 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 {
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.layout(renderer, limits),
)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.content.widget.on_event(
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.widget.mouse_interaction(
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
fn draw(
&self,
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.draw(
renderer,
theme,
&renderer::Style {
text_color: style
.text_color
.unwrap_or(renderer_style.text_color),
},
layout.children().next().unwrap(),
cursor_position,
viewport,
);
}
fn overlay(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.content
.overlay(layout.children().next().unwrap(), renderer)
}
}
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
pub fn draw_background<Renderer>(
renderer: &mut Renderer,
@ -281,17 +331,3 @@ pub fn draw_background<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)
}
}

View file

@ -0,0 +1,283 @@
//! Helper functions to create pure widgets.
use crate::widget;
use crate::{Element, Length};
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,
{
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, position)
}
/// Creates a new [`Text`] widget with the provided content.
///
/// [`Text`]: widget::Text
pub fn text<Renderer>(text: impl ToString) -> widget::Text<Renderer>
where
Renderer: crate::text::Renderer,
Renderer::Theme: widget::text::StyleSheet,
{
widget::Text::new(text)
}
/// 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(is_checked, label, 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(is_checked, label, 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 [`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::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: 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: 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: u16) -> 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: u16) -> 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(handle: impl Into<widget::svg::Handle>) -> widget::Svg {
widget::Svg::new(handle)
}

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

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

@ -5,12 +5,18 @@ 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
@ -135,6 +141,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,

View file

@ -4,6 +4,7 @@ use crate::image;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
@ -13,8 +14,7 @@ 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<'a, Handle> {
state: &'a mut State,
pub struct Viewer<Handle> {
padding: u16,
width: Length,
height: Length,
@ -24,11 +24,10 @@ pub struct Viewer<'a, Handle> {
handle: Handle,
}
impl<'a, Handle> Viewer<'a, Handle> {
impl<Handle> Viewer<Handle> {
/// Creates a new [`Viewer`] with the given [`State`].
pub fn new(state: &'a mut State, handle: Handle) -> Self {
pub fn new(handle: Handle) -> Self {
Viewer {
state,
padding: 0,
width: Length::Shrink,
height: Length::Shrink,
@ -81,43 +80,21 @@ impl<'a, Handle> Viewer<'a, Handle> {
self.scale_step = scale_step;
self
}
/// Returns the bounds of the underlying image, given the bounds of
/// the [`Viewer`]. Scaling will be applied and original aspect ratio
/// will be respected.
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
where
Renderer: image::Renderer<Handle = Handle>,
{
let (width, height) = renderer.dimensions(&self.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 = self.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)
}
}
impl<'a, Message, Renderer, Handle> Widget<Message, Renderer>
for Viewer<'a, Handle>
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
}
@ -164,6 +141,7 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -181,39 +159,43 @@ where
match delta {
mouse::ScrollDelta::Lines { y, .. }
| mouse::ScrollDelta::Pixels { y, .. } => {
let previous_scale = self.state.scale;
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
{
self.state.scale = (if y > 0.0 {
self.state.scale * (1.0 + self.scale_step)
state.scale = (if y > 0.0 {
state.scale * (1.0 + self.scale_step)
} else {
self.state.scale / (1.0 + self.scale_step)
state.scale / (1.0 + self.scale_step)
})
.max(self.min_scale)
.min(self.max_scale);
let image_size =
self.image_size(renderer, bounds.size());
let image_size = image_size(
renderer,
&self.handle,
state,
bounds.size(),
);
let factor =
self.state.scale / previous_scale - 1.0;
let factor = state.scale / previous_scale - 1.0;
let cursor_to_center =
cursor_position - bounds.center();
let adjustment = cursor_to_center * factor
+ self.state.current_offset * factor;
+ state.current_offset * factor;
self.state.current_offset = Vector::new(
state.current_offset = Vector::new(
if image_size.width > bounds.width {
self.state.current_offset.x + adjustment.x
state.current_offset.x + adjustment.x
} else {
0.0
},
if image_size.height > bounds.height {
self.state.current_offset.y + adjustment.y
state.current_offset.y + adjustment.y
} else {
0.0
},
@ -227,21 +209,34 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
if is_mouse_over =>
{
self.state.cursor_grabbed_at = Some(cursor_position);
self.state.starting_offset = self.state.current_offset;
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))
if self.state.cursor_grabbed_at.is_some() =>
{
self.state.cursor_grabbed_at = None;
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
let state = tree.state.downcast_mut::<State>();
event::Status::Captured
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 }) => {
if let Some(origin) = self.state.cursor_grabbed_at {
let image_size = self.image_size(renderer, bounds.size());
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)
@ -255,7 +250,7 @@ where
let delta = position - origin;
let x = if bounds.width < image_size.width {
(self.state.starting_offset.x - delta.x)
(state.starting_offset.x - delta.x)
.min(hidden_width)
.max(-hidden_width)
} else {
@ -263,14 +258,14 @@ where
};
let y = if bounds.height < image_size.height {
(self.state.starting_offset.y - delta.y)
(state.starting_offset.y - delta.y)
.min(hidden_height)
.max(-hidden_height)
} else {
0.0
};
self.state.current_offset = Vector::new(x, y);
state.current_offset = Vector::new(x, y);
event::Status::Captured
} else {
@ -283,15 +278,17 @@ where
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 self.state.is_cursor_grabbed() {
if state.is_cursor_grabbed() {
mouse::Interaction::Grabbing
} else if is_mouse_over {
mouse::Interaction::Grab
@ -302,6 +299,7 @@ where
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
@ -309,9 +307,11 @@ where
_cursor_position: Point,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let image_size = self.image_size(renderer, bounds.size());
let image_size =
image_size(renderer, &self.handle, state, bounds.size());
let translation = {
let image_top_left = Vector::new(
@ -319,7 +319,7 @@ where
bounds.height / 2.0 - image_size.height / 2.0,
);
image_top_left - self.state.offset(bounds, image_size)
image_top_left - state.offset(bounds, image_size)
};
renderer.with_layer(bounds, |renderer| {
@ -385,14 +385,47 @@ impl State {
}
}
impl<'a, Message, Renderer, Handle> From<Viewer<'a, Handle>>
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<'a, Handle>) -> Element<'a, Message, Renderer> {
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 (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

@ -0,0 +1,60 @@
//! Query or update internal widget state.
pub mod focusable;
pub mod scrollable;
pub use focusable::Focusable;
pub use scrollable::Scrollable;
use crate::widget::Id;
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>) {}
/// 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(...)"),
}
}
}

View file

@ -0,0 +1,169 @@
//! 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.
focused: Option<usize>,
/// The total amount of focusable widgets.
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 })
}

View file

@ -0,0 +1,35 @@
//! 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`.
fn snap_to(&mut self, percentage: f32);
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
/// the provided `percentage`.
pub fn snap_to<T>(target: Id, percentage: f32) -> impl Operation<T> {
struct SnapTo {
target: Id,
percentage: f32,
}
impl<T> Operation<T> for SnapTo {
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
if Some(&self.target) == id {
state.snap_to(self.percentage);
}
}
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
}
SnapTo { target, percentage }
}

View file

@ -30,6 +30,8 @@ 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;
@ -37,13 +39,12 @@ use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::container;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size,
Vector, Widget,
};
pub use iced_style::pane_grid::{Line, StyleSheet};
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@ -66,7 +67,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
/// ## Example
///
/// ```
/// # use iced_native::widget::{pane_grid, Text};
/// # use iced_native::widget::{pane_grid, text};
/// #
/// # type PaneGrid<'a, Message> =
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
@ -84,10 +85,10 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
/// PaneGrid::new(&mut state, |pane, state| {
/// PaneGrid::new(&state, |pane, state| {
/// pane_grid::Content::new(match state {
/// PaneState::SomePane => Text::new("This is some pane"),
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
/// PaneState::SomePane => text("This is some pane"),
/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
/// })
/// })
/// .on_drag(Message::PaneDragged)
@ -99,8 +100,7 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
state: &'a mut state::Internal,
action: &'a mut state::Action,
state: &'a state::Internal,
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length,
height: Length,
@ -121,21 +121,20 @@ where
/// The view function will be called to display each [`Pane`] present in the
/// [`State`].
pub fn new<T>(
state: &'a mut State<T>,
view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
state: &'a State<T>,
view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>,
) -> Self {
let elements = {
state
.panes
.iter_mut()
.iter()
.map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
.collect()
};
Self {
state: &mut state.internal,
action: &mut state.action,
elements,
state: &state.internal,
width: Length::Fill,
height: Length::Fill,
spacing: 0,
@ -211,6 +210,220 @@ where
}
}
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.elements
.iter()
.map(|(_, content)| content.state())
.collect()
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children_custom(
&self.elements,
|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.state,
self.width,
self.height,
self.spacing,
self.elements.iter().map(|(pane, content)| (*pane, content)),
|element, renderer, limits| element.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 {
let action = tree.state.downcast_mut::<state::Action>();
let event_status = update(
action,
self.state,
&event,
layout,
cursor_position,
shell,
self.spacing,
self.elements.iter().map(|(pane, content)| (*pane, content)),
&self.on_click,
&self.on_drag,
&self.on_resize,
);
let picked_pane = action.picked_pane().map(|(pane, _)| pane);
self.elements
.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.state,
layout,
cursor_position,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
.unwrap_or_else(|| {
self.elements
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|(((_pane, content), tree), layout)| {
content.mouse_interaction(
tree,
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,
) {
draw(
tree.state.downcast_ref(),
self.state,
layout,
cursor_position,
renderer,
theme,
style,
viewport,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
self.style,
self.elements
.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 self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.elements
.iter()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|(((_, pane), tree), layout)| {
pane.overlay(tree, layout, renderer)
})
.next()
}
}
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,
@ -656,175 +869,6 @@ pub struct ResizeEvent {
pub ratio: f32,
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
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.state,
self.width,
self.height,
self.spacing,
self.elements.iter().map(|(pane, content)| (*pane, content)),
|element, renderer, limits| element.layout(renderer, limits),
)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let event_status = update(
self.action,
self.state,
&event,
layout,
cursor_position,
shell,
self.spacing,
self.elements.iter().map(|(pane, content)| (*pane, content)),
&self.on_click,
&self.on_drag,
&self.on_resize,
);
let picked_pane = self.action.picked_pane().map(|(pane, _)| pane);
self.elements
.iter_mut()
.zip(layout.children())
.map(|((pane, content), layout)| {
let is_picked = picked_pane == Some(*pane);
content.on_event(
event.clone(),
layout,
cursor_position,
renderer,
clipboard,
shell,
is_picked,
)
})
.fold(event_status, event::Status::merge)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
self.action,
self.state,
layout,
cursor_position,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
.unwrap_or_else(|| {
self.elements
.iter()
.zip(layout.children())
.map(|((_pane, content), layout)| {
content.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
})
.max()
.unwrap_or_default()
})
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
draw(
self.action,
self.state,
layout,
cursor_position,
renderer,
theme,
style,
viewport,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
self.style,
self.elements.iter().map(|(pane, content)| (*pane, content)),
|pane, renderer, style, layout, cursor_position, rectangle| {
pane.draw(
renderer,
theme,
style,
layout,
cursor_position,
rectangle,
);
},
)
}
fn overlay(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.elements
.iter_mut()
.zip(layout.children())
.filter_map(|((_, pane), layout)| pane.overlay(layout, renderer))
.next()
}
}
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)
}
}
/*
* Helpers
*/

View file

@ -5,6 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::pane_grid::{Draggable, TitleBar};
use crate::widget::Tree;
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// The content of a [`Pane`].
@ -59,11 +60,37 @@ 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
/// [`Renderer`]: iced_native::Renderer
pub fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -89,6 +116,7 @@ where
let show_controls = bounds.contains(cursor_position);
title_bar.draw(
&tree.children[1],
renderer,
theme,
style,
@ -98,7 +126,8 @@ where
show_controls,
);
self.body.draw(
self.body.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
@ -107,7 +136,8 @@ where
viewport,
);
} else {
self.body.draw(
self.body.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
@ -131,7 +161,7 @@ where
let title_bar_size = title_bar_layout.size();
let mut body_layout = self.body.layout(
let mut body_layout = self.body.as_widget().layout(
renderer,
&layout::Limits::new(
Size::ZERO,
@ -149,12 +179,13 @@ where
vec![title_bar_layout, body_layout],
)
} else {
self.body.layout(renderer, limits)
self.body.as_widget().layout(renderer, limits)
}
}
pub(crate) fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -169,6 +200,7 @@ where
let mut children = layout.children();
event_status = title_bar.on_event(
&mut tree.children[1],
event.clone(),
children.next().unwrap(),
cursor_position,
@ -185,7 +217,8 @@ where
let body_status = if is_picked {
event::Status::Ignored
} else {
self.body.on_event(
self.body.as_widget_mut().on_event(
&mut tree.children[0],
event,
body_layout,
cursor_position,
@ -200,6 +233,7 @@ where
pub(crate) fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@ -218,6 +252,7 @@ where
}
let mouse_interaction = title_bar.mouse_interaction(
&tree.children[1],
title_bar_layout,
cursor_position,
viewport,
@ -230,25 +265,46 @@ where
};
self.body
.mouse_interaction(body_layout, cursor_position, viewport, renderer)
.as_widget()
.mouse_interaction(
&tree.children[0],
body_layout,
cursor_position,
viewport,
renderer,
)
.max(title_bar_interaction)
}
pub(crate) fn overlay(
&mut self,
pub(crate) fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
if let Some(title_bar) = self.title_bar.as_mut() {
) -> Option<overlay::Element<'b, Message, Renderer>> {
if let Some(title_bar) = self.title_bar.as_ref() {
let mut children = layout.children();
let title_bar_layout = children.next()?;
match title_bar.overlay(title_bar_layout, renderer) {
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.overlay(children.next()?, renderer),
None => self.body.as_widget().overlay(
body_state,
children.next()?,
renderer,
),
}
} else {
self.body.overlay(layout, renderer)
self.body.as_widget().overlay(
&mut tree.children[0],
layout,
renderer,
)
}
}
}

View file

@ -31,8 +31,6 @@ pub struct State<T> {
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub internal: Internal,
pub(super) action: Action,
}
impl<T> State<T> {
@ -54,11 +52,7 @@ impl<T> State<T> {
let internal =
Internal::from_configuration(&mut panes, config.into(), 0);
State {
panes,
internal,
action: Action::Idle,
}
State { panes, internal }
}
/// Returns the total amount of panes in the [`State`].

View file

@ -4,6 +4,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::Tree;
use crate::{
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
};
@ -86,11 +87,37 @@ 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
/// [`Renderer`]: iced_native::Renderer
pub fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
@ -118,14 +145,15 @@ where
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;
}
if show_controls || self.always_show_controls {
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.draw(
controls.as_widget().draw(
&tree.children[1],
renderer,
theme,
&inherited_style,
@ -137,7 +165,8 @@ where
}
if show_title {
self.content.draw(
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&inherited_style,
@ -186,11 +215,14 @@ where
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();
@ -221,6 +253,7 @@ where
pub(crate) fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -243,7 +276,8 @@ where
show_title = false;
}
controls.on_event(
controls.as_widget_mut().on_event(
&mut tree.children[1],
event.clone(),
controls_layout,
cursor_position,
@ -256,7 +290,8 @@ where
};
let title_status = if show_title {
self.content.on_event(
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
title_layout,
cursor_position,
@ -273,6 +308,7 @@ where
pub(crate) fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@ -284,7 +320,8 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
let title_interaction = self.content.mouse_interaction(
let title_interaction = self.content.as_widget().mouse_interaction(
&tree.children[0],
title_layout,
cursor_position,
viewport,
@ -293,7 +330,8 @@ where
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
let controls_interaction = controls.mouse_interaction(
let controls_interaction = controls.as_widget().mouse_interaction(
&tree.children[1],
controls_layout,
cursor_position,
viewport,
@ -312,11 +350,12 @@ where
}
}
pub(crate) fn overlay(
&mut self,
pub(crate) fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
) -> Option<overlay::Element<'b, Message, Renderer>> {
let mut children = layout.children();
let padded = children.next()?;
@ -327,12 +366,23 @@ where
content, controls, ..
} = self;
content.overlay(title_layout, renderer).or_else(move || {
controls.as_mut().and_then(|controls| {
let controls_layout = children.next()?;
let mut states = tree.children.iter_mut();
let title_state = states.next().unwrap();
let controls_state = states.next().unwrap();
controls.overlay(controls_layout, renderer)
content
.as_widget()
.overlay(title_state, title_layout, renderer)
.or_else(move || {
controls.as_ref().and_then(|controls| {
let controls_layout = children.next()?;
controls.as_widget().overlay(
controls_state,
controls_layout,
renderer,
)
})
})
})
}
}

View file

@ -9,8 +9,7 @@ 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, Point, Rectangle, Shell, Size,
Widget,
@ -27,8 +26,7 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State<T>,
on_selected: Box<dyn Fn(T) -> Message>,
on_selected: Box<dyn Fn(T) -> Message + 'a>,
options: Cow<'a, [T]>,
placeholder: Option<String>,
selected: Option<T>,
@ -39,35 +37,6 @@ where
style: <Renderer::Theme as StyleSheet>::Style,
}
/// The local state of a [`PickList`].
#[derive(Debug, Clone)]
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()
}
}
impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer>
where
T: ToString + Eq,
@ -78,17 +47,14 @@ where
/// The default padding of a [`PickList`].
pub const DEFAULT_PADDING: Padding = Padding::new(5);
/// Creates a new [`PickList`] with the given [`State`], a list of options,
/// the current selected value, and the message to produce when an option is
/// selected.
/// 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(
state: &'a mut State<T>,
options: impl Into<Cow<'a, [T]>>,
selected: Option<T>,
on_selected: impl Fn(T) -> Message + 'static,
on_selected: impl Fn(T) -> Message + 'a,
) -> Self {
Self {
state,
on_selected: Box::new(on_selected),
options: options.into(),
placeholder: None,
@ -141,6 +107,168 @@ where
}
}
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,
{
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,
) {
draw(
renderer,
theme,
layout,
cursor_position,
self.padding,
self.text_size,
&self.font,
self.placeholder.as_deref(),
self.selected.as_ref(),
self.style,
)
}
fn overlay<'b>(
&'b 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.clone(),
&self.options,
self.style,
)
}
}
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,
{
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()
}
}
/// Computes the layout of a [`PickList`].
pub fn layout<Renderer, T>(
renderer: &Renderer,
@ -429,127 +557,3 @@ pub fn draw<T, Renderer>(
});
}
}
impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
for PickList<'a, T, Message, Renderer>
where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'static,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
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,
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,
|| &mut self.state,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
draw(
renderer,
theme,
layout,
cursor_position,
self.padding,
self.text_size,
&self.font,
self.placeholder.as_deref(),
self.selected.as_ref(),
self.style,
)
}
fn overlay(
&mut self,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
overlay(
layout,
self.state,
self.padding,
self.text_size,
self.font.clone(),
&self.options,
self.style,
)
}
}
impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'static,
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet
+ container::StyleSheet
+ scrollable::StyleSheet
+ menu::StyleSheet,
<Renderer::Theme as StyleSheet>::Style:
Into<<Renderer::Theme as menu::StyleSheet>::Style>,
{
fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
Element::new(pick_list)
}
}

View file

@ -1,6 +1,7 @@
//! 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;
@ -105,6 +106,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,

View file

@ -6,7 +6,7 @@ use crate::mouse;
use crate::renderer;
use crate::text;
use crate::touch;
use crate::widget::{self, Row, Text};
use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
Shell, Widget,
@ -176,6 +176,7 @@ where
fn on_event(
&mut self,
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -200,6 +201,7 @@ where
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@ -214,6 +216,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,

View file

@ -1,16 +1,15 @@
//! Distribute content horizontally.
use crate::event::{self, Event};
use crate::layout;
use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::{Operation, Tree};
use crate::{
Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Widget,
Alignment, Clipboard, Element, Length, Padding, Point, Rectangle, Shell,
Widget,
};
use std::u32;
/// A container that distributes its contents horizontally.
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
@ -18,8 +17,6 @@ pub struct Row<'a, Message, Renderer> {
padding: Padding,
width: Length,
height: Length,
max_width: u32,
max_height: u32,
align_items: Alignment,
children: Vec<Element<'a, Message, Renderer>>,
}
@ -39,8 +36,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Alignment::Start,
children,
}
@ -74,18 +69,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
self
}
/// Sets the maximum width of the [`Row`].
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
pub fn align_items(mut self, align: Alignment) -> Self {
self.align_items = align;
@ -93,10 +76,10 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
}
/// Adds an [`Element`] to the [`Row`].
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
pub fn push(
mut self,
child: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
self.children.push(child.into());
self
}
@ -113,6 +96,14 @@ impl<'a, Message, Renderer> Widget<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
}
@ -126,11 +117,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits
.max_width(self.max_width)
.max_height(self.max_height)
.width(self.width)
.height(self.height);
let limits = limits.width(self.width).height(self.height);
layout::flex::resolve(
layout::flex::Axis::Horizontal,
@ -143,8 +130,26 @@ where
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
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, operation);
})
});
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -154,9 +159,11 @@ where
) -> event::Status {
self.children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|(child, layout)| {
child.widget.on_event(
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor_position,
@ -170,6 +177,7 @@ where
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@ -177,9 +185,11 @@ where
) -> mouse::Interaction {
self.children
.iter()
.zip(&tree.children)
.zip(layout.children())
.map(|(child, layout)| {
child.widget.mouse_interaction(
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
state,
layout,
cursor_position,
viewport,
@ -192,6 +202,7 @@ where
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -199,8 +210,14 @@ where
cursor_position: Point,
viewport: &Rectangle,
) {
for (child, layout) in self.children.iter().zip(layout.children()) {
child.draw(
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child.as_widget().draw(
state,
renderer,
theme,
style,
@ -211,28 +228,23 @@ where
}
}
fn overlay(
&mut self,
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
self.children
.iter_mut()
.zip(layout.children())
.filter_map(|(child, layout)| {
child.widget.overlay(layout, renderer)
})
.next()
) -> Option<overlay::Element<'b, Message, Renderer>> {
overlay::from_children(&self.children, tree, layout, renderer)
}
}
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + crate::Renderer,
Message: 'a,
Renderer: crate::Renderer + 'a,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(row)
fn from(row: Row<'a, Message, Renderer>) -> Self {
Self::new(row)
}
}

View file

@ -1,6 +1,7 @@
//! 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, Point, Rectangle, Size, Widget};
pub use iced_style::rule::{Appearance, FillMode, StyleSheet};
@ -78,6 +79,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,

View file

@ -5,10 +5,12 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::Column;
use crate::widget;
use crate::widget::operation::{self, Operation};
use crate::widget::tree::{self, Tree};
use crate::{
Alignment, Background, Clipboard, Color, Element, Layout, Length, Padding,
Point, Rectangle, Shell, Size, Vector, Widget,
Background, Clipboard, Color, Command, Element, Layout, Length, Point,
Rectangle, Shell, Size, Vector, Widget,
};
use std::{f32, u32};
@ -30,13 +32,12 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
id: Option<Id>,
height: Length,
max_height: u32,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
content: Column<'a, Message, Renderer>,
content: Element<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message + 'a>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -46,40 +47,23 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
/// Creates a new [`Scrollable`] with the given [`State`].
pub fn new(state: &'a mut State) -> Self {
/// Creates a new [`Scrollable`].
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Scrollable {
state,
id: None,
height: Length::Shrink,
max_height: u32::MAX,
scrollbar_width: 10,
scrollbar_margin: 0,
scroller_width: 10,
content: Column::new(),
content: content.into(),
on_scroll: None,
style: Default::default(),
}
}
/// 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, units: u16) -> Self {
self.content = self.content.spacing(units);
self
}
/// Sets the [`Padding`] of the [`Scrollable`].
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
self.content = self.content.padding(padding);
self
}
/// Sets the width of the [`Scrollable`].
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
/// Sets the [`Id`] of the [`Scrollable`].
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}
@ -89,24 +73,6 @@ where
self
}
/// Sets the maximum width of the [`Scrollable`].
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
pub fn align_items(mut self, align_items: Alignment) -> Self {
self.content = self.content.align_items(align_items);
self
}
/// Sets the scrollbar width of the [`Scrollable`] .
/// Silently enforces a minimum value of 1.
pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
@ -132,7 +98,7 @@ where
///
/// The function takes the new relative offset of the [`Scrollable`]
/// (e.g. `0` means top, while `1` means bottom).
pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'a) -> Self {
self.on_scroll = Some(Box::new(f));
self
}
@ -145,15 +111,233 @@ where
self.style = style.into();
self
}
}
/// Adds an element to the [`Scrollable`].
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
self.content = self.content.push(child);
self
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
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 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.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
Widget::<Message, Renderer>::width(self),
self.height,
u32::MAX,
|renderer, limits| {
self.content.as_widget().layout(renderer, limits)
},
)
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
operation: &mut dyn Operation<Message>,
) {
let state = tree.state.downcast_mut::<State>();
operation.scrollable(state, self.id.as_ref().map(|id| &id.0));
operation.container(None, &mut |operation| {
self.content.as_widget().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
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 {
update(
tree.state.downcast_mut::<State>(),
event,
layout,
cursor_position,
clipboard,
shell,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
&self.on_scroll,
|event, layout, cursor_position, clipboard, shell| {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
},
)
}
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::<State>(),
renderer,
theme,
layout,
cursor_position,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
self.style,
|renderer, layout, cursor_position, viewport| {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
)
},
)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
tree.state.downcast_ref::<State>(),
layout,
cursor_position,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
|layout, cursor_position, viewport| {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
cursor_position,
viewport,
renderer,
)
},
)
}
fn overlay<'b>(
&'b self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content
.as_widget()
.overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
)
.map(|overlay| {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = tree
.state
.downcast_ref::<State>()
.offset(bounds, content_bounds);
overlay.translate(Vector::new(0.0, -(offset as f32)))
})
}
}
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
text_input: Scrollable<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(text_input)
}
}
/// The identifier of a [`Scrollable`].
#[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())
}
}
/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`]
/// to the provided `percentage`.
pub fn snap_to<Message: 'static>(id: Id, percentage: f32) -> Command<Message> {
Command::widget(operation::scrollable::snap_to(id.0, percentage))
}
/// Computes the layout of a [`Scrollable`].
@ -625,145 +809,6 @@ fn notify_on_scroll<Message>(
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content)
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(
renderer,
limits,
Widget::<Message, Renderer>::width(self),
self.height,
self.max_height,
|renderer, limits| self.content.layout(renderer, limits),
)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
update(
self.state,
event,
layout,
cursor_position,
clipboard,
shell,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
&self.on_scroll,
|event, layout, cursor_position, clipboard, shell| {
self.content.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
},
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
self.state,
layout,
cursor_position,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
|layout, cursor_position, viewport| {
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,
_viewport: &Rectangle,
) {
draw(
self.state,
renderer,
theme,
layout,
cursor_position,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
self.style,
|renderer, layout, cursor_position, viewport| {
self.content.draw(
renderer,
theme,
style,
layout,
cursor_position,
viewport,
)
},
)
}
fn overlay(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
let Self { content, state, .. } = self;
content
.overlay(layout.children().next().unwrap(), renderer)
.map(|overlay| {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = state.offset(bounds, content_bounds);
overlay.translate(Vector::new(0.0, -(offset as f32)))
})
}
}
/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy)]
pub struct State {
@ -782,6 +827,12 @@ impl Default for State {
}
}
impl operation::Scrollable for State {
fn snap_to(&mut self, percentage: f32) {
State::snap_to(self, percentage);
}
}
/// The local state of a [`Scrollable`].
#[derive(Debug, Clone, Copy)]
enum Offset {
@ -926,17 +977,3 @@ struct Scroller {
/// The bounds of the [`Scroller`].
bounds: Rectangle,
}
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a + crate::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
scrollable: Scrollable<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(scrollable)
}
}

View file

@ -6,6 +6,7 @@ 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, Point, Rectangle,
Shell, Size, Widget,
@ -29,15 +30,15 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
/// # use iced_native::renderer::Null;
/// #
/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>;
/// #
/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
///
/// let state = &mut slider::State::new();
/// let value = 50.0;
///
/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged);
/// 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)
@ -47,11 +48,10 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
range: RangeInclusive<T>,
step: T,
value: T,
on_change: Box<dyn Fn(T) -> Message>,
on_change: Box<dyn Fn(T) -> Message + 'a>,
on_release: Option<Message>,
width: Length,
height: u16,
@ -71,20 +71,14 @@ where
/// Creates a new [`Slider`].
///
/// It expects:
/// * the local [`State`] of the [`Slider`]
/// * 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>(
state: &'a mut State,
range: RangeInclusive<T>,
value: T,
on_change: F,
) -> Self
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
where
F: 'static + Fn(T) -> Message,
F: 'a + Fn(T) -> Message,
{
let value = if value >= *range.start() {
value
@ -99,7 +93,6 @@ where
};
Slider {
state,
value,
range,
step: T::from(1),
@ -150,6 +143,120 @@ where
}
}
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(Length::Units(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>(
@ -366,102 +473,3 @@ impl State {
State::default()
}
}
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 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(Length::Units(self.height));
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
&mut self,
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.state,
&mut self.value,
&self.range,
self.step,
self.on_change.as_ref(),
&self.on_release,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
cursor_position,
self.state,
self.value,
&self.range,
theme,
self.style,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position, self.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)
}
}

View file

@ -1,6 +1,7 @@
//! 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.
@ -59,6 +60,7 @@ where
fn draw(
&self,
_state: &Tree,
_renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,

View file

@ -1,13 +1,16 @@
//! Display vector graphics in your application.
use crate::layout;
use crate::renderer;
use crate::svg::{self, Handle};
use crate::svg;
use crate::widget::Tree;
use crate::{
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
};
use std::path::PathBuf;
pub use svg::Handle;
/// A vector graphics image.
///
/// An [`Svg`] image resizes smoothly without losing any quality.
@ -109,6 +112,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,

View file

@ -3,6 +3,7 @@ use crate::alignment;
use crate::layout;
use crate::renderer;
use crate::text;
use crate::widget::Tree;
use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
pub use iced_style::text::{Appearance, StyleSheet};
@ -44,9 +45,9 @@ where
Renderer::Theme: StyleSheet,
{
/// Create a new fragment of [`Text`] with the given contents.
pub fn new<T: Into<String>>(label: T) -> Self {
pub fn new<T: ToString>(label: T) -> Self {
Text {
content: label.into(),
content: label.to_string(),
size: None,
font: Default::default(),
width: Length::Shrink,
@ -145,6 +146,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -243,3 +245,13 @@ where
}
}
}
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()
}
}

View file

@ -19,9 +19,12 @@ use crate::mouse::{self, click};
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
use crate::widget;
use crate::widget::operation::{self, Operation};
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Size, Vector, Widget,
};
pub use iced_style::text_input::{Appearance, StyleSheet};
@ -30,20 +33,15 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
///
/// # Example
/// ```
/// # use iced_native::renderer::Null;
/// # use iced_native::widget::text_input;
/// #
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>;
/// #[derive(Debug, Clone)]
/// enum Message {
/// TextInputChanged(String),
/// }
///
/// let mut state = text_input::State::new();
/// let value = "Some text";
///
/// let input = TextInput::new(
/// &mut state,
/// "This is the placeholder...",
/// value,
/// Message::TextInputChanged,
@ -57,7 +55,7 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
id: Option<Id>,
placeholder: String,
value: Value,
is_secure: bool,
@ -80,21 +78,15 @@ where
/// Creates a new [`TextInput`].
///
/// It expects:
/// - some [`State`]
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`TextInput`] changes
pub fn new<F>(
state: &'a mut State,
placeholder: &str,
value: &str,
on_change: F,
) -> Self
/// - a placeholder,
/// - the current value, and
/// - a function that produces a message when the [`TextInput`] changes.
pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
where
F: 'a + Fn(String) -> Message,
{
TextInput {
state,
id: None,
placeholder: String::from(placeholder),
value: Value::new(value),
is_secure: false,
@ -109,6 +101,12 @@ where
}
}
/// Sets the [`Id`] of the [`TextInput`].
pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
}
/// Converts the [`TextInput`] into a secure password input.
pub fn password(mut self) -> Self {
self.is_secure = true;
@ -127,7 +125,7 @@ where
/// Sets the [`Font`] of the [`TextInput`].
///
/// [`Font`]: crate::text::Renderer::Font
/// [`Font`]: text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
@ -166,17 +164,13 @@ where
self
}
/// Returns the current [`State`] of the [`TextInput`].
pub fn state(&self) -> &State {
self.state
}
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
/// [`text_input::Value`] if provided.
///
/// [`Renderer`]: text::Renderer
pub fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
@ -188,7 +182,7 @@ where
theme,
layout,
cursor_position,
self.state,
tree.state.downcast_ref::<State>(),
value.unwrap_or(&self.value),
&self.placeholder,
self.size,
@ -199,6 +193,150 @@ where
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: text::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 {
layout(renderer, limits, self.width, self.padding, self.size)
}
fn operate(
&self,
tree: &mut Tree,
_layout: Layout<'_>,
operation: &mut dyn Operation<Message>,
) {
let state = tree.state.downcast_mut::<State>();
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
}
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,
renderer,
clipboard,
shell,
&mut self.value,
self.size,
&self.font,
self.is_secure,
self.on_change.as_ref(),
self.on_paste.as_deref(),
&self.on_submit,
|| 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,
) {
draw(
renderer,
theme,
layout,
cursor_position,
tree.state.downcast_ref::<State>(),
&self.value,
&self.placeholder,
self.size,
&self.font,
self.is_secure,
self.style,
)
}
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position)
}
}
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(text_input)
}
}
/// The identifier of a [`TextInput`].
#[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())
}
}
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::focusable::focus(id.0))
}
/// Computes the layout of a [`TextInput`].
pub fn layout<Renderer>(
renderer: &Renderer,
@ -777,93 +915,6 @@ pub fn mouse_interaction(
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
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.size)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
update(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
&mut self.value,
self.size,
&self.font,
self.is_secure,
self.on_change.as_ref(),
self.on_paste.as_deref(),
&self.on_submit,
|| &mut self.state,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
) {
self.draw(renderer, theme, layout, cursor_position, None)
}
}
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Message: 'a + Clone,
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(text_input)
}
}
/// The state of a [`TextInput`].
#[derive(Debug, Default, Clone)]
pub struct State {
@ -907,6 +958,7 @@ impl State {
/// Focuses the [`TextInput`].
pub fn focus(&mut self) {
self.is_focused = true;
self.move_cursor_to_end();
}
/// Unfocuses the [`TextInput`].
@ -935,6 +987,20 @@ impl State {
}
}
impl operation::Focusable for State {
fn is_focused(&self) -> bool {
State::is_focused(self)
}
fn focus(&mut self) {
State::focus(self)
}
fn unfocus(&mut self) {
State::unfocus(self)
}
}
mod platform {
use crate::keyboard;

View file

@ -5,7 +5,7 @@ use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget::{self, Row, Text};
use crate::widget::{self, Row, Text, Tree};
use crate::{
Alignment, Clipboard, Element, Event, Layout, Length, Point, Rectangle,
Shell, Widget,
@ -180,6 +180,7 @@ where
fn on_event(
&mut self,
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -205,6 +206,7 @@ where
fn mouse_interaction(
&self,
_state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
_viewport: &Rectangle,
@ -219,6 +221,7 @@ where
fn draw(
&self,
_state: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,

View file

@ -6,7 +6,8 @@ use crate::renderer;
use crate::text;
use crate::widget;
use crate::widget::container;
use crate::widget::text::Text;
use crate::widget::overlay;
use crate::widget::{Text, Tree};
use crate::{
Clipboard, Element, Event, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@ -14,7 +15,7 @@ use crate::{
/// An element to display a widget over another.
#[allow(missing_debug_implementations)]
pub struct Tooltip<'a, Message, Renderer>
pub struct Tooltip<'a, Message, Renderer: text::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
@ -89,6 +90,153 @@ where
}
}
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.children().next().unwrap(),
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.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 self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget().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 {
@ -220,122 +368,3 @@ pub fn draw<Renderer>(
});
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Tooltip<'a, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{
fn width(&self) -> Length {
self.content.width()
}
fn height(&self) -> Length {
self.content.height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.content.layout(renderer, limits)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.content.widget.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
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,
inherited_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.content.draw(
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.style,
|renderer, limits| {
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
},
|renderer, defaults, layout, cursor_position, viewport| {
Widget::<(), Renderer>::draw(
tooltip,
renderer,
theme,
defaults,
layout,
cursor_position,
viewport,
);
},
)
}
}
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)
}
}

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

@ -0,0 +1,187 @@
//! Store internal widget state in a state tree to ensure continuity.
use crate::Widget;
use std::any::{self, Any};
use std::borrow::Borrow;
use std::fmt;
/// A persistent state widget tree.
///
/// A [`Tree`] is normally associated with a specific widget in the widget tree.
#[derive(Debug)]
pub struct Tree {
/// The tag of the [`Tree`].
pub tag: Tag,
/// The [`State`] of the [`Tree`].
pub state: State,
/// The children of the root widget of the [`Tree`].
pub children: Vec<Tree>,
}
impl Tree {
/// Creates an empty, stateless [`Tree`] with no children.
pub fn empty() -> Self {
Self {
tag: Tag::stateless(),
state: State::None,
children: Vec::new(),
}
}
/// Creates a new [`Tree`] for the provided [`Element`].
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 [`Element`].
///
/// If the tag of the [`Element`] matches the tag of the [`Tree`], then the
/// [`Element`] 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 [`Element`].
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 [`Element`] 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"),
}
}
}