Replace stateful widgets with new iced_pure API

This commit is contained in:
Héctor Ramón Jiménez 2022-07-27 06:49:20 +02:00
parent c44267b85f
commit ff2519b1d4
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
142 changed files with 3631 additions and 14494 deletions

View file

@ -7,6 +7,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::tree::{self, Tree};
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Vector, Widget,
@ -17,8 +18,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 +26,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 +41,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 +55,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 +65,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 +99,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 +116,159 @@ 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 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 +434,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::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);
@ -145,6 +145,7 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -154,9 +155,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 +173,7 @@ where
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@ -177,9 +181,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 +198,7 @@ where
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -199,8 +206,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 +224,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::Tree;
use crate::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Widget,
@ -121,6 +122,144 @@ 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 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 +294,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 +316,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<'a, Message, Renderer>(
children: Vec<Element<'a, Message, Renderer>>,
) -> widget::Row<'a, Message, Renderer> {
widget::Row::with_children(children)
}
/// Creates a new [`Row`] with the given children.
///
/// [`Row`]: widget::Row
pub fn row<'a, Message, Renderer>(
children: Vec<Element<'a, Message, Renderer>>,
) -> widget::Row<'a, 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 Into<String>) -> 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)
}

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

@ -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::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,
@ -145,6 +132,7 @@ where
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
@ -154,9 +142,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 +160,7 @@ where
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
@ -177,9 +168,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 +185,7 @@ where
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -199,8 +193,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 +211,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,10 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::widget::Column;
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, Element, Layout, Length, Point, Rectangle,
Shell, Size, Vector, Widget,
};
use std::{f32, u32};
@ -30,13 +30,11 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
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,67 +44,25 @@ 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,
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);
self
}
/// Sets the height of the [`Scrollable`].
pub fn height(mut self, height: Length) -> Self {
self.height = height;
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 +88,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,14 +101,189 @@ 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 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)
}
}
@ -625,145 +756,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 {
@ -926,17 +918,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};
@ -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,6 +19,7 @@ use crate::mouse::{self, click};
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@ -30,20 +31,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 +53,6 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
state: &'a mut State,
placeholder: String,
value: Value,
is_secure: bool,
@ -80,21 +75,14 @@ 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,
placeholder: String::from(placeholder),
value: Value::new(value),
is_secure: false,
@ -127,7 +115,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 +154,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 +172,7 @@ where
theme,
layout,
cursor_position,
self.state,
tree.state.downcast_ref::<State>(),
value.unwrap_or(&self.value),
&self.placeholder,
self.size,
@ -199,6 +183,116 @@ 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 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)
}
}
/// Computes the layout of a [`TextInput`].
pub fn layout<Renderer>(
renderer: &Renderer,
@ -777,93 +871,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 {

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"),
}
}
}