summaryrefslogtreecommitdiffstats
path: root/core/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/widget')
-rw-r--r--core/src/widget/operation.rs32
-rw-r--r--core/src/widget/operation/focusable.rs16
-rw-r--r--core/src/widget/operation/scrollable.rs23
-rw-r--r--core/src/widget/operation/text_input.rs13
-rw-r--r--core/src/widget/text.rs132
-rw-r--r--core/src/widget/tree.rs86
6 files changed, 233 insertions, 69 deletions
diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs
index ad188c36..b91cf9ac 100644
--- a/core/src/widget/operation.rs
+++ b/core/src/widget/operation.rs
@@ -8,6 +8,7 @@ pub use scrollable::Scrollable;
pub use text_input::TextInput;
use crate::widget::Id;
+use crate::{Rectangle, Vector};
use std::any::Any;
use std::fmt;
@@ -23,6 +24,7 @@ pub trait Operation<T> {
fn container(
&mut self,
id: Option<&Id>,
+ bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
);
@@ -30,7 +32,14 @@ pub trait Operation<T> {
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
/// Operates on a widget that can be scrolled.
- fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
+ fn scrollable(
+ &mut self,
+ _state: &mut dyn Scrollable,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ _translation: Vector,
+ ) {
+ }
/// Operates on a widget that has text input.
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
@@ -92,6 +101,7 @@ where
fn container(
&mut self,
id: Option<&Id>,
+ bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
struct MapRef<'a, A> {
@@ -102,11 +112,12 @@ where
fn container(
&mut self,
id: Option<&Id>,
+ bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
let Self { operation, .. } = self;
- operation.container(id, &mut |operation| {
+ operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut MapRef { operation });
});
}
@@ -115,8 +126,10 @@ where
&mut self,
state: &mut dyn Scrollable,
id: Option<&Id>,
+ bounds: Rectangle,
+ translation: Vector,
) {
- self.operation.scrollable(state, id);
+ self.operation.scrollable(state, id, bounds, translation);
}
fn focusable(
@@ -145,15 +158,21 @@ where
MapRef {
operation: operation.as_mut(),
}
- .container(id, operate_on_children);
+ .container(id, bounds, operate_on_children);
}
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
self.operation.focusable(state, id);
}
- fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
- self.operation.scrollable(state, id);
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ translation: Vector,
+ ) {
+ self.operation.scrollable(state, id, bounds, translation);
}
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
@@ -197,6 +216,7 @@ pub fn scope<T: 'static>(
fn container(
&mut self,
id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
) {
if id == Some(&self.target) {
diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs
index 312e4894..68c22faa 100644
--- a/core/src/widget/operation/focusable.rs
+++ b/core/src/widget/operation/focusable.rs
@@ -1,6 +1,7 @@
//! Operate on widgets that can be focused.
use crate::widget::operation::{Operation, Outcome};
use crate::widget::Id;
+use crate::Rectangle;
/// The internal state of a widget that can be focused.
pub trait Focusable {
@@ -45,9 +46,10 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
@@ -80,9 +82,10 @@ where
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
fn finish(&self) -> Outcome<T> {
@@ -126,9 +129,10 @@ pub fn focus_previous<T>() -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
@@ -159,9 +163,10 @@ pub fn focus_next<T>() -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
@@ -185,9 +190,10 @@ pub fn find_focused() -> impl Operation<Id> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
fn finish(&self) -> Outcome<Id> {
diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs
index f947344d..12161255 100644
--- a/core/src/widget/operation/scrollable.rs
+++ b/core/src/widget/operation/scrollable.rs
@@ -1,5 +1,6 @@
//! Operate on widgets that can be scrolled.
use crate::widget::{Id, Operation};
+use crate::{Rectangle, Vector};
/// The internal state of a widget that can be scrolled.
pub trait Scrollable {
@@ -22,12 +23,19 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
- fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ _translation: Vector,
+ ) {
if Some(&self.target) == id {
state.snap_to(self.offset);
}
@@ -49,12 +57,19 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
- fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ _translation: Vector,
+ ) {
if Some(&self.target) == id {
state.scroll_to(self.offset);
}
diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs
index 4c773e99..41731d4c 100644
--- a/core/src/widget/operation/text_input.rs
+++ b/core/src/widget/operation/text_input.rs
@@ -1,6 +1,7 @@
//! Operate on widgets that have text input.
use crate::widget::operation::Operation;
use crate::widget::Id;
+use crate::Rectangle;
/// The internal state of a widget that has text input.
pub trait TextInput {
@@ -34,9 +35,10 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
@@ -63,9 +65,10 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
@@ -93,9 +96,10 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
@@ -121,9 +125,10 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
+ _bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
- operate_on_children(self)
+ operate_on_children(self);
}
}
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index 79df2b02..97e0acac 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -3,9 +3,9 @@ use crate::alignment;
use crate::layout;
use crate::mouse;
use crate::renderer;
-use crate::text;
-use crate::widget::Tree;
-use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};
+use crate::text::{self, Paragraph};
+use crate::widget::tree::{self, Tree};
+use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
use std::borrow::Cow;
@@ -19,7 +19,7 @@ where
Renderer::Theme: StyleSheet,
{
content: Cow<'a, str>,
- size: Option<f32>,
+ size: Option<Pixels>,
line_height: LineHeight,
width: Length,
height: Length,
@@ -53,7 +53,7 @@ where
/// Sets the size of the [`Text`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
- self.size = Some(size.into().0);
+ self.size = Some(size.into());
self
}
@@ -117,11 +117,23 @@ where
}
}
+/// The internal state of a [`Text`] widget.
+#[derive(Debug, Default)]
+pub struct State<P: Paragraph>(P);
+
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State<Renderer::Paragraph>>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State(Renderer::Paragraph::default()))
+ }
+
fn width(&self) -> Length {
self.width
}
@@ -132,30 +144,29 @@ where
fn layout(
&self,
+ tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits.width(self.width).height(self.height);
-
- let size = self.size.unwrap_or_else(|| renderer.default_size());
-
- let bounds = renderer.measure(
+ layout(
+ tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
+ renderer,
+ limits,
+ self.width,
+ self.height,
&self.content,
- size,
self.line_height,
- self.font.unwrap_or_else(|| renderer.default_font()),
- limits.max(),
+ self.size,
+ self.font,
+ self.horizontal_alignment,
+ self.vertical_alignment,
self.shaping,
- );
-
- let size = limits.resolve(bounds);
-
- layout::Node::new(size)
+ )
}
fn draw(
&self,
- _state: &Tree,
+ tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@@ -163,22 +174,60 @@ where
_cursor_position: mouse::Cursor,
_viewport: &Rectangle,
) {
+ let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
+
draw(
renderer,
style,
layout,
- &self.content,
- self.size,
- self.line_height,
- self.font,
+ state,
theme.appearance(self.style.clone()),
- self.horizontal_alignment,
- self.vertical_alignment,
- self.shaping,
);
}
}
+/// Produces the [`layout::Node`] of a [`Text`] widget.
+pub fn layout<Renderer>(
+ state: &mut State<Renderer::Paragraph>,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ width: Length,
+ height: Length,
+ content: &str,
+ line_height: LineHeight,
+ size: Option<Pixels>,
+ font: Option<Renderer::Font>,
+ horizontal_alignment: alignment::Horizontal,
+ vertical_alignment: alignment::Vertical,
+ shaping: Shaping,
+) -> layout::Node
+where
+ Renderer: text::Renderer,
+{
+ let limits = limits.width(width).height(height);
+ let bounds = limits.max();
+
+ let size = size.unwrap_or_else(|| renderer.default_size());
+ let font = font.unwrap_or_else(|| renderer.default_font());
+
+ let State(ref mut paragraph) = state;
+
+ paragraph.update(text::Text {
+ content,
+ bounds,
+ size,
+ line_height,
+ font,
+ horizontal_alignment,
+ vertical_alignment,
+ shaping,
+ });
+
+ let size = limits.resolve(paragraph.min_bounds());
+
+ layout::Node::new(size)
+}
+
/// Draws text using the same logic as the [`Text`] widget.
///
/// Specifically:
@@ -193,44 +242,31 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
- content: &str,
- size: Option<f32>,
- line_height: LineHeight,
- font: Option<Renderer::Font>,
+ state: &State<Renderer::Paragraph>,
appearance: Appearance,
- horizontal_alignment: alignment::Horizontal,
- vertical_alignment: alignment::Vertical,
- shaping: Shaping,
) where
Renderer: text::Renderer,
{
+ let State(ref paragraph) = state;
let bounds = layout.bounds();
- let x = match horizontal_alignment {
+ let x = match paragraph.horizontal_alignment() {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.center_x(),
alignment::Horizontal::Right => bounds.x + bounds.width,
};
- let y = match vertical_alignment {
+ let y = match paragraph.vertical_alignment() {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.center_y(),
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
- let size = size.unwrap_or_else(|| renderer.default_size());
-
- renderer.fill_text(crate::Text {
- content,
- size,
- line_height,
- bounds: Rectangle { x, y, ..bounds },
- color: appearance.color.unwrap_or(style.text_color),
- font: font.unwrap_or_else(|| renderer.default_font()),
- horizontal_alignment,
- vertical_alignment,
- shaping,
- });
+ renderer.fill_paragraph(
+ paragraph,
+ Point::new(x, y),
+ appearance.color.unwrap_or(style.text_color),
+ );
}
impl<'a, Message, Renderer> From<Text<'a, Renderer>>
diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs
index da269632..ff52b1ce 100644
--- a/core/src/widget/tree.rs
+++ b/core/src/widget/tree.rs
@@ -61,7 +61,7 @@ impl Tree {
Renderer: crate::Renderer,
{
if self.tag == new.borrow().tag() {
- new.borrow().diff(self)
+ new.borrow().diff(self);
} else {
*self = Self::new(new);
}
@@ -78,7 +78,7 @@ impl Tree {
new_children,
|tree, widget| tree.diff(widget.borrow()),
|widget| Self::new(widget.borrow()),
- )
+ );
}
/// Reconciliates the children of the tree with the provided list of widgets using custom
@@ -107,6 +107,88 @@ impl Tree {
}
}
+/// Reconciliates the `current_children` with the provided list of widgets using
+/// custom logic both for diffing and creating new widget state.
+///
+/// The algorithm will try to minimize the impact of diffing by querying the
+/// `maybe_changed` closure.
+pub fn diff_children_custom_with_search<T>(
+ current_children: &mut Vec<Tree>,
+ new_children: &[T],
+ diff: impl Fn(&mut Tree, &T),
+ maybe_changed: impl Fn(usize) -> bool,
+ new_state: impl Fn(&T) -> Tree,
+) {
+ if new_children.is_empty() {
+ current_children.clear();
+ return;
+ }
+
+ if current_children.is_empty() {
+ current_children.extend(new_children.iter().map(new_state));
+ return;
+ }
+
+ let first_maybe_changed = maybe_changed(0);
+ let last_maybe_changed = maybe_changed(current_children.len() - 1);
+
+ if current_children.len() > new_children.len() {
+ if !first_maybe_changed && last_maybe_changed {
+ current_children.truncate(new_children.len());
+ } else {
+ let difference_index = if first_maybe_changed {
+ 0
+ } else {
+ (1..current_children.len())
+ .find(|&i| maybe_changed(i))
+ .unwrap_or(0)
+ };
+
+ let _ = current_children.splice(
+ difference_index
+ ..difference_index
+ + (current_children.len() - new_children.len()),
+ std::iter::empty(),
+ );
+ }
+ }
+
+ if current_children.len() < new_children.len() {
+ let first_maybe_changed = maybe_changed(0);
+ let last_maybe_changed = maybe_changed(current_children.len() - 1);
+
+ if !first_maybe_changed && last_maybe_changed {
+ current_children.extend(
+ new_children[current_children.len()..].iter().map(new_state),
+ );
+ } else {
+ let difference_index = if first_maybe_changed {
+ 0
+ } else {
+ (1..current_children.len())
+ .find(|&i| maybe_changed(i))
+ .unwrap_or(0)
+ };
+
+ let _ = current_children.splice(
+ difference_index..difference_index,
+ new_children[difference_index
+ ..difference_index
+ + (new_children.len() - current_children.len())]
+ .iter()
+ .map(new_state),
+ );
+ }
+ }
+
+ // TODO: Merge loop with extend logic (?)
+ for (child_state, new) in
+ current_children.iter_mut().zip(new_children.iter())
+ {
+ diff(child_state, new);
+ }
+}
+
/// The identifier of some widget state.
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Tag(any::TypeId);