Merge branch 'master' into feat/multi-window-support

This commit is contained in:
Héctor Ramón Jiménez 2023-11-29 22:28:31 +01:00
commit e09b4e24dd
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
331 changed files with 12085 additions and 3976 deletions

View file

@ -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) {

View file

@ -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> {

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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>>

View file

@ -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);