Implement explicit text caching in the widget state tree
This commit is contained in:
parent
c9bd48704d
commit
ed3454301e
79 changed files with 1910 additions and 1705 deletions
|
|
@ -7,17 +7,18 @@ use crate::core::layout;
|
|||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::text::{self, Text};
|
||||
use crate::core::text::{self, Paragraph as _, Text};
|
||||
use crate::core::touch;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell,
|
||||
Size, Widget,
|
||||
Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
|
||||
Shell, Size, Widget,
|
||||
};
|
||||
use crate::overlay::menu::{self, Menu};
|
||||
use crate::scrollable;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub use crate::style::pick_list::{Appearance, StyleSheet};
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ where
|
|||
selected: Option<T>,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Option<Renderer::Font>,
|
||||
|
|
@ -101,7 +102,7 @@ where
|
|||
|
||||
/// Sets the text size of the [`PickList`].
|
||||
pub fn text_size(mut self, size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(size.into().0);
|
||||
self.text_size = Some(size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -157,11 +158,11 @@ where
|
|||
From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
tree::Tag::of::<State<Renderer::Paragraph>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
tree::State::new(State::<Renderer::Paragraph>::new())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -174,10 +175,12 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
|
|
@ -210,7 +213,7 @@ where
|
|||
self.on_selected.as_ref(),
|
||||
self.selected.as_ref(),
|
||||
&self.options,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
|| tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +253,7 @@ where
|
|||
self.selected.as_ref(),
|
||||
&self.handle,
|
||||
&self.style,
|
||||
|| tree.state.downcast_ref::<State>(),
|
||||
|| tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +263,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
|
||||
overlay(
|
||||
layout,
|
||||
|
|
@ -295,28 +298,32 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`PickList`].
|
||||
/// The state of a [`PickList`].
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub struct State<P: text::Paragraph> {
|
||||
menu: menu::State,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<usize>,
|
||||
option_paragraphs: RefCell<Vec<P>>,
|
||||
placeholder_paragraph: RefCell<P>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl<P: text::Paragraph> State<P> {
|
||||
/// Creates a new [`State`] for a [`PickList`].
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
menu: menu::State::default(),
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
is_open: bool::default(),
|
||||
hovered_option: Option::default(),
|
||||
option_paragraphs: RefCell::new(Vec::new()),
|
||||
placeholder_paragraph: RefCell::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
impl<P: text::Paragraph> Default for State<P> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
|
@ -330,7 +337,7 @@ pub enum Handle<Font> {
|
|||
/// This is the default.
|
||||
Arrow {
|
||||
/// Font size of the content.
|
||||
size: Option<f32>,
|
||||
size: Option<Pixels>,
|
||||
},
|
||||
/// A custom static handle.
|
||||
Static(Icon<Font>),
|
||||
|
|
@ -359,7 +366,7 @@ pub struct Icon<Font> {
|
|||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// Font size of the content.
|
||||
pub size: Option<f32>,
|
||||
pub size: Option<Pixels>,
|
||||
/// Line height of the content.
|
||||
pub line_height: text::LineHeight,
|
||||
/// The shaping strategy of the icon.
|
||||
|
|
@ -368,11 +375,12 @@ pub struct Icon<Font> {
|
|||
|
||||
/// Computes the layout of a [`PickList`].
|
||||
pub fn layout<Renderer, T>(
|
||||
state: &State<Renderer::Paragraph>,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Option<Renderer::Font>,
|
||||
|
|
@ -386,38 +394,70 @@ where
|
|||
use std::f32;
|
||||
|
||||
let limits = limits.width(width).height(Length::Shrink).pad(padding);
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let mut paragraphs = state.option_paragraphs.borrow_mut();
|
||||
|
||||
paragraphs.resize_with(options.len(), Default::default);
|
||||
|
||||
let option_text = Text {
|
||||
content: "",
|
||||
bounds: Size::new(
|
||||
f32::INFINITY,
|
||||
text_line_height.to_absolute(text_size).into(),
|
||||
),
|
||||
size: text_size,
|
||||
line_height: text_line_height,
|
||||
font,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text_shaping,
|
||||
};
|
||||
|
||||
for (option, paragraph) in options.iter().zip(paragraphs.iter_mut()) {
|
||||
let label = option.to_string();
|
||||
|
||||
renderer.update_paragraph(
|
||||
paragraph,
|
||||
Text {
|
||||
content: &label,
|
||||
..option_text
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(placeholder) = placeholder {
|
||||
let mut paragraph = state.placeholder_paragraph.borrow_mut();
|
||||
renderer.update_paragraph(
|
||||
&mut paragraph,
|
||||
Text {
|
||||
content: placeholder,
|
||||
..option_text
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let max_width = match width {
|
||||
Length::Shrink => {
|
||||
let measure = |label: &str| -> f32 {
|
||||
let width = renderer.measure_width(
|
||||
label,
|
||||
text_size,
|
||||
font.unwrap_or_else(|| renderer.default_font()),
|
||||
text_shaping,
|
||||
);
|
||||
let labels_width =
|
||||
paragraphs.iter().fold(0.0, |width, paragraph| {
|
||||
f32::max(width, paragraph.min_width())
|
||||
});
|
||||
|
||||
width.round()
|
||||
};
|
||||
|
||||
let labels = options.iter().map(ToString::to_string);
|
||||
|
||||
let labels_width = labels
|
||||
.map(|label| measure(&label))
|
||||
.fold(100.0, |candidate, current| current.max(candidate));
|
||||
|
||||
let placeholder_width = placeholder.map(measure).unwrap_or(100.0);
|
||||
|
||||
labels_width.max(placeholder_width)
|
||||
labels_width.max(
|
||||
placeholder
|
||||
.map(|_| state.placeholder_paragraph.borrow().min_width())
|
||||
.unwrap_or(0.0),
|
||||
)
|
||||
}
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
let size = {
|
||||
let intrinsic = Size::new(
|
||||
max_width + text_size + padding.left,
|
||||
f32::from(text_line_height.to_absolute(Pixels(text_size))),
|
||||
max_width + text_size.0 + padding.left,
|
||||
f32::from(text_line_height.to_absolute(text_size)),
|
||||
);
|
||||
|
||||
limits.resolve(intrinsic).pad(padding)
|
||||
|
|
@ -428,7 +468,7 @@ where
|
|||
|
||||
/// Processes an [`Event`] and updates the [`State`] of a [`PickList`]
|
||||
/// accordingly.
|
||||
pub fn update<'a, T, Message>(
|
||||
pub fn update<'a, T, P, Message>(
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
|
|
@ -436,10 +476,11 @@ pub fn update<'a, T, Message>(
|
|||
on_selected: &dyn Fn(T) -> Message,
|
||||
selected: Option<&T>,
|
||||
options: &[T],
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
state: impl FnOnce() -> &'a mut State<P>,
|
||||
) -> event::Status
|
||||
where
|
||||
T: PartialEq + Clone + 'a,
|
||||
P: text::Paragraph + 'a,
|
||||
{
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
|
|
@ -534,9 +575,9 @@ pub fn mouse_interaction(
|
|||
/// Returns the current overlay of a [`PickList`].
|
||||
pub fn overlay<'a, T, Message, Renderer>(
|
||||
layout: Layout<'_>,
|
||||
state: &'a mut State,
|
||||
state: &'a mut State<Renderer::Paragraph>,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_shaping: text::Shaping,
|
||||
font: Renderer::Font,
|
||||
options: &'a [T],
|
||||
|
|
@ -591,7 +632,7 @@ pub fn draw<'a, T, Renderer>(
|
|||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Renderer::Font,
|
||||
|
|
@ -599,7 +640,7 @@ pub fn draw<'a, T, Renderer>(
|
|||
selected: Option<&T>,
|
||||
handle: &Handle<Renderer::Font>,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
state: impl FnOnce() -> &'a State,
|
||||
state: impl FnOnce() -> &'a State<Renderer::Paragraph>,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
|
|
@ -665,22 +706,26 @@ pub fn draw<'a, T, Renderer>(
|
|||
if let Some((font, code_point, size, line_height, shaping)) = handle {
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: &code_point.to_string(),
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
color: style.handle_color,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + bounds.width - padding.horizontal(),
|
||||
y: bounds.center_y(),
|
||||
height: f32::from(line_height.to_absolute(Pixels(size))),
|
||||
..bounds
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: &code_point.to_string(),
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds: Size::new(
|
||||
bounds.width,
|
||||
f32::from(line_height.to_absolute(size)),
|
||||
),
|
||||
horizontal_alignment: alignment::Horizontal::Right,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping,
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Right,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping,
|
||||
});
|
||||
Point::new(
|
||||
bounds.x + bounds.width - padding.horizontal(),
|
||||
bounds.center_y(),
|
||||
),
|
||||
style.handle_color,
|
||||
);
|
||||
}
|
||||
|
||||
let label = selected.map(ToString::to_string);
|
||||
|
|
@ -688,27 +733,26 @@ pub fn draw<'a, T, Renderer>(
|
|||
if let Some(label) = label.as_deref().or(placeholder) {
|
||||
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: label,
|
||||
size: text_size,
|
||||
line_height: text_line_height,
|
||||
font,
|
||||
color: if is_selected {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: label,
|
||||
size: text_size,
|
||||
line_height: text_line_height,
|
||||
font,
|
||||
bounds: Size::new(
|
||||
bounds.width - padding.horizontal(),
|
||||
f32::from(text_line_height.to_absolute(text_size)),
|
||||
),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text_shaping,
|
||||
},
|
||||
Point::new(bounds.x + padding.left, bounds.center_y()),
|
||||
if is_selected {
|
||||
style.text_color
|
||||
} else {
|
||||
style.placeholder_color
|
||||
},
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + padding.left,
|
||||
y: bounds.center_y(),
|
||||
width: bounds.width - padding.horizontal(),
|
||||
height: f32::from(
|
||||
text_line_height.to_absolute(Pixels(text_size)),
|
||||
),
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text_shaping,
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue