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
|
|
@ -306,10 +306,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.widget.layout(renderer, limits)
|
||||
self.widget.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -491,10 +492,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.element.widget.layout(renderer, limits)
|
||||
self.element.widget.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub mod flex;
|
|||
pub use limits::Limits;
|
||||
pub use node::Node;
|
||||
|
||||
use crate::{Point, Rectangle, Vector};
|
||||
use crate::{Point, Rectangle, Size, Vector};
|
||||
|
||||
/// The bounds of a [`Node`] and its children, using absolute coordinates.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -63,3 +63,29 @@ impl<'a> Layout<'a> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Node`] with two children nodes one right next to each other.
|
||||
pub fn next_to_each_other(
|
||||
limits: &Limits,
|
||||
spacing: f32,
|
||||
left: impl FnOnce(&Limits) -> Node,
|
||||
right: impl FnOnce(&Limits) -> Node,
|
||||
) -> Node {
|
||||
let left_node = left(limits);
|
||||
let left_size = left_node.size();
|
||||
|
||||
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
|
||||
|
||||
let mut right_node = right(&right_limits);
|
||||
let right_size = right_node.size();
|
||||
|
||||
right_node.move_to(Point::new(left_size.width + spacing, 0.0));
|
||||
|
||||
Node::with_children(
|
||||
Size::new(
|
||||
left_size.width + spacing + right_size.width,
|
||||
left_size.height.max(right_size.height),
|
||||
),
|
||||
vec![left_node, right_node],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
use crate::Element;
|
||||
|
||||
use crate::layout::{Limits, Node};
|
||||
use crate::widget;
|
||||
use crate::{Alignment, Padding, Point, Size};
|
||||
|
||||
/// The main axis of a flex layout.
|
||||
|
|
@ -66,6 +67,7 @@ pub fn resolve<Message, Renderer>(
|
|||
spacing: f32,
|
||||
align_items: Alignment,
|
||||
items: &[Element<'_, Message, Renderer>],
|
||||
trees: &[widget::Tree],
|
||||
) -> Node
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
@ -81,7 +83,7 @@ where
|
|||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
nodes.resize(items.len(), Node::default());
|
||||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
|
|
@ -94,7 +96,8 @@ where
|
|||
let child_limits =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let layout =
|
||||
child.as_widget().layout(tree, renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
|
|
@ -108,7 +111,7 @@ where
|
|||
|
||||
let remaining = available.max(0.0);
|
||||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
|
|
@ -133,7 +136,8 @@ where
|
|||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let layout =
|
||||
child.as_widget().layout(tree, renderer, &child_limits);
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
|
||||
nodes[i] = layout;
|
||||
|
|
|
|||
|
|
@ -5,26 +5,13 @@ mod null;
|
|||
#[cfg(debug_assertions)]
|
||||
pub use null::Null;
|
||||
|
||||
use crate::layout;
|
||||
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
|
||||
use crate::{Background, BorderRadius, Color, Rectangle, Vector};
|
||||
|
||||
/// A component that can be used by widgets to draw themselves on a screen.
|
||||
pub trait Renderer: Sized {
|
||||
/// The supported theme of the [`Renderer`].
|
||||
type Theme;
|
||||
|
||||
/// Lays out the elements of a user interface.
|
||||
///
|
||||
/// You should override this if you need to perform any operations before or
|
||||
/// after layouting. For instance, trimming the measurements cache.
|
||||
fn layout<Message>(
|
||||
&mut self,
|
||||
element: &Element<'_, Message, Self>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
element.as_widget().layout(self, limits)
|
||||
}
|
||||
|
||||
/// Draws the primitives recorded in the given closure in a new layer.
|
||||
///
|
||||
/// The layer will clip its contents to the provided `bounds`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::alignment;
|
||||
use crate::renderer::{self, Renderer};
|
||||
use crate::text::{self, Text};
|
||||
use crate::{Background, Font, Point, Rectangle, Size, Vector};
|
||||
use crate::{Background, Color, Font, Pixels, Point, Rectangle, Size, Vector};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ impl Renderer for Null {
|
|||
|
||||
impl text::Renderer for Null {
|
||||
type Font = Font;
|
||||
type Paragraph = ();
|
||||
|
||||
const ICON_FONT: Font = Font::DEFAULT;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
|
|
@ -50,37 +52,83 @@ impl text::Renderer for Null {
|
|||
Font::default()
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
16.0
|
||||
fn default_size(&self) -> Pixels {
|
||||
Pixels(16.0)
|
||||
}
|
||||
|
||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
_content: &str,
|
||||
_size: f32,
|
||||
_line_height: text::LineHeight,
|
||||
_font: Font,
|
||||
_bounds: Size,
|
||||
_shaping: text::Shaping,
|
||||
) -> Size {
|
||||
Size::new(0.0, 20.0)
|
||||
fn create_paragraph(&self, _text: Text<'_, Self::Font>) -> Self::Paragraph {
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
fn resize_paragraph(
|
||||
&self,
|
||||
_contents: &str,
|
||||
_size: f32,
|
||||
_line_height: text::LineHeight,
|
||||
_font: Self::Font,
|
||||
_bounds: Size,
|
||||
_shaping: text::Shaping,
|
||||
_point: Point,
|
||||
_nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
_paragraph: &mut Self::Paragraph,
|
||||
_new_bounds: Size,
|
||||
) {
|
||||
}
|
||||
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
_paragraph: &Self::Paragraph,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
) {
|
||||
}
|
||||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
_paragraph: Text<'_, Self::Font>,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl text::Paragraph for () {
|
||||
type Font = Font;
|
||||
|
||||
fn content(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
fn text_size(&self) -> Pixels {
|
||||
Pixels(16.0)
|
||||
}
|
||||
|
||||
fn font(&self) -> Self::Font {
|
||||
Font::default()
|
||||
}
|
||||
|
||||
fn line_height(&self) -> text::LineHeight {
|
||||
text::LineHeight::default()
|
||||
}
|
||||
|
||||
fn shaping(&self) -> text::Shaping {
|
||||
text::Shaping::default()
|
||||
}
|
||||
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal {
|
||||
alignment::Horizontal::Left
|
||||
}
|
||||
|
||||
fn vertical_alignment(&self) -> alignment::Vertical {
|
||||
alignment::Vertical::Top
|
||||
}
|
||||
|
||||
fn grapheme_position(&self, _line: usize, _index: usize) -> Option<Point> {
|
||||
None
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
|
||||
fn bounds(&self) -> Size {
|
||||
Size::ZERO
|
||||
}
|
||||
|
||||
fn min_bounds(&self) -> Size {
|
||||
Size::ZERO
|
||||
}
|
||||
|
||||
fn hit_test(&self, _point: Point) -> Option<text::Hit> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
167
core/src/text.rs
167
core/src/text.rs
|
|
@ -1,6 +1,6 @@
|
|||
//! Draw and interact with text.
|
||||
use crate::alignment;
|
||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
||||
use crate::{Color, Pixels, Point, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
|
@ -12,17 +12,14 @@ pub struct Text<'a, Font> {
|
|||
pub content: &'a str,
|
||||
|
||||
/// The bounds of the paragraph.
|
||||
pub bounds: Rectangle,
|
||||
pub bounds: Size,
|
||||
|
||||
/// The size of the [`Text`] in logical pixels.
|
||||
pub size: f32,
|
||||
pub size: Pixels,
|
||||
|
||||
/// The line height of the [`Text`].
|
||||
pub line_height: LineHeight,
|
||||
|
||||
/// The color of the [`Text`].
|
||||
pub color: Color,
|
||||
|
||||
/// The font of the [`Text`].
|
||||
pub font: Font,
|
||||
|
||||
|
|
@ -132,7 +129,10 @@ impl Hit {
|
|||
/// A renderer capable of measuring and drawing [`Text`].
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The font type used.
|
||||
type Font: Copy;
|
||||
type Font: Copy + PartialEq;
|
||||
|
||||
/// The [`Paragraph`] of this [`Renderer`].
|
||||
type Paragraph: Paragraph<Font = Self::Font> + 'static;
|
||||
|
||||
/// The icon font of the backend.
|
||||
const ICON_FONT: Self::Font;
|
||||
|
|
@ -151,62 +151,107 @@ pub trait Renderer: crate::Renderer {
|
|||
fn default_font(&self) -> Self::Font;
|
||||
|
||||
/// Returns the default size of [`Text`].
|
||||
fn default_size(&self) -> f32;
|
||||
|
||||
/// Measures the text in the given bounds and returns the minimum boundaries
|
||||
/// that can fit the contents.
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Self::Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
) -> Size;
|
||||
|
||||
/// Measures the width of the text as if it were laid out in a single line.
|
||||
fn measure_width(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
font: Self::Font,
|
||||
shaping: Shaping,
|
||||
) -> f32 {
|
||||
let bounds = self.measure(
|
||||
content,
|
||||
size,
|
||||
LineHeight::Absolute(Pixels(size)),
|
||||
font,
|
||||
Size::INFINITY,
|
||||
shaping,
|
||||
);
|
||||
|
||||
bounds.width
|
||||
}
|
||||
|
||||
/// Tests whether the provided point is within the boundaries of text
|
||||
/// laid out with the given parameters, returning information about
|
||||
/// the nearest character.
|
||||
///
|
||||
/// If `nearest_only` is true, the hit test does not consider whether the
|
||||
/// the point is interior to any glyph bounds, returning only the character
|
||||
/// with the nearest centeroid.
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Self::Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<Hit>;
|
||||
fn default_size(&self) -> Pixels;
|
||||
|
||||
/// Loads a [`Self::Font`] from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||
|
||||
/// Draws the given [`Text`].
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>);
|
||||
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
|
||||
fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph;
|
||||
|
||||
/// Lays out the given [`Paragraph`] with some new boundaries.
|
||||
fn resize_paragraph(
|
||||
&self,
|
||||
paragraph: &mut Self::Paragraph,
|
||||
new_bounds: Size,
|
||||
);
|
||||
|
||||
/// Updates a [`Paragraph`] to match the given [`Text`], if needed.
|
||||
fn update_paragraph(
|
||||
&self,
|
||||
paragraph: &mut Self::Paragraph,
|
||||
text: Text<'_, Self::Font>,
|
||||
) {
|
||||
if paragraph.content() != text.content
|
||||
|| paragraph.text_size() != text.size
|
||||
|| paragraph.line_height().to_absolute(text.size)
|
||||
!= text.line_height.to_absolute(text.size)
|
||||
|| paragraph.font() != text.font
|
||||
|| paragraph.shaping() != text.shaping
|
||||
|| paragraph.horizontal_alignment() != text.horizontal_alignment
|
||||
|| paragraph.vertical_alignment() != text.vertical_alignment
|
||||
{
|
||||
*paragraph = self.create_paragraph(text);
|
||||
} else if paragraph.bounds() != text.bounds {
|
||||
self.resize_paragraph(paragraph, text.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the given [`Paragraph`] at the given position and with the given
|
||||
/// [`Color`].
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
text: &Self::Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
);
|
||||
|
||||
/// Draws the given [`Text`] at the given position and with the given
|
||||
/// [`Color`].
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: Text<'_, Self::Font>,
|
||||
position: Point,
|
||||
color: Color,
|
||||
);
|
||||
}
|
||||
/// A text paragraph.
|
||||
pub trait Paragraph: Default {
|
||||
/// The font of this [`Paragraph`].
|
||||
type Font;
|
||||
|
||||
/// Returns the content of the [`Paragraph`].
|
||||
fn content(&self) -> &str;
|
||||
|
||||
/// Returns the text size of the [`Paragraph`].
|
||||
fn text_size(&self) -> Pixels;
|
||||
|
||||
/// Returns the [`LineHeight`] of the [`Paragraph`].
|
||||
fn line_height(&self) -> LineHeight;
|
||||
|
||||
/// Returns the [`Font`] of the [`Paragraph`].
|
||||
fn font(&self) -> Self::Font;
|
||||
|
||||
/// Returns the [`Shaping`] strategy of the [`Paragraph`].
|
||||
fn shaping(&self) -> Shaping;
|
||||
|
||||
/// Returns the horizontal alignment of the [`Paragraph`].
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal;
|
||||
|
||||
/// Returns the vertical alignment of the [`Paragraph`].
|
||||
fn vertical_alignment(&self) -> alignment::Vertical;
|
||||
|
||||
/// Returns the boundaries of the [`Paragraph`].
|
||||
fn bounds(&self) -> Size;
|
||||
|
||||
/// Returns the minimum boundaries that can fit the contents of the
|
||||
/// [`Paragraph`].
|
||||
fn min_bounds(&self) -> Size;
|
||||
|
||||
/// Tests whether the provided point is within the boundaries of the
|
||||
/// [`Paragraph`], returning information about the nearest character.
|
||||
fn hit_test(&self, point: Point) -> Option<Hit>;
|
||||
|
||||
/// Returns the distance to the given grapheme index in the [`Paragraph`].
|
||||
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
|
||||
|
||||
/// Returns the minimum width that can fit the contents of the [`Paragraph`].
|
||||
fn min_width(&self) -> f32 {
|
||||
self.min_bounds().width
|
||||
}
|
||||
|
||||
/// Returns the minimum height that can fit the contents of the [`Paragraph`].
|
||||
fn min_height(&self) -> f32 {
|
||||
self.min_bounds().height
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ where
|
|||
/// user interface.
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node;
|
||||
|
|
@ -62,7 +63,7 @@ where
|
|||
/// Draws the [`Widget`] using the associated `Renderer`.
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Tree,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ 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 as _};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub use text::{LineHeight, Shaping};
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
content: Cow<'a, str>,
|
||||
size: Option<f32>,
|
||||
size: Option<Pixels>,
|
||||
line_height: LineHeight,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -53,7 +54,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 +118,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The internal state of a [`Text`] widget.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State<T>(RefCell<T>);
|
||||
|
||||
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(RefCell::new(Renderer::Paragraph::default())))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -132,30 +145,29 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &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_ref::<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 +175,63 @@ 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: &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 mut paragraph = state.0.borrow_mut();
|
||||
|
||||
renderer.update_paragraph(
|
||||
&mut paragraph,
|
||||
text::Text {
|
||||
content,
|
||||
bounds,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
shaping,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
},
|
||||
);
|
||||
|
||||
let size = limits.resolve(paragraph.min_bounds());
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
/// Draws text using the same logic as the [`Text`] widget.
|
||||
///
|
||||
/// Specifically:
|
||||
|
|
@ -193,44 +246,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 paragraph = state.0.borrow();
|
||||
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(
|
||||
¶graph,
|
||||
Point::new(x, y),
|
||||
appearance.color.unwrap_or(style.text_color),
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Text<'a, Renderer>>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use iced::mouse;
|
|||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
|
||||
use iced::widget::{column, row, text, Slider};
|
||||
use iced::{
|
||||
Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
|
||||
Size, Vector,
|
||||
Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
|
||||
Settings, Size, Vector,
|
||||
};
|
||||
use palette::{
|
||||
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
|
||||
|
|
@ -168,7 +168,7 @@ impl Theme {
|
|||
let mut text = canvas::Text {
|
||||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
size: 15.0,
|
||||
size: Pixels(15.0),
|
||||
..canvas::Text::default()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ impl Sandbox for Example {
|
|||
Message::Selected(language) => {
|
||||
self.selected_language = Some(language);
|
||||
self.text = language.hello().to_string();
|
||||
self.languages.unfocus();
|
||||
}
|
||||
Message::OptionHovered(language) => {
|
||||
self.text = language.hello().to_string();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ mod quad {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ mod circle {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -591,7 +591,7 @@ mod grid {
|
|||
|
||||
let text = Text {
|
||||
color: Color::WHITE,
|
||||
size: 14.0,
|
||||
size: 14.0.into(),
|
||||
position: Point::new(frame.width(), frame.height()),
|
||||
horizontal_alignment: alignment::Horizontal::Right,
|
||||
vertical_alignment: alignment::Vertical::Bottom,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ mod rainbow {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use iced_wgpu::graphics::Viewport;
|
|||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||
use iced_winit::core::mouse;
|
||||
use iced_winit::core::renderer;
|
||||
use iced_winit::core::{Color, Size};
|
||||
use iced_winit::core::{Color, Font, Pixels, Size};
|
||||
use iced_winit::runtime::program;
|
||||
use iced_winit::runtime::Debug;
|
||||
use iced_winit::style::Theme;
|
||||
|
|
@ -143,12 +143,11 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// Initialize iced
|
||||
let mut debug = Debug::new();
|
||||
let mut renderer = Renderer::new(Backend::new(
|
||||
&device,
|
||||
&queue,
|
||||
Settings::default(),
|
||||
format,
|
||||
));
|
||||
let mut renderer = Renderer::new(
|
||||
Backend::new(&device, &queue, Settings::default(), format),
|
||||
Font::default(),
|
||||
Pixels(16.0),
|
||||
);
|
||||
|
||||
let mut state = program::State::new(
|
||||
controls,
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &iced::Renderer<Theme>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -285,10 +285,13 @@ mod modal {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.base.as_widget().layout(renderer, limits)
|
||||
self.base
|
||||
.as_widget()
|
||||
.layout(&tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -408,7 +411,11 @@ mod modal {
|
|||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let mut child = self.content.as_widget().layout(renderer, &limits);
|
||||
let mut child = self
|
||||
.content
|
||||
.as_widget()
|
||||
.layout(self.tree, renderer, &limits);
|
||||
|
||||
child.align(Alignment::Center, Alignment::Center, limits.max());
|
||||
|
||||
let mut node = layout::Node::with_children(self.size, vec![child]);
|
||||
|
|
|
|||
|
|
@ -326,10 +326,13 @@ mod toast {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
self.content
|
||||
.as_widget()
|
||||
.layout(&tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
fn tag(&self) -> widget::tree::Tag {
|
||||
|
|
@ -517,6 +520,7 @@ mod toast {
|
|||
10.0,
|
||||
Alignment::End,
|
||||
self.toasts,
|
||||
self.state,
|
||||
)
|
||||
.translate(Vector::new(position.x, position.y))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use iced::widget::{
|
|||
scrollable, slider, text, text_input, toggler, vertical_space,
|
||||
};
|
||||
use iced::widget::{Button, Column, Container, Slider};
|
||||
use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
|
||||
use iced::{Color, Element, Font, Length, Pixels, Renderer, Sandbox, Settings};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
env_logger::init();
|
||||
|
|
@ -571,7 +571,7 @@ impl<'a> Step {
|
|||
text_input = text_input.icon(text_input::Icon {
|
||||
font: Font::default(),
|
||||
code_point: '🚀',
|
||||
size: Some(28.0),
|
||||
size: Some(Pixels(28.0)),
|
||||
spacing: 10.0,
|
||||
side: text_input::Side::Right,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ log = "0.4"
|
|||
raw-window-handle = "0.5"
|
||||
thiserror = "1.0"
|
||||
bitflags = "1.2"
|
||||
cosmic-text = "0.9"
|
||||
rustc-hash = "1.1"
|
||||
unicode-segmentation = "1.6"
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.4"
|
||||
|
|
@ -32,6 +35,14 @@ features = ["derive"]
|
|||
version = "0.10"
|
||||
path = "../core"
|
||||
|
||||
[dependencies.twox-hash]
|
||||
version = "1.6"
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
|
||||
version = "1.6.1"
|
||||
features = ["std"]
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.24"
|
||||
optional = true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! Write a graphics backend.
|
||||
use iced_core::image;
|
||||
use iced_core::svg;
|
||||
use iced_core::text;
|
||||
use iced_core::{Font, Point, Size};
|
||||
use crate::core::image;
|
||||
use crate::core::svg;
|
||||
use crate::core::Size;
|
||||
use crate::text;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
|
@ -12,70 +12,15 @@ use std::borrow::Cow;
|
|||
pub trait Backend {
|
||||
/// The custom kind of primitives this [`Backend`] supports.
|
||||
type Primitive;
|
||||
|
||||
/// Trims the measurements cache.
|
||||
///
|
||||
/// This method is currently necessary to properly trim the text cache in
|
||||
/// `iced_wgpu` and `iced_glow` because of limitations in the text rendering
|
||||
/// pipeline. It will be removed in the future.
|
||||
fn trim_measurements(&mut self) {}
|
||||
}
|
||||
|
||||
/// A graphics backend that supports text rendering.
|
||||
pub trait Text {
|
||||
/// The icon font of the backend.
|
||||
const ICON_FONT: Font;
|
||||
|
||||
/// The `char` representing a ✔ icon in the [`ICON_FONT`].
|
||||
///
|
||||
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||
const CHECKMARK_ICON: char;
|
||||
|
||||
/// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
|
||||
///
|
||||
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||
const ARROW_DOWN_ICON: char;
|
||||
|
||||
/// Returns the default [`Font`].
|
||||
fn default_font(&self) -> Font;
|
||||
|
||||
/// Returns the default size of text.
|
||||
fn default_size(&self) -> f32;
|
||||
|
||||
/// Measures the text contents with the given size and font,
|
||||
/// returning the size of a laid out paragraph that fits in the provided
|
||||
/// bounds.
|
||||
fn measure(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
) -> Size;
|
||||
|
||||
/// Tests whether the provided point is within the boundaries of [`Text`]
|
||||
/// laid out with the given parameters, returning information about
|
||||
/// the nearest character.
|
||||
///
|
||||
/// If nearest_only is true, the hit test does not consider whether the
|
||||
/// the point is interior to any glyph bounds, returning only the character
|
||||
/// with the nearest centeroid.
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit>;
|
||||
|
||||
/// Loads a [`Font`] from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||
|
||||
/// Returns the [`cosmic_text::FontSystem`] of the [`Backend`].
|
||||
fn font_system(&self) -> &text::FontSystem;
|
||||
}
|
||||
|
||||
/// A graphics backend that supports image rendering.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,32 @@ impl<T: Damage> Damage for Primitive<T> {
|
|||
|
||||
bounds.expand(1.5)
|
||||
}
|
||||
Self::Paragraph {
|
||||
paragraph,
|
||||
position,
|
||||
..
|
||||
} => {
|
||||
let mut bounds =
|
||||
Rectangle::new(*position, paragraph.min_bounds);
|
||||
|
||||
bounds.x = match paragraph.horizontal_alignment {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => {
|
||||
bounds.x - bounds.width / 2.0
|
||||
}
|
||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
||||
};
|
||||
|
||||
bounds.y = match paragraph.vertical_alignment {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => {
|
||||
bounds.y - bounds.height / 2.0
|
||||
}
|
||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
||||
};
|
||||
|
||||
bounds.expand(1.5)
|
||||
}
|
||||
Self::Quad { bounds, .. }
|
||||
| Self::Image { bounds, .. }
|
||||
| Self::Svg { bounds, .. } => bounds.expand(1.0),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::text::{LineHeight, Shaping};
|
||||
use crate::core::{Color, Font, Point};
|
||||
use crate::core::{Color, Font, Pixels, Point};
|
||||
|
||||
/// A bunch of text that can be drawn to a canvas
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -19,7 +19,7 @@ pub struct Text {
|
|||
/// The color of the text
|
||||
pub color: Color,
|
||||
/// The size of the text
|
||||
pub size: f32,
|
||||
pub size: Pixels,
|
||||
/// The line height of the text.
|
||||
pub line_height: LineHeight,
|
||||
/// The font of the text
|
||||
|
|
@ -38,7 +38,7 @@ impl Default for Text {
|
|||
content: String::new(),
|
||||
position: Point::ORIGIN,
|
||||
color: Color::BLACK,
|
||||
size: 16.0,
|
||||
size: Pixels(16.0),
|
||||
line_height: LineHeight::Relative(1.2),
|
||||
font: Font::default(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
//missing_docs,
|
||||
unsafe_code,
|
||||
unused_results,
|
||||
clippy::extra_unused_lifetimes,
|
||||
|
|
@ -34,6 +34,7 @@ pub mod damage;
|
|||
pub mod gradient;
|
||||
pub mod mesh;
|
||||
pub mod renderer;
|
||||
pub mod text;
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
pub mod geometry;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ use crate::core::alignment;
|
|||
use crate::core::image;
|
||||
use crate::core::svg;
|
||||
use crate::core::text;
|
||||
use crate::core::{Background, Color, Font, Rectangle, Vector};
|
||||
use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Vector};
|
||||
use crate::text::paragraph;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ pub enum Primitive<T> {
|
|||
/// The color of the text
|
||||
color: Color,
|
||||
/// The size of the text in logical pixels
|
||||
size: f32,
|
||||
size: Pixels,
|
||||
/// The line height of the text
|
||||
line_height: text::LineHeight,
|
||||
/// The font of the text
|
||||
|
|
@ -31,6 +32,15 @@ pub enum Primitive<T> {
|
|||
/// The shaping strategy of the text.
|
||||
shaping: text::Shaping,
|
||||
},
|
||||
/// A paragraph primitive
|
||||
Paragraph {
|
||||
/// The [`Paragraph`].
|
||||
paragraph: paragraph::Weak,
|
||||
/// The position of the [`Paragraph`].
|
||||
position: Point,
|
||||
/// The color of the [`Paragraph`].
|
||||
color: Color,
|
||||
},
|
||||
/// A quad primitive
|
||||
Quad {
|
||||
/// The bounds of the quad
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
//! Create a renderer from a [`Backend`].
|
||||
use crate::backend::{self, Backend};
|
||||
use crate::Primitive;
|
||||
|
||||
use iced_core::image;
|
||||
use iced_core::layout;
|
||||
use iced_core::renderer;
|
||||
use iced_core::svg;
|
||||
use iced_core::text::{self, Text};
|
||||
use iced_core::{
|
||||
Background, Color, Element, Font, Point, Rectangle, Size, Vector,
|
||||
use crate::core;
|
||||
use crate::core::image;
|
||||
use crate::core::renderer;
|
||||
use crate::core::svg;
|
||||
use crate::core::text::Text;
|
||||
use crate::core::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Vector,
|
||||
};
|
||||
use crate::text;
|
||||
use crate::Primitive;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
|
|
@ -18,15 +18,23 @@ use std::marker::PhantomData;
|
|||
#[derive(Debug)]
|
||||
pub struct Renderer<B: Backend, Theme> {
|
||||
backend: B,
|
||||
default_font: Font,
|
||||
default_text_size: Pixels,
|
||||
primitives: Vec<Primitive<B::Primitive>>,
|
||||
theme: PhantomData<Theme>,
|
||||
}
|
||||
|
||||
impl<B: Backend, T> Renderer<B, T> {
|
||||
/// Creates a new [`Renderer`] from the given [`Backend`].
|
||||
pub fn new(backend: B) -> Self {
|
||||
pub fn new(
|
||||
backend: B,
|
||||
default_font: Font,
|
||||
default_text_size: Pixels,
|
||||
) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
default_font,
|
||||
default_text_size,
|
||||
primitives: Vec::new(),
|
||||
theme: PhantomData,
|
||||
}
|
||||
|
|
@ -88,16 +96,6 @@ impl<B: Backend, T> Renderer<B, T> {
|
|||
impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {
|
||||
type Theme = T;
|
||||
|
||||
fn layout<Message>(
|
||||
&mut self,
|
||||
element: &Element<'_, Message, Self>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.backend.trim_measurements();
|
||||
|
||||
element.as_widget().layout(self, limits)
|
||||
}
|
||||
|
||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||
let current = self.start_layer();
|
||||
|
||||
|
|
@ -137,77 +135,66 @@ impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<B, T> text::Renderer for Renderer<B, T>
|
||||
impl<B, T> core::text::Renderer for Renderer<B, T>
|
||||
where
|
||||
B: Backend + backend::Text,
|
||||
{
|
||||
type Font = Font;
|
||||
type Paragraph = text::Paragraph;
|
||||
|
||||
const ICON_FONT: Font = B::ICON_FONT;
|
||||
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
|
||||
const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON;
|
||||
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||
|
||||
fn default_font(&self) -> Self::Font {
|
||||
self.backend().default_font()
|
||||
self.default_font
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
self.backend().default_size()
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
) -> Size {
|
||||
self.backend().measure(
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
)
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
self.backend().hit_test(
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
point,
|
||||
nearest_only,
|
||||
)
|
||||
fn default_size(&self) -> Pixels {
|
||||
self.default_text_size
|
||||
}
|
||||
|
||||
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
self.backend.load_font(bytes);
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
|
||||
fn create_paragraph(&self, text: Text<'_, Self::Font>) -> text::Paragraph {
|
||||
text::Paragraph::with_text(text, self.backend.font_system())
|
||||
}
|
||||
|
||||
fn resize_paragraph(
|
||||
&self,
|
||||
paragraph: &mut Self::Paragraph,
|
||||
new_bounds: Size,
|
||||
) {
|
||||
paragraph.resize(new_bounds, self.backend.font_system());
|
||||
}
|
||||
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
paragraph: &Self::Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
) {
|
||||
self.primitives.push(Primitive::Paragraph {
|
||||
paragraph: paragraph.downgrade(),
|
||||
position,
|
||||
color,
|
||||
});
|
||||
}
|
||||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: Text<'_, Self::Font>,
|
||||
position: Point,
|
||||
color: Color,
|
||||
) {
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content.to_string(),
|
||||
bounds: text.bounds,
|
||||
bounds: Rectangle::new(position, text.bounds),
|
||||
size: text.size,
|
||||
line_height: text.line_height,
|
||||
color: text.color,
|
||||
color,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
|
|
|
|||
113
graphics/src/text.rs
Normal file
113
graphics/src/text.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
pub mod cache;
|
||||
pub mod paragraph;
|
||||
|
||||
pub use cache::Cache;
|
||||
pub use paragraph::Paragraph;
|
||||
|
||||
pub use cosmic_text;
|
||||
|
||||
use crate::core::font::{self, Font};
|
||||
use crate::core::text::Shaping;
|
||||
use crate::core::Size;
|
||||
|
||||
use std::sync::{self, Arc, RwLock};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct FontSystem(RwLock<cosmic_text::FontSystem>);
|
||||
|
||||
impl FontSystem {
|
||||
pub fn new() -> Self {
|
||||
FontSystem(RwLock::new(cosmic_text::FontSystem::new_with_fonts(
|
||||
[cosmic_text::fontdb::Source::Binary(Arc::new(
|
||||
include_bytes!("../fonts/Iced-Icons.ttf").as_slice(),
|
||||
))]
|
||||
.into_iter(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut cosmic_text::FontSystem {
|
||||
self.0.get_mut().expect("Lock font system")
|
||||
}
|
||||
|
||||
pub fn write(&self) -> sync::RwLockWriteGuard<'_, cosmic_text::FontSystem> {
|
||||
self.0.write().expect("Write font system")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FontSystem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn measure(buffer: &cosmic_text::Buffer) -> Size {
|
||||
let (width, total_lines) = buffer
|
||||
.layout_runs()
|
||||
.fold((0.0, 0usize), |(width, total_lines), run| {
|
||||
(run.line_w.max(width), total_lines + 1)
|
||||
});
|
||||
|
||||
Size::new(width, total_lines as f32 * buffer.metrics().line_height)
|
||||
}
|
||||
|
||||
pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> {
|
||||
cosmic_text::Attrs::new()
|
||||
.family(to_family(font.family))
|
||||
.weight(to_weight(font.weight))
|
||||
.stretch(to_stretch(font.stretch))
|
||||
.style(to_style(font.style))
|
||||
}
|
||||
|
||||
fn to_family(family: font::Family) -> cosmic_text::Family<'static> {
|
||||
match family {
|
||||
font::Family::Name(name) => cosmic_text::Family::Name(name),
|
||||
font::Family::SansSerif => cosmic_text::Family::SansSerif,
|
||||
font::Family::Serif => cosmic_text::Family::Serif,
|
||||
font::Family::Cursive => cosmic_text::Family::Cursive,
|
||||
font::Family::Fantasy => cosmic_text::Family::Fantasy,
|
||||
font::Family::Monospace => cosmic_text::Family::Monospace,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_weight(weight: font::Weight) -> cosmic_text::Weight {
|
||||
match weight {
|
||||
font::Weight::Thin => cosmic_text::Weight::THIN,
|
||||
font::Weight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT,
|
||||
font::Weight::Light => cosmic_text::Weight::LIGHT,
|
||||
font::Weight::Normal => cosmic_text::Weight::NORMAL,
|
||||
font::Weight::Medium => cosmic_text::Weight::MEDIUM,
|
||||
font::Weight::Semibold => cosmic_text::Weight::SEMIBOLD,
|
||||
font::Weight::Bold => cosmic_text::Weight::BOLD,
|
||||
font::Weight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD,
|
||||
font::Weight::Black => cosmic_text::Weight::BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch {
|
||||
match stretch {
|
||||
font::Stretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed,
|
||||
font::Stretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed,
|
||||
font::Stretch::Condensed => cosmic_text::Stretch::Condensed,
|
||||
font::Stretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed,
|
||||
font::Stretch::Normal => cosmic_text::Stretch::Normal,
|
||||
font::Stretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded,
|
||||
font::Stretch::Expanded => cosmic_text::Stretch::Expanded,
|
||||
font::Stretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded,
|
||||
font::Stretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_style(style: font::Style) -> cosmic_text::Style {
|
||||
match style {
|
||||
font::Style::Normal => cosmic_text::Style::Normal,
|
||||
font::Style::Italic => cosmic_text::Style::Italic,
|
||||
font::Style::Oblique => cosmic_text::Style::Oblique,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
|
||||
match shaping {
|
||||
Shaping::Basic => cosmic_text::Shaping::Basic,
|
||||
Shaping::Advanced => cosmic_text::Shaping::Advanced,
|
||||
}
|
||||
}
|
||||
120
graphics/src/text/cache.rs
Normal file
120
graphics/src/text/cache.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use crate::core::{Font, Size};
|
||||
use crate::text;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::collections::hash_map;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Default)]
|
||||
pub struct Cache {
|
||||
entries: FxHashMap<KeyHash, cosmic_text::Buffer>,
|
||||
aliases: FxHashMap<KeyHash, KeyHash>,
|
||||
recently_used: FxHashSet<KeyHash>,
|
||||
hasher: HashBuilder,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||
|
||||
impl Cache {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &KeyHash) -> Option<&cosmic_text::Buffer> {
|
||||
self.entries.get(key)
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
&mut self,
|
||||
font_system: &mut cosmic_text::FontSystem,
|
||||
key: Key<'_>,
|
||||
) -> (KeyHash, &mut cosmic_text::Buffer) {
|
||||
let hash = key.hash(self.hasher.build_hasher());
|
||||
|
||||
if let Some(hash) = self.aliases.get(&hash) {
|
||||
let _ = self.recently_used.insert(*hash);
|
||||
|
||||
return (*hash, self.entries.get_mut(hash).unwrap());
|
||||
}
|
||||
|
||||
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
|
||||
let metrics = cosmic_text::Metrics::new(key.size, key.line_height);
|
||||
let mut buffer = cosmic_text::Buffer::new(font_system, metrics);
|
||||
|
||||
buffer.set_size(
|
||||
font_system,
|
||||
key.bounds.width,
|
||||
key.bounds.height.max(key.line_height),
|
||||
);
|
||||
buffer.set_text(
|
||||
font_system,
|
||||
key.content,
|
||||
text::to_attributes(key.font),
|
||||
text::to_shaping(key.shaping),
|
||||
);
|
||||
|
||||
let bounds = text::measure(&buffer);
|
||||
let _ = entry.insert(buffer);
|
||||
|
||||
for bounds in [
|
||||
bounds,
|
||||
Size {
|
||||
width: key.bounds.width,
|
||||
..bounds
|
||||
},
|
||||
] {
|
||||
if key.bounds != bounds {
|
||||
let _ = self.aliases.insert(
|
||||
Key { bounds, ..key }.hash(self.hasher.build_hasher()),
|
||||
hash,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.recently_used.insert(hash);
|
||||
|
||||
(hash, self.entries.get_mut(&hash).unwrap())
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.entries
|
||||
.retain(|key, _| self.recently_used.contains(key));
|
||||
|
||||
self.aliases
|
||||
.retain(|_, value| self.recently_used.contains(value));
|
||||
|
||||
self.recently_used.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Key<'a> {
|
||||
pub content: &'a str,
|
||||
pub size: f32,
|
||||
pub line_height: f32,
|
||||
pub font: Font,
|
||||
pub bounds: Size,
|
||||
pub shaping: text::Shaping,
|
||||
}
|
||||
|
||||
impl Key<'_> {
|
||||
fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash {
|
||||
self.content.hash(&mut hasher);
|
||||
self.size.to_bits().hash(&mut hasher);
|
||||
self.line_height.to_bits().hash(&mut hasher);
|
||||
self.font.hash(&mut hasher);
|
||||
self.bounds.width.to_bits().hash(&mut hasher);
|
||||
self.bounds.height.to_bits().hash(&mut hasher);
|
||||
self.shaping.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub type KeyHash = u64;
|
||||
246
graphics/src/text/paragraph.rs
Normal file
246
graphics/src/text/paragraph.rs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
use crate::core;
|
||||
use crate::core::alignment;
|
||||
use crate::core::text::{Hit, LineHeight, Shaping, Text};
|
||||
use crate::core::{Font, Pixels, Point, Size};
|
||||
use crate::text::{self, FontSystem};
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::{self, Arc};
|
||||
|
||||
#[derive(Clone, PartialEq, Default)]
|
||||
pub struct Paragraph(Arc<Internal>);
|
||||
|
||||
struct Internal {
|
||||
buffer: cosmic_text::Buffer,
|
||||
content: String, // TODO: Reuse from `buffer` (?)
|
||||
font: Font,
|
||||
shaping: Shaping,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
bounds: Size,
|
||||
min_bounds: Size,
|
||||
}
|
||||
|
||||
impl Paragraph {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_text(text: Text<'_, Font>, font_system: &FontSystem) -> Self {
|
||||
let mut font_system = font_system.write();
|
||||
|
||||
let mut buffer = cosmic_text::Buffer::new(
|
||||
&mut font_system,
|
||||
cosmic_text::Metrics::new(
|
||||
text.size.into(),
|
||||
text.line_height.to_absolute(text.size).into(),
|
||||
),
|
||||
);
|
||||
|
||||
buffer.set_size(
|
||||
&mut font_system,
|
||||
text.bounds.width,
|
||||
text.bounds.height,
|
||||
);
|
||||
|
||||
buffer.set_text(
|
||||
&mut font_system,
|
||||
text.content,
|
||||
text::to_attributes(text.font),
|
||||
text::to_shaping(text.shaping),
|
||||
);
|
||||
|
||||
let min_bounds = text::measure(&buffer);
|
||||
|
||||
Self(Arc::new(Internal {
|
||||
buffer,
|
||||
content: text.content.to_owned(),
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
bounds: text.bounds,
|
||||
min_bounds,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &cosmic_text::Buffer {
|
||||
&self.0.buffer
|
||||
}
|
||||
|
||||
pub fn downgrade(&self) -> Weak {
|
||||
Weak {
|
||||
raw: Arc::downgrade(&self.0),
|
||||
min_bounds: self.0.min_bounds,
|
||||
horizontal_alignment: self.0.horizontal_alignment,
|
||||
vertical_alignment: self.0.vertical_alignment,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_bounds: Size, font_system: &FontSystem) {
|
||||
if let Some(internal) = Arc::get_mut(&mut self.0) {
|
||||
// If there is no strong reference holding on to the paragraph, we
|
||||
// resize the buffer in-place
|
||||
internal.buffer.set_size(
|
||||
&mut font_system.write(),
|
||||
new_bounds.width,
|
||||
new_bounds.height,
|
||||
);
|
||||
|
||||
internal.bounds = new_bounds;
|
||||
internal.min_bounds = text::measure(&internal.buffer);
|
||||
} else {
|
||||
let metrics = self.0.buffer.metrics();
|
||||
|
||||
// If there is a strong reference somewhere, we recompute the buffer
|
||||
// from scratch
|
||||
*self = Self::with_text(
|
||||
Text {
|
||||
content: &self.0.content,
|
||||
bounds: self.0.bounds,
|
||||
size: Pixels(metrics.font_size),
|
||||
line_height: LineHeight::Absolute(Pixels(
|
||||
metrics.line_height,
|
||||
)),
|
||||
font: self.0.font,
|
||||
horizontal_alignment: self.0.horizontal_alignment,
|
||||
vertical_alignment: self.0.vertical_alignment,
|
||||
shaping: self.0.shaping,
|
||||
},
|
||||
font_system,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::text::Paragraph for Paragraph {
|
||||
type Font = Font;
|
||||
|
||||
fn content(&self) -> &str {
|
||||
&self.0.content
|
||||
}
|
||||
|
||||
fn text_size(&self) -> Pixels {
|
||||
Pixels(self.0.buffer.metrics().font_size)
|
||||
}
|
||||
|
||||
fn line_height(&self) -> LineHeight {
|
||||
LineHeight::Absolute(Pixels(self.0.buffer.metrics().line_height))
|
||||
}
|
||||
|
||||
fn font(&self) -> Font {
|
||||
self.0.font
|
||||
}
|
||||
|
||||
fn shaping(&self) -> Shaping {
|
||||
self.0.shaping
|
||||
}
|
||||
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal {
|
||||
self.0.horizontal_alignment
|
||||
}
|
||||
|
||||
fn vertical_alignment(&self) -> alignment::Vertical {
|
||||
self.0.vertical_alignment
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Size {
|
||||
self.0.bounds
|
||||
}
|
||||
|
||||
fn min_bounds(&self) -> Size {
|
||||
self.0.min_bounds
|
||||
}
|
||||
|
||||
fn hit_test(&self, point: Point) -> Option<Hit> {
|
||||
let cursor = self.0.buffer.hit(point.x, point.y)?;
|
||||
|
||||
Some(Hit::CharOffset(cursor.index))
|
||||
}
|
||||
|
||||
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
|
||||
let run = self.0.buffer.layout_runs().nth(line)?;
|
||||
|
||||
// TODO: Index represents a grapheme, not a glyph
|
||||
let glyph = run.glyphs.get(index).or_else(|| run.glyphs.last())?;
|
||||
|
||||
let advance_last = if index == run.glyphs.len() {
|
||||
glyph.w
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
Some(Point::new(
|
||||
glyph.x + glyph.x_offset * glyph.font_size + advance_last,
|
||||
glyph.y - glyph.y_offset * glyph.font_size,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Internal {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.content == other.content
|
||||
&& self.font == other.font
|
||||
&& self.shaping == other.shaping
|
||||
&& self.horizontal_alignment == other.horizontal_alignment
|
||||
&& self.vertical_alignment == other.vertical_alignment
|
||||
&& self.bounds == other.bounds
|
||||
&& self.min_bounds == other.min_bounds
|
||||
&& self.buffer.metrics() == other.buffer.metrics()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Internal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
|
||||
font_size: 1.0,
|
||||
line_height: 1.0,
|
||||
}),
|
||||
content: String::new(),
|
||||
font: Font::default(),
|
||||
shaping: Shaping::default(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
bounds: Size::ZERO,
|
||||
min_bounds: Size::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Paragraph {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Paragraph")
|
||||
.field("content", &self.0.content)
|
||||
.field("font", &self.0.font)
|
||||
.field("shaping", &self.0.shaping)
|
||||
.field("horizontal_alignment", &self.0.horizontal_alignment)
|
||||
.field("vertical_alignment", &self.0.vertical_alignment)
|
||||
.field("bounds", &self.0.bounds)
|
||||
.field("min_bounds", &self.0.min_bounds)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Weak {
|
||||
raw: sync::Weak<Internal>,
|
||||
pub min_bounds: Size,
|
||||
pub horizontal_alignment: alignment::Horizontal,
|
||||
pub vertical_alignment: alignment::Vertical,
|
||||
}
|
||||
|
||||
impl Weak {
|
||||
pub fn upgrade(&self) -> Option<Paragraph> {
|
||||
self.raw.upgrade().map(Paragraph)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Weak {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self.raw.upgrade(), other.raw.upgrade()) {
|
||||
(Some(p1), Some(p2)) => p1 == p2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -224,16 +224,15 @@ impl Candidate {
|
|||
match self {
|
||||
Self::TinySkia => {
|
||||
let (compositor, backend) =
|
||||
iced_tiny_skia::window::compositor::new(
|
||||
iced_tiny_skia::Settings {
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
},
|
||||
);
|
||||
iced_tiny_skia::window::compositor::new();
|
||||
|
||||
Ok((
|
||||
Compositor::TinySkia(compositor),
|
||||
Renderer::TinySkia(iced_tiny_skia::Renderer::new(backend)),
|
||||
Renderer::TinySkia(iced_tiny_skia::Renderer::new(
|
||||
backend,
|
||||
settings.default_font,
|
||||
settings.default_text_size,
|
||||
)),
|
||||
))
|
||||
}
|
||||
#[cfg(feature = "wgpu")]
|
||||
|
|
@ -250,7 +249,11 @@ impl Candidate {
|
|||
|
||||
Ok((
|
||||
Compositor::Wgpu(compositor),
|
||||
Renderer::Wgpu(iced_wgpu::Renderer::new(backend)),
|
||||
Renderer::Wgpu(iced_wgpu::Renderer::new(
|
||||
backend,
|
||||
settings.default_font,
|
||||
settings.default_text_size,
|
||||
)),
|
||||
))
|
||||
}
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ pub use geometry::Geometry;
|
|||
|
||||
use crate::core::renderer;
|
||||
use crate::core::text::{self, Text};
|
||||
use crate::core::{Background, Font, Point, Rectangle, Size, Vector};
|
||||
use crate::core::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Vector,
|
||||
};
|
||||
use crate::graphics::text::Paragraph;
|
||||
use crate::graphics::Mesh;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -142,6 +145,7 @@ impl<T> core::Renderer for Renderer<T> {
|
|||
|
||||
impl<T> text::Renderer for Renderer<T> {
|
||||
type Font = Font;
|
||||
type Paragraph = Paragraph;
|
||||
|
||||
const ICON_FONT: Font = iced_tiny_skia::Renderer::<T>::ICON_FONT;
|
||||
const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::<T>::CHECKMARK_ICON;
|
||||
|
|
@ -152,59 +156,50 @@ impl<T> text::Renderer for Renderer<T> {
|
|||
delegate!(self, renderer, renderer.default_font())
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
fn default_size(&self) -> Pixels {
|
||||
delegate!(self, renderer, renderer.default_size())
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
) -> Size {
|
||||
delegate!(
|
||||
self,
|
||||
renderer,
|
||||
renderer.measure(content, size, line_height, font, bounds, shaping)
|
||||
)
|
||||
fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph {
|
||||
delegate!(self, renderer, renderer.create_paragraph(text))
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
fn resize_paragraph(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
paragraph: &mut Self::Paragraph,
|
||||
new_bounds: Size,
|
||||
) {
|
||||
delegate!(
|
||||
self,
|
||||
renderer,
|
||||
renderer.hit_test(
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
point,
|
||||
nearest_only
|
||||
)
|
||||
)
|
||||
renderer.resize_paragraph(paragraph, new_bounds)
|
||||
);
|
||||
}
|
||||
|
||||
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
delegate!(self, renderer, renderer.load_font(bytes));
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
|
||||
delegate!(self, renderer, renderer.fill_text(text));
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
text: &Self::Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
) {
|
||||
delegate!(
|
||||
self,
|
||||
renderer,
|
||||
renderer.fill_paragraph(text, position, color)
|
||||
);
|
||||
}
|
||||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: Text<'_, Self::Font>,
|
||||
position: Point,
|
||||
color: Color,
|
||||
) {
|
||||
delegate!(self, renderer, renderer.fill_text(text, position, color));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::Font;
|
||||
use crate::core::{Font, Pixels};
|
||||
use crate::graphics::Antialiasing;
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
|
|
@ -12,7 +12,7 @@ pub struct Settings {
|
|||
/// The default size of text.
|
||||
///
|
||||
/// By default, it will be set to `16.0`.
|
||||
pub default_text_size: f32,
|
||||
pub default_text_size: Pixels,
|
||||
|
||||
/// The antialiasing strategy that will be used for triangle primitives.
|
||||
///
|
||||
|
|
@ -24,7 +24,7 @@ impl Default for Settings {
|
|||
fn default() -> Settings {
|
||||
Settings {
|
||||
default_font: Font::default(),
|
||||
default_text_size: 16.0,
|
||||
default_text_size: Pixels(16.0),
|
||||
antialiasing: None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,8 +95,11 @@ where
|
|||
let Cache { mut state } = cache;
|
||||
state.diff(root.as_widget());
|
||||
|
||||
let base =
|
||||
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
|
||||
let base = root.as_widget().layout(
|
||||
&state,
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, bounds),
|
||||
);
|
||||
|
||||
UserInterface {
|
||||
root,
|
||||
|
|
@ -226,8 +229,9 @@ where
|
|||
if shell.is_layout_invalid() {
|
||||
let _ = ManuallyDrop::into_inner(manual_overlay);
|
||||
|
||||
self.base = renderer.layout(
|
||||
&self.root,
|
||||
self.base = self.root.as_widget().layout(
|
||||
&self.state,
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, self.bounds),
|
||||
);
|
||||
|
||||
|
|
@ -325,8 +329,9 @@ where
|
|||
}
|
||||
|
||||
shell.revalidate_layout(|| {
|
||||
self.base = renderer.layout(
|
||||
&self.root,
|
||||
self.base = self.root.as_widget().layout(
|
||||
&self.state,
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, self.bounds),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Configure your application.
|
||||
use crate::window;
|
||||
use crate::Font;
|
||||
use crate::{Font, Pixels};
|
||||
|
||||
/// The settings of an application.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -29,7 +29,7 @@ pub struct Settings<Flags> {
|
|||
/// The text size that will be used by default.
|
||||
///
|
||||
/// The default value is `16.0`.
|
||||
pub default_text_size: f32,
|
||||
pub default_text_size: Pixels,
|
||||
|
||||
/// If set to true, the renderer will try to perform antialiasing for some
|
||||
/// primitives.
|
||||
|
|
@ -80,7 +80,7 @@ where
|
|||
window: Default::default(),
|
||||
flags: Default::default(),
|
||||
default_font: Default::default(),
|
||||
default_text_size: 16.0,
|
||||
default_text_size: Pixels(16.0),
|
||||
antialiasing: false,
|
||||
exit_on_close_request: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
use crate::core::text;
|
||||
use crate::core::Gradient;
|
||||
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
|
||||
use crate::core::{Background, Color, Gradient, Rectangle, Vector};
|
||||
use crate::graphics::backend;
|
||||
use crate::graphics::text;
|
||||
use crate::graphics::{Damage, Viewport};
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::Settings;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct Backend {
|
||||
default_font: Font,
|
||||
default_text_size: f32,
|
||||
text_pipeline: crate::text::Pipeline,
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -21,10 +17,8 @@ pub struct Backend {
|
|||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn new(settings: Settings) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
text_pipeline: crate::text::Pipeline::new(),
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -364,6 +358,32 @@ impl Backend {
|
|||
}
|
||||
}
|
||||
}
|
||||
Primitive::Paragraph {
|
||||
paragraph,
|
||||
position,
|
||||
color,
|
||||
} => {
|
||||
let physical_bounds =
|
||||
(Rectangle::new(*position, paragraph.min_bounds)
|
||||
+ translation)
|
||||
* scale_factor;
|
||||
|
||||
if !clip_bounds.intersects(&physical_bounds) {
|
||||
return;
|
||||
}
|
||||
|
||||
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
|
||||
.then_some(clip_mask as &_);
|
||||
|
||||
self.text_pipeline.draw_paragraph(
|
||||
paragraph,
|
||||
*position + translation,
|
||||
*color,
|
||||
scale_factor,
|
||||
pixels,
|
||||
clip_mask,
|
||||
);
|
||||
}
|
||||
Primitive::Text {
|
||||
content,
|
||||
bounds,
|
||||
|
|
@ -599,6 +619,12 @@ impl Backend {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Backend {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn into_color(color: Color) -> tiny_skia::Color {
|
||||
tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
|
||||
.expect("Convert color from iced to tiny_skia")
|
||||
|
|
@ -779,58 +805,8 @@ impl iced_graphics::Backend for Backend {
|
|||
}
|
||||
|
||||
impl backend::Text for Backend {
|
||||
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||
|
||||
fn default_font(&self) -> Font {
|
||||
self.default_font
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
self.default_text_size
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
) -> Size {
|
||||
self.text_pipeline.measure(
|
||||
contents,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
)
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: text::Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
self.text_pipeline.hit_test(
|
||||
contents,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
point,
|
||||
nearest_only,
|
||||
)
|
||||
fn font_system(&self) -> &text::FontSystem {
|
||||
self.text_pipeline.font_system()
|
||||
}
|
||||
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
|
|
@ -840,7 +816,10 @@ impl backend::Text for Backend {
|
|||
|
||||
#[cfg(feature = "image")]
|
||||
impl backend::Image for Backend {
|
||||
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
|
||||
fn dimensions(
|
||||
&self,
|
||||
handle: &crate::core::image::Handle,
|
||||
) -> crate::core::Size<u32> {
|
||||
self.raster_pipeline.dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
|
@ -850,7 +829,7 @@ impl backend::Svg for Backend {
|
|||
fn viewport_dimensions(
|
||||
&self,
|
||||
handle: &crate::core::svg::Handle,
|
||||
) -> Size<u32> {
|
||||
) -> crate::core::Size<u32> {
|
||||
self.vector_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::Font;
|
||||
use crate::core::{Font, Pixels};
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
///
|
||||
|
|
@ -11,14 +11,14 @@ pub struct Settings {
|
|||
/// The default size of text.
|
||||
///
|
||||
/// By default, it will be set to `16.0`.
|
||||
pub default_text_size: f32,
|
||||
pub default_text_size: Pixels,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
default_font: Font::default(),
|
||||
default_text_size: 16.0,
|
||||
default_text_size: Pixels(16.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::font::{self, Font};
|
||||
use crate::core::text::{Hit, LineHeight, Shaping};
|
||||
use crate::core::{Color, Pixels, Point, Rectangle, Size};
|
||||
use crate::core::text::{LineHeight, Shaping};
|
||||
use crate::core::{Color, Font, Pixels, Point, Rectangle};
|
||||
use crate::graphics::text::cache::{self, Cache};
|
||||
use crate::graphics::text::paragraph;
|
||||
use crate::graphics::text::FontSystem;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Pipeline {
|
||||
font_system: RefCell<cosmic_text::FontSystem>,
|
||||
font_system: FontSystem,
|
||||
glyph_cache: GlyphCache,
|
||||
cache: RefCell<Cache>,
|
||||
}
|
||||
|
|
@ -20,17 +21,16 @@ pub struct Pipeline {
|
|||
impl Pipeline {
|
||||
pub fn new() -> Self {
|
||||
Pipeline {
|
||||
font_system: RefCell::new(cosmic_text::FontSystem::new_with_fonts(
|
||||
[cosmic_text::fontdb::Source::Binary(Arc::new(
|
||||
include_bytes!("../fonts/Iced-Icons.ttf").as_slice(),
|
||||
))]
|
||||
.into_iter(),
|
||||
)),
|
||||
font_system: FontSystem::new(),
|
||||
glyph_cache: GlyphCache::new(),
|
||||
cache: RefCell::new(Cache::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font_system(&self) -> &FontSystem {
|
||||
&self.font_system
|
||||
}
|
||||
|
||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
self.font_system.get_mut().db_mut().load_font_source(
|
||||
cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
|
||||
|
|
@ -39,12 +39,23 @@ impl Pipeline {
|
|||
self.cache = RefCell::new(Cache::new());
|
||||
}
|
||||
|
||||
pub fn draw_paragraph(
|
||||
&mut self,
|
||||
_paragraph: ¶graph::Weak,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
_scale_factor: f32,
|
||||
_pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||
_clip_mask: Option<&tiny_skia::Mask>,
|
||||
) {
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
content: &str,
|
||||
bounds: Rectangle,
|
||||
color: Color,
|
||||
size: f32,
|
||||
size: Pixels,
|
||||
line_height: LineHeight,
|
||||
font: Font,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
|
|
@ -54,22 +65,22 @@ impl Pipeline {
|
|||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||
clip_mask: Option<&tiny_skia::Mask>,
|
||||
) {
|
||||
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
|
||||
let line_height = f32::from(line_height.to_absolute(size));
|
||||
|
||||
let font_system = self.font_system.get_mut();
|
||||
let key = Key {
|
||||
let key = cache::Key {
|
||||
bounds: bounds.size(),
|
||||
content,
|
||||
font,
|
||||
size,
|
||||
size: size.into(),
|
||||
line_height,
|
||||
shaping,
|
||||
};
|
||||
|
||||
let (_, entry) = self.cache.get_mut().allocate(font_system, key);
|
||||
let (_, buffer) = self.cache.get_mut().allocate(font_system, key);
|
||||
|
||||
let max_width = entry.bounds.width * scale_factor;
|
||||
let total_height = entry.bounds.height * scale_factor;
|
||||
let max_width = bounds.width * scale_factor;
|
||||
let total_height = bounds.height * scale_factor;
|
||||
|
||||
let bounds = bounds * scale_factor;
|
||||
|
||||
|
|
@ -87,7 +98,7 @@ impl Pipeline {
|
|||
|
||||
let mut swash = cosmic_text::SwashCache::new();
|
||||
|
||||
for run in entry.buffer.layout_runs() {
|
||||
for run in buffer.layout_runs() {
|
||||
for glyph in run.glyphs {
|
||||
let physical_glyph = glyph.physical((x, y), scale_factor);
|
||||
|
||||
|
|
@ -122,130 +133,6 @@ impl Pipeline {
|
|||
self.cache.get_mut().trim();
|
||||
self.glyph_cache.trim();
|
||||
}
|
||||
|
||||
pub fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
) -> Size {
|
||||
let mut measurement_cache = self.cache.borrow_mut();
|
||||
|
||||
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
|
||||
|
||||
let (_, entry) = measurement_cache.allocate(
|
||||
&mut self.font_system.borrow_mut(),
|
||||
Key {
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
},
|
||||
);
|
||||
|
||||
entry.bounds
|
||||
}
|
||||
|
||||
pub fn hit_test(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
point: Point,
|
||||
_nearest_only: bool,
|
||||
) -> Option<Hit> {
|
||||
let mut measurement_cache = self.cache.borrow_mut();
|
||||
|
||||
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
|
||||
|
||||
let (_, entry) = measurement_cache.allocate(
|
||||
&mut self.font_system.borrow_mut(),
|
||||
Key {
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
},
|
||||
);
|
||||
|
||||
let cursor = entry.buffer.hit(point.x, point.y)?;
|
||||
|
||||
Some(Hit::CharOffset(cursor.index))
|
||||
}
|
||||
}
|
||||
|
||||
fn measure(buffer: &cosmic_text::Buffer) -> Size {
|
||||
let (width, total_lines) = buffer
|
||||
.layout_runs()
|
||||
.fold((0.0, 0usize), |(width, total_lines), run| {
|
||||
(run.line_w.max(width), total_lines + 1)
|
||||
});
|
||||
|
||||
Size::new(width, total_lines as f32 * buffer.metrics().line_height)
|
||||
}
|
||||
|
||||
fn to_family(family: font::Family) -> cosmic_text::Family<'static> {
|
||||
match family {
|
||||
font::Family::Name(name) => cosmic_text::Family::Name(name),
|
||||
font::Family::SansSerif => cosmic_text::Family::SansSerif,
|
||||
font::Family::Serif => cosmic_text::Family::Serif,
|
||||
font::Family::Cursive => cosmic_text::Family::Cursive,
|
||||
font::Family::Fantasy => cosmic_text::Family::Fantasy,
|
||||
font::Family::Monospace => cosmic_text::Family::Monospace,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_weight(weight: font::Weight) -> cosmic_text::Weight {
|
||||
match weight {
|
||||
font::Weight::Thin => cosmic_text::Weight::THIN,
|
||||
font::Weight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT,
|
||||
font::Weight::Light => cosmic_text::Weight::LIGHT,
|
||||
font::Weight::Normal => cosmic_text::Weight::NORMAL,
|
||||
font::Weight::Medium => cosmic_text::Weight::MEDIUM,
|
||||
font::Weight::Semibold => cosmic_text::Weight::SEMIBOLD,
|
||||
font::Weight::Bold => cosmic_text::Weight::BOLD,
|
||||
font::Weight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD,
|
||||
font::Weight::Black => cosmic_text::Weight::BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch {
|
||||
match stretch {
|
||||
font::Stretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed,
|
||||
font::Stretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed,
|
||||
font::Stretch::Condensed => cosmic_text::Stretch::Condensed,
|
||||
font::Stretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed,
|
||||
font::Stretch::Normal => cosmic_text::Stretch::Normal,
|
||||
font::Stretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded,
|
||||
font::Stretch::Expanded => cosmic_text::Stretch::Expanded,
|
||||
font::Stretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded,
|
||||
font::Stretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_style(style: font::Style) -> cosmic_text::Style {
|
||||
match style {
|
||||
font::Style::Normal => cosmic_text::Style::Normal,
|
||||
font::Style::Italic => cosmic_text::Style::Italic,
|
||||
font::Style::Oblique => cosmic_text::Style::Oblique,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
|
||||
match shaping {
|
||||
Shaping::Basic => cosmic_text::Shaping::Basic,
|
||||
Shaping::Advanced => cosmic_text::Shaping::Advanced,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
|
@ -358,135 +245,3 @@ impl GlyphCache {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Cache {
|
||||
entries: FxHashMap<KeyHash, Entry>,
|
||||
measurements: FxHashMap<KeyHash, KeyHash>,
|
||||
recently_used: FxHashSet<KeyHash>,
|
||||
hasher: HashBuilder,
|
||||
trim_count: usize,
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
buffer: cosmic_text::Buffer,
|
||||
bounds: Size,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||
|
||||
impl Cache {
|
||||
const TRIM_INTERVAL: usize = 300;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
entries: FxHashMap::default(),
|
||||
measurements: FxHashMap::default(),
|
||||
recently_used: FxHashSet::default(),
|
||||
hasher: HashBuilder::default(),
|
||||
trim_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate(
|
||||
&mut self,
|
||||
font_system: &mut cosmic_text::FontSystem,
|
||||
key: Key<'_>,
|
||||
) -> (KeyHash, &mut Entry) {
|
||||
let hash = key.hash(self.hasher.build_hasher());
|
||||
|
||||
if let Some(hash) = self.measurements.get(&hash) {
|
||||
let _ = self.recently_used.insert(*hash);
|
||||
|
||||
return (*hash, self.entries.get_mut(hash).unwrap());
|
||||
}
|
||||
|
||||
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
|
||||
let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2);
|
||||
let mut buffer = cosmic_text::Buffer::new(font_system, metrics);
|
||||
|
||||
buffer.set_size(
|
||||
font_system,
|
||||
key.bounds.width,
|
||||
key.bounds.height.max(key.size * 1.2),
|
||||
);
|
||||
buffer.set_text(
|
||||
font_system,
|
||||
key.content,
|
||||
cosmic_text::Attrs::new()
|
||||
.family(to_family(key.font.family))
|
||||
.weight(to_weight(key.font.weight))
|
||||
.stretch(to_stretch(key.font.stretch))
|
||||
.style(to_style(key.font.style)),
|
||||
to_shaping(key.shaping),
|
||||
);
|
||||
|
||||
let bounds = measure(&buffer);
|
||||
|
||||
let _ = entry.insert(Entry { buffer, bounds });
|
||||
|
||||
for bounds in [
|
||||
bounds,
|
||||
Size {
|
||||
width: key.bounds.width,
|
||||
..bounds
|
||||
},
|
||||
] {
|
||||
if key.bounds != bounds {
|
||||
let _ = self.measurements.insert(
|
||||
Key { bounds, ..key }.hash(self.hasher.build_hasher()),
|
||||
hash,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.recently_used.insert(hash);
|
||||
|
||||
(hash, self.entries.get_mut(&hash).unwrap())
|
||||
}
|
||||
|
||||
fn trim(&mut self) {
|
||||
if self.trim_count > Self::TRIM_INTERVAL {
|
||||
self.entries
|
||||
.retain(|key, _| self.recently_used.contains(key));
|
||||
self.measurements
|
||||
.retain(|_, value| self.recently_used.contains(value));
|
||||
|
||||
self.recently_used.clear();
|
||||
|
||||
self.trim_count = 0;
|
||||
} else {
|
||||
self.trim_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Key<'a> {
|
||||
content: &'a str,
|
||||
size: f32,
|
||||
line_height: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
}
|
||||
|
||||
impl Key<'_> {
|
||||
fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash {
|
||||
self.content.hash(&mut hasher);
|
||||
self.size.to_bits().hash(&mut hasher);
|
||||
self.line_height.to_bits().hash(&mut hasher);
|
||||
self.font.hash(&mut hasher);
|
||||
self.bounds.width.to_bits().hash(&mut hasher);
|
||||
self.bounds.height.to_bits().hash(&mut hasher);
|
||||
self.shaping.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
type KeyHash = u64;
|
||||
|
|
|
|||
|
|
@ -28,9 +28,16 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
|||
settings: Self::Settings,
|
||||
_compatible_window: Option<&W>,
|
||||
) -> Result<(Self, Self::Renderer), Error> {
|
||||
let (compositor, backend) = new(settings);
|
||||
let (compositor, backend) = new();
|
||||
|
||||
Ok((compositor, Renderer::new(backend)))
|
||||
Ok((
|
||||
compositor,
|
||||
Renderer::new(
|
||||
backend,
|
||||
settings.default_font,
|
||||
settings.default_text_size,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||
|
|
@ -113,12 +120,12 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) {
|
||||
pub fn new<Theme>() -> (Compositor<Theme>, Backend) {
|
||||
(
|
||||
Compositor {
|
||||
_theme: PhantomData,
|
||||
},
|
||||
Backend::new(settings),
|
||||
Backend::new(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,20 +21,11 @@ guillotiere = "0.6"
|
|||
futures = "0.3"
|
||||
bitflags = "1.2"
|
||||
once_cell = "1.0"
|
||||
rustc-hash = "1.1"
|
||||
log = "0.4"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wgpu = { version = "0.16", features = ["webgl"] }
|
||||
|
||||
[dependencies.twox-hash]
|
||||
version = "1.6"
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
|
||||
version = "1.6.1"
|
||||
features = ["std"]
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.9"
|
||||
features = ["derive"]
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core;
|
||||
use crate::core::{Color, Font, Point, Size};
|
||||
use crate::core::{Color, Size};
|
||||
use crate::graphics;
|
||||
use crate::graphics::backend;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::{Transformation, Viewport};
|
||||
|
|
@ -29,9 +29,6 @@ pub struct Backend {
|
|||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: image::Pipeline,
|
||||
|
||||
default_font: Font,
|
||||
default_text_size: f32,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
|
|
@ -57,9 +54,6 @@ impl Backend {
|
|||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,65 +307,11 @@ impl Backend {
|
|||
|
||||
impl crate::graphics::Backend for Backend {
|
||||
type Primitive = primitive::Custom;
|
||||
|
||||
fn trim_measurements(&mut self) {
|
||||
self.text_pipeline.trim_measurements();
|
||||
}
|
||||
}
|
||||
|
||||
impl backend::Text for Backend {
|
||||
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||
|
||||
fn default_font(&self) -> Font {
|
||||
self.default_font
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
self.default_text_size
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: core::text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: core::text::Shaping,
|
||||
) -> Size {
|
||||
self.text_pipeline.measure(
|
||||
contents,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
)
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
line_height: core::text::LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: core::text::Shaping,
|
||||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<core::text::Hit> {
|
||||
self.text_pipeline.hit_test(
|
||||
contents,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
point,
|
||||
nearest_only,
|
||||
)
|
||||
fn font_system(&self) -> &graphics::text::FontSystem {
|
||||
self.text_pipeline.font_system()
|
||||
}
|
||||
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
|
|
@ -381,14 +321,17 @@ impl backend::Text for Backend {
|
|||
|
||||
#[cfg(feature = "image")]
|
||||
impl backend::Image for Backend {
|
||||
fn dimensions(&self, handle: &core::image::Handle) -> Size<u32> {
|
||||
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
|
||||
self.image_pipeline.dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
impl backend::Svg for Backend {
|
||||
fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size<u32> {
|
||||
fn viewport_dimensions(
|
||||
&self,
|
||||
handle: &crate::core::svg::Handle,
|
||||
) -> Size<u32> {
|
||||
self.image_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub use text::Text;
|
|||
|
||||
use crate::core;
|
||||
use crate::core::alignment;
|
||||
use crate::core::{Color, Font, Point, Rectangle, Size, Vector};
|
||||
use crate::core::{Color, Font, Pixels, Point, Rectangle, Size, Vector};
|
||||
use crate::graphics;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::Viewport;
|
||||
|
|
@ -56,14 +56,14 @@ impl<'a> Layer<'a> {
|
|||
Layer::new(Rectangle::with_size(viewport.logical_size()));
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
let text = Text {
|
||||
let text = text::Cached {
|
||||
content: line.as_ref(),
|
||||
bounds: Rectangle::new(
|
||||
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
||||
Size::INFINITY,
|
||||
),
|
||||
color: Color::new(0.9, 0.9, 0.9, 1.0),
|
||||
size: 20.0,
|
||||
size: Pixels(20.0),
|
||||
line_height: core::text::LineHeight::default(),
|
||||
font: Font::MONOSPACE,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
|
|
@ -71,13 +71,13 @@ impl<'a> Layer<'a> {
|
|||
shaping: core::text::Shaping::Basic,
|
||||
};
|
||||
|
||||
overlay.text.push(text);
|
||||
overlay.text.push(Text::Cached(text.clone()));
|
||||
|
||||
overlay.text.push(Text {
|
||||
overlay.text.push(Text::Cached(text::Cached {
|
||||
bounds: text.bounds + Vector::new(-1.0, -1.0),
|
||||
color: Color::BLACK,
|
||||
..text
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
overlay
|
||||
|
|
@ -113,6 +113,19 @@ impl<'a> Layer<'a> {
|
|||
current_layer: usize,
|
||||
) {
|
||||
match primitive {
|
||||
Primitive::Paragraph {
|
||||
paragraph,
|
||||
position,
|
||||
color,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.text.push(Text::Managed {
|
||||
paragraph: paragraph.clone(),
|
||||
position: *position + translation,
|
||||
color: *color,
|
||||
});
|
||||
}
|
||||
Primitive::Text {
|
||||
content,
|
||||
bounds,
|
||||
|
|
@ -126,7 +139,7 @@ impl<'a> Layer<'a> {
|
|||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.text.push(Text {
|
||||
layer.text.push(Text::Cached(text::Cached {
|
||||
content,
|
||||
bounds: *bounds + translation,
|
||||
size: *size,
|
||||
|
|
@ -136,7 +149,7 @@ impl<'a> Layer<'a> {
|
|||
horizontal_alignment: *horizontal_alignment,
|
||||
vertical_alignment: *vertical_alignment,
|
||||
shaping: *shaping,
|
||||
});
|
||||
}));
|
||||
}
|
||||
Primitive::Quad {
|
||||
bounds,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::text;
|
||||
use crate::core::{Color, Font, Rectangle};
|
||||
use crate::core::{Color, Font, Pixels, Point, Rectangle};
|
||||
use crate::graphics::text::paragraph;
|
||||
|
||||
/// A paragraph of text.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Text<'a> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Text<'a> {
|
||||
Managed {
|
||||
paragraph: paragraph::Weak,
|
||||
position: Point,
|
||||
color: Color,
|
||||
},
|
||||
Cached(Cached<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cached<'a> {
|
||||
/// The content of the [`Text`].
|
||||
pub content: &'a str,
|
||||
|
||||
|
|
@ -15,7 +26,7 @@ pub struct Text<'a> {
|
|||
pub color: Color,
|
||||
|
||||
/// The size of the [`Text`] in logical pixels.
|
||||
pub size: f32,
|
||||
pub size: Pixels,
|
||||
|
||||
/// The line height of the [`Text`].
|
||||
pub line_height: text::LineHeight,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
//missing_docs,
|
||||
unsafe_code,
|
||||
unused_results,
|
||||
clippy::extra_unused_lifetimes,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Configure a renderer.
|
||||
use crate::core::Font;
|
||||
use crate::core::{Font, Pixels};
|
||||
use crate::graphics::Antialiasing;
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
|
|
@ -21,7 +21,7 @@ pub struct Settings {
|
|||
/// The default size of text.
|
||||
///
|
||||
/// By default, it will be set to `16.0`.
|
||||
pub default_text_size: f32,
|
||||
pub default_text_size: Pixels,
|
||||
|
||||
/// The antialiasing strategy that will be used for triangle primitives.
|
||||
///
|
||||
|
|
@ -59,7 +59,7 @@ impl Default for Settings {
|
|||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
internal_backend: wgpu::Backends::all(),
|
||||
default_font: Font::default(),
|
||||
default_text_size: 16.0,
|
||||
default_text_size: Pixels(16.0),
|
||||
antialiasing: None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
415
wgpu/src/text.rs
415
wgpu/src/text.rs
|
|
@ -1,20 +1,17 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::font::{self, Font};
|
||||
use crate::core::text::{Hit, LineHeight, Shaping};
|
||||
use crate::core::{Pixels, Point, Rectangle, Size};
|
||||
use crate::core::{Rectangle, Size};
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::text::cache::{self, Cache};
|
||||
use crate::graphics::text::{FontSystem, Paragraph};
|
||||
use crate::layer::Text;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Pipeline {
|
||||
font_system: RefCell<glyphon::FontSystem>,
|
||||
font_system: FontSystem,
|
||||
renderers: Vec<glyphon::TextRenderer>,
|
||||
atlas: glyphon::TextAtlas,
|
||||
prepare_layer: usize,
|
||||
|
|
@ -28,12 +25,7 @@ impl Pipeline {
|
|||
format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
Pipeline {
|
||||
font_system: RefCell::new(glyphon::FontSystem::new_with_fonts(
|
||||
[glyphon::fontdb::Source::Binary(Arc::new(
|
||||
include_bytes!("../fonts/Iced-Icons.ttf").as_slice(),
|
||||
))]
|
||||
.into_iter(),
|
||||
)),
|
||||
font_system: FontSystem::new(),
|
||||
renderers: Vec::new(),
|
||||
atlas: glyphon::TextAtlas::with_color_mode(
|
||||
device,
|
||||
|
|
@ -50,6 +42,10 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn font_system(&self) -> &FontSystem {
|
||||
&self.font_system
|
||||
}
|
||||
|
||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
let _ = self.font_system.get_mut().db_mut().load_font_source(
|
||||
glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
|
||||
|
|
@ -80,59 +76,99 @@ impl Pipeline {
|
|||
let renderer = &mut self.renderers[self.prepare_layer];
|
||||
let cache = self.cache.get_mut();
|
||||
|
||||
if self.prepare_layer == 0 {
|
||||
cache.trim(Purpose::Drawing);
|
||||
enum Allocation {
|
||||
Paragraph(Paragraph),
|
||||
Cache(cache::KeyHash),
|
||||
}
|
||||
|
||||
let keys: Vec<_> = sections
|
||||
let allocations: Vec<_> = sections
|
||||
.iter()
|
||||
.map(|section| {
|
||||
.map(|section| match section {
|
||||
Text::Managed { paragraph, .. } => {
|
||||
paragraph.upgrade().map(Allocation::Paragraph)
|
||||
}
|
||||
Text::Cached(text) => {
|
||||
let (key, _) = cache.allocate(
|
||||
font_system,
|
||||
Key {
|
||||
content: section.content,
|
||||
size: section.size,
|
||||
cache::Key {
|
||||
content: text.content,
|
||||
size: text.size.into(),
|
||||
line_height: f32::from(
|
||||
section
|
||||
.line_height
|
||||
.to_absolute(Pixels(section.size)),
|
||||
text.line_height.to_absolute(text.size),
|
||||
),
|
||||
font: section.font,
|
||||
font: text.font,
|
||||
bounds: Size {
|
||||
width: section.bounds.width,
|
||||
height: section.bounds.height,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
shaping: section.shaping,
|
||||
shaping: text.shaping,
|
||||
},
|
||||
Purpose::Drawing,
|
||||
);
|
||||
|
||||
key
|
||||
Some(Allocation::Cache(key))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let bounds = bounds * scale_factor;
|
||||
let layer_bounds = bounds * scale_factor;
|
||||
|
||||
let text_areas =
|
||||
sections
|
||||
.iter()
|
||||
.zip(keys.iter())
|
||||
.filter_map(|(section, key)| {
|
||||
let entry = cache.get(key).expect("Get cached buffer");
|
||||
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|
||||
|(section, allocation)| {
|
||||
let (
|
||||
buffer,
|
||||
bounds,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
color,
|
||||
) = match section {
|
||||
Text::Managed {
|
||||
position, color, ..
|
||||
} => {
|
||||
use crate::core::text::Paragraph as _;
|
||||
|
||||
let x = section.bounds.x * scale_factor;
|
||||
let y = section.bounds.y * scale_factor;
|
||||
let Some(Allocation::Paragraph(paragraph)) = allocation
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let max_width = entry.bounds.width * scale_factor;
|
||||
let total_height = entry.bounds.height * scale_factor;
|
||||
(
|
||||
paragraph.buffer(),
|
||||
Rectangle::new(*position, paragraph.min_bounds()),
|
||||
paragraph.horizontal_alignment(),
|
||||
paragraph.vertical_alignment(),
|
||||
*color,
|
||||
)
|
||||
}
|
||||
Text::Cached(text) => {
|
||||
let Some(Allocation::Cache(key)) = allocation else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let left = match section.horizontal_alignment {
|
||||
let buffer = cache.get(key).expect("Get cached buffer");
|
||||
|
||||
(
|
||||
buffer,
|
||||
text.bounds,
|
||||
text.horizontal_alignment,
|
||||
text.vertical_alignment,
|
||||
text.color,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let x = bounds.x * scale_factor;
|
||||
let y = bounds.y * scale_factor;
|
||||
|
||||
let max_width = bounds.width * scale_factor;
|
||||
let total_height = bounds.height * scale_factor;
|
||||
|
||||
let left = match horizontal_alignment {
|
||||
alignment::Horizontal::Left => x,
|
||||
alignment::Horizontal::Center => x - max_width / 2.0,
|
||||
alignment::Horizontal::Right => x - max_width,
|
||||
};
|
||||
|
||||
let top = match section.vertical_alignment {
|
||||
let top = match vertical_alignment {
|
||||
alignment::Vertical::Top => y,
|
||||
alignment::Vertical::Center => y - total_height / 2.0,
|
||||
alignment::Vertical::Bottom => y - total_height,
|
||||
|
|
@ -141,14 +177,14 @@ impl Pipeline {
|
|||
let section_bounds = Rectangle {
|
||||
x: left,
|
||||
y: top,
|
||||
width: section.bounds.width * scale_factor,
|
||||
height: section.bounds.height * scale_factor,
|
||||
width: max_width,
|
||||
height: total_height,
|
||||
};
|
||||
|
||||
let clip_bounds = bounds.intersection(§ion_bounds)?;
|
||||
let clip_bounds = layer_bounds.intersection(§ion_bounds)?;
|
||||
|
||||
Some(glyphon::TextArea {
|
||||
buffer: &entry.buffer,
|
||||
buffer,
|
||||
left,
|
||||
top,
|
||||
scale: scale_factor,
|
||||
|
|
@ -159,8 +195,7 @@ impl Pipeline {
|
|||
bottom: (clip_bounds.y + clip_bounds.height) as i32,
|
||||
},
|
||||
default_color: {
|
||||
let [r, g, b, a] =
|
||||
color::pack(section.color).components();
|
||||
let [r, g, b, a] = color::pack(color).components();
|
||||
|
||||
glyphon::Color::rgba(
|
||||
(r * 255.0) as u8,
|
||||
|
|
@ -170,7 +205,8 @@ impl Pipeline {
|
|||
)
|
||||
},
|
||||
})
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let result = renderer.prepare(
|
||||
device,
|
||||
|
|
@ -219,287 +255,8 @@ impl Pipeline {
|
|||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.atlas.trim();
|
||||
self.cache.get_mut().trim();
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
|
||||
pub fn trim_measurements(&mut self) {
|
||||
self.cache.get_mut().trim(Purpose::Measuring);
|
||||
}
|
||||
|
||||
pub fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
) -> Size {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
|
||||
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
|
||||
|
||||
let (_, entry) = cache.allocate(
|
||||
&mut self.font_system.borrow_mut(),
|
||||
Key {
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
},
|
||||
Purpose::Measuring,
|
||||
);
|
||||
|
||||
entry.bounds
|
||||
}
|
||||
|
||||
pub fn hit_test(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
line_height: LineHeight,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
point: Point,
|
||||
_nearest_only: bool,
|
||||
) -> Option<Hit> {
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
|
||||
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
|
||||
|
||||
let (_, entry) = cache.allocate(
|
||||
&mut self.font_system.borrow_mut(),
|
||||
Key {
|
||||
content,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
bounds,
|
||||
shaping,
|
||||
},
|
||||
Purpose::Measuring,
|
||||
);
|
||||
|
||||
let cursor = entry.buffer.hit(point.x, point.y)?;
|
||||
|
||||
Some(Hit::CharOffset(cursor.index))
|
||||
}
|
||||
}
|
||||
|
||||
fn measure(buffer: &glyphon::Buffer) -> Size {
|
||||
let (width, total_lines) = buffer
|
||||
.layout_runs()
|
||||
.fold((0.0, 0usize), |(width, total_lines), run| {
|
||||
(run.line_w.max(width), total_lines + 1)
|
||||
});
|
||||
|
||||
Size::new(width, total_lines as f32 * buffer.metrics().line_height)
|
||||
}
|
||||
|
||||
fn to_family(family: font::Family) -> glyphon::Family<'static> {
|
||||
match family {
|
||||
font::Family::Name(name) => glyphon::Family::Name(name),
|
||||
font::Family::SansSerif => glyphon::Family::SansSerif,
|
||||
font::Family::Serif => glyphon::Family::Serif,
|
||||
font::Family::Cursive => glyphon::Family::Cursive,
|
||||
font::Family::Fantasy => glyphon::Family::Fantasy,
|
||||
font::Family::Monospace => glyphon::Family::Monospace,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_weight(weight: font::Weight) -> glyphon::Weight {
|
||||
match weight {
|
||||
font::Weight::Thin => glyphon::Weight::THIN,
|
||||
font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT,
|
||||
font::Weight::Light => glyphon::Weight::LIGHT,
|
||||
font::Weight::Normal => glyphon::Weight::NORMAL,
|
||||
font::Weight::Medium => glyphon::Weight::MEDIUM,
|
||||
font::Weight::Semibold => glyphon::Weight::SEMIBOLD,
|
||||
font::Weight::Bold => glyphon::Weight::BOLD,
|
||||
font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD,
|
||||
font::Weight::Black => glyphon::Weight::BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch {
|
||||
match stretch {
|
||||
font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed,
|
||||
font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed,
|
||||
font::Stretch::Condensed => glyphon::Stretch::Condensed,
|
||||
font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed,
|
||||
font::Stretch::Normal => glyphon::Stretch::Normal,
|
||||
font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded,
|
||||
font::Stretch::Expanded => glyphon::Stretch::Expanded,
|
||||
font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded,
|
||||
font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_style(style: font::Style) -> glyphon::Style {
|
||||
match style {
|
||||
font::Style::Normal => glyphon::Style::Normal,
|
||||
font::Style::Italic => glyphon::Style::Italic,
|
||||
font::Style::Oblique => glyphon::Style::Oblique,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_shaping(shaping: Shaping) -> glyphon::Shaping {
|
||||
match shaping {
|
||||
Shaping::Basic => glyphon::Shaping::Basic,
|
||||
Shaping::Advanced => glyphon::Shaping::Advanced,
|
||||
}
|
||||
}
|
||||
|
||||
struct Cache {
|
||||
entries: FxHashMap<KeyHash, Entry>,
|
||||
aliases: FxHashMap<KeyHash, KeyHash>,
|
||||
recently_measured: FxHashSet<KeyHash>,
|
||||
recently_drawn: FxHashSet<KeyHash>,
|
||||
hasher: HashBuilder,
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
buffer: glyphon::Buffer,
|
||||
bounds: Size,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Purpose {
|
||||
Measuring,
|
||||
Drawing,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||
|
||||
impl Cache {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
entries: FxHashMap::default(),
|
||||
aliases: FxHashMap::default(),
|
||||
recently_measured: FxHashSet::default(),
|
||||
recently_drawn: FxHashSet::default(),
|
||||
hasher: HashBuilder::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, key: &KeyHash) -> Option<&Entry> {
|
||||
self.entries.get(key)
|
||||
}
|
||||
|
||||
fn allocate(
|
||||
&mut self,
|
||||
font_system: &mut glyphon::FontSystem,
|
||||
key: Key<'_>,
|
||||
purpose: Purpose,
|
||||
) -> (KeyHash, &mut Entry) {
|
||||
let hash = key.hash(self.hasher.build_hasher());
|
||||
|
||||
let recently_used = match purpose {
|
||||
Purpose::Measuring => &mut self.recently_measured,
|
||||
Purpose::Drawing => &mut self.recently_drawn,
|
||||
};
|
||||
|
||||
if let Some(hash) = self.aliases.get(&hash) {
|
||||
let _ = recently_used.insert(*hash);
|
||||
|
||||
return (*hash, self.entries.get_mut(hash).unwrap());
|
||||
}
|
||||
|
||||
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
|
||||
let metrics = glyphon::Metrics::new(key.size, key.line_height);
|
||||
let mut buffer = glyphon::Buffer::new(font_system, metrics);
|
||||
|
||||
buffer.set_size(
|
||||
font_system,
|
||||
key.bounds.width,
|
||||
key.bounds.height.max(key.line_height),
|
||||
);
|
||||
buffer.set_text(
|
||||
font_system,
|
||||
key.content,
|
||||
glyphon::Attrs::new()
|
||||
.family(to_family(key.font.family))
|
||||
.weight(to_weight(key.font.weight))
|
||||
.stretch(to_stretch(key.font.stretch))
|
||||
.style(to_style(key.font.style)),
|
||||
to_shaping(key.shaping),
|
||||
);
|
||||
|
||||
let bounds = measure(&buffer);
|
||||
let _ = entry.insert(Entry { buffer, bounds });
|
||||
|
||||
for bounds in [
|
||||
bounds,
|
||||
Size {
|
||||
width: key.bounds.width,
|
||||
..bounds
|
||||
},
|
||||
] {
|
||||
if key.bounds != bounds {
|
||||
let _ = self.aliases.insert(
|
||||
Key { bounds, ..key }.hash(self.hasher.build_hasher()),
|
||||
hash,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = recently_used.insert(hash);
|
||||
|
||||
(hash, self.entries.get_mut(&hash).unwrap())
|
||||
}
|
||||
|
||||
fn trim(&mut self, purpose: Purpose) {
|
||||
self.entries.retain(|key, _| {
|
||||
self.recently_measured.contains(key)
|
||||
|| self.recently_drawn.contains(key)
|
||||
});
|
||||
self.aliases.retain(|_, value| {
|
||||
self.recently_measured.contains(value)
|
||||
|| self.recently_drawn.contains(value)
|
||||
});
|
||||
|
||||
match purpose {
|
||||
Purpose::Measuring => {
|
||||
self.recently_measured.clear();
|
||||
}
|
||||
Purpose::Drawing => {
|
||||
self.recently_drawn.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Key<'a> {
|
||||
content: &'a str,
|
||||
size: f32,
|
||||
line_height: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
shaping: Shaping,
|
||||
}
|
||||
|
||||
impl Key<'_> {
|
||||
fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash {
|
||||
self.content.hash(&mut hasher);
|
||||
self.size.to_bits().hash(&mut hasher);
|
||||
self.line_height.to_bits().hash(&mut hasher);
|
||||
self.font.hash(&mut hasher);
|
||||
self.bounds.width.to_bits().hash(&mut hasher);
|
||||
self.bounds.height.to_bits().hash(&mut hasher);
|
||||
self.shaping.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
type KeyHash = u64;
|
||||
|
|
|
|||
|
|
@ -216,7 +216,14 @@ impl<Theme> graphics::Compositor for Compositor<Theme> {
|
|||
) -> Result<(Self, Self::Renderer), Error> {
|
||||
let (compositor, backend) = new(settings, compatible_window)?;
|
||||
|
||||
Ok((compositor, Renderer::new(backend)))
|
||||
Ok((
|
||||
compositor,
|
||||
Renderer::new(
|
||||
backend,
|
||||
settings.default_font,
|
||||
settings.default_text_size,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||
|
|
|
|||
|
|
@ -159,19 +159,15 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
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)
|
||||
},
|
||||
)
|
||||
layout(limits, self.width, self.height, self.padding, |limits| {
|
||||
self.content
|
||||
.as_widget()
|
||||
.layout(&tree.children[0], renderer, limits)
|
||||
})
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -426,17 +422,16 @@ where
|
|||
}
|
||||
|
||||
/// Computes the layout of a [`Button`].
|
||||
pub fn layout<Renderer>(
|
||||
renderer: &Renderer,
|
||||
pub fn layout(
|
||||
limits: &layout::Limits,
|
||||
width: Length,
|
||||
height: Length,
|
||||
padding: Padding,
|
||||
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||
layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(width).height(height);
|
||||
|
||||
let mut content = layout_content(renderer, &limits.pad(padding));
|
||||
let mut content = layout_content(&limits.pad(padding));
|
||||
let padding = padding.fit(content.size(), limits.max());
|
||||
let size = limits.pad(padding).resolve(content.size()).pad(padding);
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@ use crate::core::mouse;
|
|||
use crate::core::renderer;
|
||||
use crate::core::text;
|
||||
use crate::core::touch;
|
||||
use crate::core::widget::Tree;
|
||||
use crate::core::widget;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell,
|
||||
Widget,
|
||||
Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
use crate::{Row, Text};
|
||||
|
||||
pub use iced_style::checkbox::{Appearance, StyleSheet};
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ where
|
|||
width: Length,
|
||||
size: f32,
|
||||
spacing: f32,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Option<Renderer::Font>,
|
||||
|
|
@ -118,7 +117,7 @@ where
|
|||
|
||||
/// Sets the text size of the [`Checkbox`].
|
||||
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(text_size.into().0);
|
||||
self.text_size = Some(text_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +166,14 @@ where
|
|||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet + crate::text::StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<widget::text::State<Renderer::Paragraph>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(widget::text::State::<Renderer::Paragraph>::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -177,26 +184,35 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
Row::<(), Renderer>::new()
|
||||
.width(self.width)
|
||||
.spacing(self.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.push(Row::new().width(self.size).height(self.size))
|
||||
.push(
|
||||
Text::new(&self.label)
|
||||
.font(self.font.unwrap_or_else(|| renderer.default_font()))
|
||||
.width(self.width)
|
||||
.size(
|
||||
self.text_size
|
||||
.unwrap_or_else(|| renderer.default_size()),
|
||||
layout::next_to_each_other(
|
||||
&limits.width(self.width),
|
||||
self.spacing,
|
||||
|_| layout::Node::new(Size::new(self.size, self.size)),
|
||||
|limits| {
|
||||
let state = tree
|
||||
.state
|
||||
.downcast_ref::<widget::text::State<Renderer::Paragraph>>();
|
||||
|
||||
widget::text::layout(
|
||||
state,
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
Length::Shrink,
|
||||
&self.label,
|
||||
self.text_line_height,
|
||||
self.text_size,
|
||||
self.font,
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Top,
|
||||
self.text_shaping,
|
||||
)
|
||||
.line_height(self.text_line_height)
|
||||
.shaping(self.text_shaping),
|
||||
},
|
||||
)
|
||||
.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -244,7 +260,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -283,24 +299,23 @@ where
|
|||
line_height,
|
||||
shaping,
|
||||
} = &self.icon;
|
||||
let size = size.unwrap_or(bounds.height * 0.7);
|
||||
let size = size.unwrap_or(Pixels(bounds.height * 0.7));
|
||||
|
||||
if self.is_checked {
|
||||
renderer.fill_text(text::Text {
|
||||
renderer.fill_text(
|
||||
text::Text {
|
||||
content: &code_point.to_string(),
|
||||
font: *font,
|
||||
size,
|
||||
line_height: *line_height,
|
||||
bounds: Rectangle {
|
||||
x: bounds.center_x(),
|
||||
y: bounds.center_y(),
|
||||
..bounds
|
||||
},
|
||||
color: custom_style.icon_color,
|
||||
bounds: bounds.size(),
|
||||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: *shaping,
|
||||
});
|
||||
},
|
||||
bounds.center(),
|
||||
custom_style.icon_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,16 +326,10 @@ where
|
|||
renderer,
|
||||
style,
|
||||
label_layout,
|
||||
&self.label,
|
||||
self.text_size,
|
||||
self.text_line_height,
|
||||
self.font,
|
||||
tree.state.downcast_ref(),
|
||||
crate::text::Appearance {
|
||||
color: custom_style.text_color,
|
||||
},
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Center,
|
||||
self.text_shaping,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -348,7 +357,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>,
|
||||
/// The line height of the icon.
|
||||
pub line_height: text::LineHeight,
|
||||
/// The shaping strategy of the icon.
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
@ -138,6 +139,7 @@ where
|
|||
self.spacing,
|
||||
self.align_items,
|
||||
&self.children,
|
||||
&tree.children,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,11 +144,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Returns whether the [`ComboBox`] is currently focused or not.
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.state.is_focused()
|
||||
}
|
||||
|
||||
/// Sets the text sixe of the [`ComboBox`].
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.text_input = self.text_input.size(size);
|
||||
|
|
@ -179,7 +174,6 @@ pub struct State<T>(RefCell<Inner<T>>);
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Inner<T> {
|
||||
text_input: text_input::State,
|
||||
value: String,
|
||||
options: Vec<T>,
|
||||
option_matchers: Vec<String>,
|
||||
|
|
@ -216,7 +210,6 @@ where
|
|||
);
|
||||
|
||||
Self(RefCell::new(Inner {
|
||||
text_input: text_input::State::new(),
|
||||
value,
|
||||
options,
|
||||
option_matchers,
|
||||
|
|
@ -224,51 +217,12 @@ where
|
|||
}))
|
||||
}
|
||||
|
||||
/// Focuses the [`ComboBox`].
|
||||
pub fn focused(self) -> Self {
|
||||
self.focus();
|
||||
self
|
||||
}
|
||||
|
||||
/// Focuses the [`ComboBox`].
|
||||
pub fn focus(&self) {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
inner.text_input.focus();
|
||||
}
|
||||
|
||||
/// Unfocuses the [`ComboBox`].
|
||||
pub fn unfocus(&self) {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
inner.text_input.unfocus();
|
||||
}
|
||||
|
||||
/// Returns whether the [`ComboBox`] is currently focused or not.
|
||||
pub fn is_focused(&self) -> bool {
|
||||
let inner = self.0.borrow();
|
||||
|
||||
inner.text_input.is_focused()
|
||||
}
|
||||
|
||||
fn value(&self) -> String {
|
||||
let inner = self.0.borrow();
|
||||
|
||||
inner.value.clone()
|
||||
}
|
||||
|
||||
fn text_input_tree(&self) -> widget::Tree {
|
||||
let inner = self.0.borrow();
|
||||
|
||||
inner.text_input_tree()
|
||||
}
|
||||
|
||||
fn update_text_input(&self, tree: widget::Tree) {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
|
||||
inner.update_text_input(tree)
|
||||
}
|
||||
|
||||
fn with_inner<O>(&self, f: impl FnOnce(&Inner<T>) -> O) -> O {
|
||||
let inner = self.0.borrow();
|
||||
|
||||
|
|
@ -288,21 +242,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Inner<T> {
|
||||
fn text_input_tree(&self) -> widget::Tree {
|
||||
widget::Tree {
|
||||
tag: widget::tree::Tag::of::<text_input::State>(),
|
||||
state: widget::tree::State::new(self.text_input.clone()),
|
||||
children: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn update_text_input(&mut self, tree: widget::Tree) {
|
||||
self.text_input =
|
||||
tree.state.downcast_ref::<text_input::State>().clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Filtered<T>
|
||||
where
|
||||
T: Clone,
|
||||
|
|
@ -366,10 +305,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.text_input.layout(renderer, limits)
|
||||
self.text_input.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn tag(&self) -> widget::tree::Tag {
|
||||
|
|
@ -385,6 +325,10 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _>)]
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut widget::Tree,
|
||||
|
|
@ -398,7 +342,13 @@ where
|
|||
) -> event::Status {
|
||||
let menu = tree.state.downcast_mut::<Menu<T>>();
|
||||
|
||||
let started_focused = self.state.is_focused();
|
||||
let started_focused = {
|
||||
let text_input_state = tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<text_input::State<Renderer::Paragraph>>();
|
||||
|
||||
text_input_state.is_focused()
|
||||
};
|
||||
// This is intended to check whether or not the message buffer was empty,
|
||||
// since `Shell` does not expose such functionality.
|
||||
let mut published_message_to_shell = false;
|
||||
|
|
@ -408,9 +358,8 @@ where
|
|||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
||||
// Provide it to the widget
|
||||
let mut tree = self.state.text_input_tree();
|
||||
let mut event_status = self.text_input.on_event(
|
||||
&mut tree,
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor,
|
||||
|
|
@ -419,7 +368,6 @@ where
|
|||
&mut local_shell,
|
||||
viewport,
|
||||
);
|
||||
self.state.update_text_input(tree);
|
||||
|
||||
// Then finally react to them here
|
||||
for message in local_messages {
|
||||
|
|
@ -450,7 +398,15 @@ where
|
|||
shell.invalidate_layout();
|
||||
}
|
||||
|
||||
if self.state.is_focused() {
|
||||
let is_focused = {
|
||||
let text_input_state = tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<text_input::State<Renderer::Paragraph>>();
|
||||
|
||||
text_input_state.is_focused()
|
||||
};
|
||||
|
||||
if is_focused {
|
||||
self.state.with_inner(|state| {
|
||||
if !started_focused {
|
||||
if let Some(on_option_hovered) = &mut self.on_option_hovered
|
||||
|
|
@ -589,9 +545,8 @@ where
|
|||
published_message_to_shell = true;
|
||||
|
||||
// Unfocus the input
|
||||
let mut tree = state.text_input_tree();
|
||||
let _ = self.text_input.on_event(
|
||||
&mut tree,
|
||||
&mut tree.children[0],
|
||||
Event::Mouse(mouse::Event::ButtonPressed(
|
||||
mouse::Button::Left,
|
||||
)),
|
||||
|
|
@ -602,21 +557,25 @@ where
|
|||
&mut Shell::new(&mut vec![]),
|
||||
viewport,
|
||||
);
|
||||
state.update_text_input(tree);
|
||||
}
|
||||
});
|
||||
|
||||
if started_focused
|
||||
&& !self.state.is_focused()
|
||||
&& !published_message_to_shell
|
||||
{
|
||||
let is_focused = {
|
||||
let text_input_state = tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<text_input::State<Renderer::Paragraph>>();
|
||||
|
||||
text_input_state.is_focused()
|
||||
};
|
||||
|
||||
if started_focused && !is_focused && !published_message_to_shell {
|
||||
if let Some(message) = self.on_close.take() {
|
||||
shell.publish(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Focus changed, invalidate widget tree to force a fresh `view`
|
||||
if started_focused != self.state.is_focused() {
|
||||
if started_focused != is_focused {
|
||||
shell.invalidate_widgets();
|
||||
}
|
||||
|
||||
|
|
@ -625,20 +584,24 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
tree: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let tree = self.state.text_input_tree();
|
||||
self.text_input
|
||||
.mouse_interaction(&tree, layout, cursor, viewport, renderer)
|
||||
self.text_input.mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
tree: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
@ -646,16 +609,28 @@ where
|
|||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let selection = if self.state.is_focused() || self.selection.is_empty()
|
||||
{
|
||||
let is_focused = {
|
||||
let text_input_state = tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<text_input::State<Renderer::Paragraph>>();
|
||||
|
||||
text_input_state.is_focused()
|
||||
};
|
||||
|
||||
let selection = if is_focused || self.selection.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&self.selection)
|
||||
};
|
||||
|
||||
let tree = self.state.text_input_tree();
|
||||
self.text_input
|
||||
.draw(&tree, renderer, theme, layout, cursor, selection);
|
||||
self.text_input.draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor,
|
||||
selection,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
|
|
@ -664,6 +639,15 @@ where
|
|||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let is_focused = {
|
||||
let text_input_state = tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<text_input::State<Renderer::Paragraph>>();
|
||||
|
||||
text_input_state.is_focused()
|
||||
};
|
||||
|
||||
if is_focused {
|
||||
let Menu {
|
||||
menu,
|
||||
filtered_options,
|
||||
|
|
@ -671,7 +655,6 @@ where
|
|||
..
|
||||
} = tree.state.downcast_mut::<Menu<T>>();
|
||||
|
||||
if self.state.is_focused() {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
self.state.sync_filtered_options(filtered_options);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use crate::core::layout;
|
|||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::widget::{self, Operation, Tree};
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::widget::{self, Operation};
|
||||
use crate::core::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels,
|
||||
Point, Rectangle, Shell, Size, Vector, Widget,
|
||||
|
|
@ -135,12 +136,20 @@ where
|
|||
Renderer: crate::core::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
self.content.as_widget().tag()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
self.content.as_widget().state()
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
self.content.as_widget().children()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
self.content.as_widget().diff(tree);
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -153,11 +162,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
|
|
@ -166,9 +175,7 @@ where
|
|||
self.padding,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
|renderer, limits| {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
},
|
||||
|limits| self.content.as_widget().layout(tree, renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +191,7 @@ where
|
|||
layout.bounds(),
|
||||
&mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
tree,
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
operation,
|
||||
|
|
@ -205,7 +212,7 @@ where
|
|||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
tree,
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor,
|
||||
|
|
@ -225,7 +232,7 @@ where
|
|||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
tree,
|
||||
layout.children().next().unwrap(),
|
||||
cursor,
|
||||
viewport,
|
||||
|
|
@ -248,7 +255,7 @@ where
|
|||
draw_background(renderer, &style, layout.bounds());
|
||||
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
tree,
|
||||
renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
|
|
@ -269,7 +276,7 @@ where
|
|||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
tree,
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
)
|
||||
|
|
@ -291,8 +298,7 @@ where
|
|||
}
|
||||
|
||||
/// Computes the layout of a [`Container`].
|
||||
pub fn layout<Renderer>(
|
||||
renderer: &Renderer,
|
||||
pub fn layout(
|
||||
limits: &layout::Limits,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -301,7 +307,7 @@ pub fn layout<Renderer>(
|
|||
padding: Padding,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||
layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
|
||||
) -> layout::Node {
|
||||
let limits = limits
|
||||
.loose()
|
||||
|
|
@ -310,7 +316,7 @@ pub fn layout<Renderer>(
|
|||
.width(width)
|
||||
.height(height);
|
||||
|
||||
let mut content = layout_content(renderer, &limits.pad(padding).loose());
|
||||
let mut content = layout_content(&limits.pad(padding).loose());
|
||||
let padding = padding.fit(content.size(), limits.max());
|
||||
let size = limits.pad(padding).resolve(content.size());
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -152,11 +152,12 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.with_element(|element| {
|
||||
element.as_widget().layout(renderer, limits)
|
||||
element.as_widget().layout(tree, renderer, limits)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -254,11 +254,12 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.with_element(|element| {
|
||||
element.as_widget().layout(renderer, limits)
|
||||
element.as_widget().layout(tree, renderer, limits)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@ impl<'a, Message, Renderer> Content<'a, Message, Renderer>
|
|||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn layout(&mut self, renderer: &Renderer) {
|
||||
fn layout(&mut self, tree: &Tree, renderer: &Renderer) {
|
||||
if self.layout.is_none() {
|
||||
self.layout =
|
||||
Some(self.element.as_widget().layout(
|
||||
self.layout = Some(self.element.as_widget().layout(
|
||||
tree,
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, self.size),
|
||||
));
|
||||
|
|
@ -104,7 +104,7 @@ where
|
|||
R: Deref<Target = Renderer>,
|
||||
{
|
||||
self.update(tree, layout.bounds().size(), view);
|
||||
self.layout(renderer.deref());
|
||||
self.layout(tree, renderer.deref());
|
||||
|
||||
let content_layout = Layout::with_offset(
|
||||
layout.position() - Point::ORIGIN,
|
||||
|
|
@ -144,6 +144,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
@ -285,7 +286,7 @@ where
|
|||
overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>,
|
||||
tree| {
|
||||
content.update(tree, layout.bounds().size(), &self.view);
|
||||
content.layout(renderer);
|
||||
content.layout(tree, renderer);
|
||||
|
||||
let Content {
|
||||
element,
|
||||
|
|
|
|||
|
|
@ -120,10 +120,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
self.content.as_widget().layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ where
|
|||
on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
|
||||
width: f32,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Option<Renderer::Font>,
|
||||
|
|
@ -85,7 +85,7 @@ where
|
|||
|
||||
/// Sets the text size of the [`Menu`].
|
||||
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(text_size.into().0);
|
||||
self.text_size = Some(text_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ where
|
|||
)
|
||||
.width(self.width);
|
||||
|
||||
let mut node = self.container.layout(renderer, &limits);
|
||||
let mut node = self.container.layout(self.state, renderer, &limits);
|
||||
|
||||
node.move_to(if space_below > space_above {
|
||||
position + Vector::new(0.0, self.target_height)
|
||||
|
|
@ -328,7 +328,7 @@ where
|
|||
on_selected: Box<dyn FnMut(T) -> Message + 'a>,
|
||||
on_option_hovered: Option<&'a dyn Fn(T) -> Message>,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Option<Renderer::Font>,
|
||||
|
|
@ -352,6 +352,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
@ -361,8 +362,7 @@ where
|
|||
let text_size =
|
||||
self.text_size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let text_line_height =
|
||||
self.text_line_height.to_absolute(Pixels(text_size));
|
||||
let text_line_height = self.text_line_height.to_absolute(text_size);
|
||||
|
||||
let size = {
|
||||
let intrinsic = Size::new(
|
||||
|
|
@ -407,9 +407,9 @@ where
|
|||
.text_size
|
||||
.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let option_height = f32::from(
|
||||
self.text_line_height.to_absolute(Pixels(text_size)),
|
||||
) + self.padding.vertical();
|
||||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(text_size))
|
||||
+ self.padding.vertical();
|
||||
|
||||
let new_hovered_option =
|
||||
(cursor_position.y / option_height) as usize;
|
||||
|
|
@ -436,9 +436,9 @@ where
|
|||
.text_size
|
||||
.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let option_height = f32::from(
|
||||
self.text_line_height.to_absolute(Pixels(text_size)),
|
||||
) + self.padding.vertical();
|
||||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(text_size))
|
||||
+ self.padding.vertical();
|
||||
|
||||
*self.hovered_option =
|
||||
Some((cursor_position.y / option_height) as usize);
|
||||
|
|
@ -490,7 +490,7 @@ where
|
|||
let text_size =
|
||||
self.text_size.unwrap_or_else(|| renderer.default_size());
|
||||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
||||
f32::from(self.text_line_height.to_absolute(text_size))
|
||||
+ self.padding.vertical();
|
||||
|
||||
let offset = viewport.y - bounds.y;
|
||||
|
|
@ -526,26 +526,24 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
renderer.fill_text(Text {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: &option.to_string(),
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + self.padding.left,
|
||||
y: bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
..bounds
|
||||
},
|
||||
bounds: Size::new(f32::INFINITY, bounds.height),
|
||||
size: text_size,
|
||||
line_height: self.text_line_height,
|
||||
font: self.font.unwrap_or_else(|| renderer.default_font()),
|
||||
color: if is_selected {
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: self.text_shaping,
|
||||
},
|
||||
Point::new(bounds.x + self.padding.left, bounds.center_y()),
|
||||
if is_selected {
|
||||
appearance.selected_text_color
|
||||
} else {
|
||||
appearance.text_color
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: self.text_shaping,
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,10 +275,12 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
tree,
|
||||
renderer,
|
||||
limits,
|
||||
self.contents.layout(),
|
||||
|
|
@ -286,7 +288,9 @@ where
|
|||
self.height,
|
||||
self.spacing,
|
||||
self.contents.iter(),
|
||||
|content, renderer, limits| content.layout(renderer, limits),
|
||||
|content, tree, renderer, limits| {
|
||||
content.layout(tree, renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -471,6 +475,7 @@ where
|
|||
|
||||
/// Calculates the [`Layout`] of a [`PaneGrid`].
|
||||
pub fn layout<Renderer, T>(
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
node: &Node,
|
||||
|
|
@ -478,19 +483,21 @@ pub fn layout<Renderer, T>(
|
|||
height: Length,
|
||||
spacing: f32,
|
||||
contents: impl Iterator<Item = (Pane, T)>,
|
||||
layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
|
||||
layout_content: impl Fn(T, &Tree, &Renderer, &layout::Limits) -> layout::Node,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(width).height(height);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
let regions = node.pane_regions(spacing, size);
|
||||
let children = contents
|
||||
.filter_map(|(pane, content)| {
|
||||
.zip(tree.children.iter())
|
||||
.filter_map(|((pane, content), tree)| {
|
||||
let region = regions.get(&pane)?;
|
||||
let size = Size::new(region.width, region.height);
|
||||
|
||||
let mut node = layout_content(
|
||||
content,
|
||||
tree,
|
||||
renderer,
|
||||
&layout::Limits::new(size, size),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -150,18 +150,23 @@ where
|
|||
|
||||
pub(crate) fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
if let Some(title_bar) = &self.title_bar {
|
||||
let max_size = limits.max();
|
||||
|
||||
let title_bar_layout = title_bar
|
||||
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
|
||||
let title_bar_layout = title_bar.layout(
|
||||
&tree.children[1],
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, max_size),
|
||||
);
|
||||
|
||||
let title_bar_size = title_bar_layout.size();
|
||||
|
||||
let mut body_layout = self.body.as_widget().layout(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
&layout::Limits::new(
|
||||
Size::ZERO,
|
||||
|
|
@ -179,7 +184,9 @@ where
|
|||
vec![title_bar_layout, body_layout],
|
||||
)
|
||||
} else {
|
||||
self.body.as_widget().layout(renderer, limits)
|
||||
self.body
|
||||
.as_widget()
|
||||
.layout(&tree.children[0], renderer, limits)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -213,23 +213,27 @@ where
|
|||
|
||||
pub(crate) fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.pad(self.padding);
|
||||
let max_size = limits.max();
|
||||
|
||||
let title_layout = self
|
||||
.content
|
||||
.as_widget()
|
||||
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
|
||||
let title_layout = self.content.as_widget().layout(
|
||||
&tree.children[0],
|
||||
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 mut controls_layout = controls.as_widget().layout(
|
||||
&tree.children[1],
|
||||
renderer,
|
||||
&layout::Limits::new(Size::ZERO, max_size),
|
||||
);
|
||||
|
||||
let controls_size = controls_layout.size();
|
||||
let space_before_controls = max_size.width - controls_size.width;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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
|
||||
},
|
||||
bounds: Size::new(
|
||||
bounds.width,
|
||||
f32::from(line_height.to_absolute(size)),
|
||||
),
|
||||
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 {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: label,
|
||||
size: text_size,
|
||||
line_height: text_line_height,
|
||||
font,
|
||||
color: if is_selected {
|
||||
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,
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer<Theme>,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use crate::core::mouse;
|
|||
use crate::core::renderer;
|
||||
use crate::core::text;
|
||||
use crate::core::touch;
|
||||
use crate::core::widget::Tree;
|
||||
use crate::core::widget;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle,
|
||||
Shell, Widget,
|
||||
Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, Shell, Size,
|
||||
Widget,
|
||||
};
|
||||
use crate::{Row, Text};
|
||||
|
||||
pub use iced_style::radio::{Appearance, StyleSheet};
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ where
|
|||
width: Length,
|
||||
size: f32,
|
||||
spacing: f32,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_shaping: text::Shaping,
|
||||
font: Option<Renderer::Font>,
|
||||
|
|
@ -152,7 +152,7 @@ where
|
|||
|
||||
/// Sets the text size of the [`Radio`] button.
|
||||
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(text_size.into().0);
|
||||
self.text_size = Some(text_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +193,14 @@ where
|
|||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet + crate::text::StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<widget::text::State<Renderer::Paragraph>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(widget::text::State::<Renderer::Paragraph>::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -203,25 +211,35 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
Row::<(), Renderer>::new()
|
||||
.width(self.width)
|
||||
.spacing(self.spacing)
|
||||
.align_items(Alignment::Center)
|
||||
.push(Row::new().width(self.size).height(self.size))
|
||||
.push(
|
||||
Text::new(&self.label)
|
||||
.width(self.width)
|
||||
.size(
|
||||
self.text_size
|
||||
.unwrap_or_else(|| renderer.default_size()),
|
||||
layout::next_to_each_other(
|
||||
&limits.width(self.width),
|
||||
self.spacing,
|
||||
|_| layout::Node::new(Size::new(self.size, self.size)),
|
||||
|limits| {
|
||||
let state = tree
|
||||
.state
|
||||
.downcast_ref::<widget::text::State<Renderer::Paragraph>>();
|
||||
|
||||
widget::text::layout(
|
||||
state,
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
Length::Shrink,
|
||||
&self.label,
|
||||
self.text_line_height,
|
||||
self.text_size,
|
||||
self.font,
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Top,
|
||||
self.text_shaping,
|
||||
)
|
||||
.line_height(self.text_line_height)
|
||||
.shaping(self.text_shaping),
|
||||
},
|
||||
)
|
||||
.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -267,7 +285,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -327,16 +345,10 @@ where
|
|||
renderer,
|
||||
style,
|
||||
label_layout,
|
||||
&self.label,
|
||||
self.text_size,
|
||||
self.text_line_height,
|
||||
self.font,
|
||||
tree.state.downcast_ref(),
|
||||
crate::text::Appearance {
|
||||
color: custom_style.text_color,
|
||||
},
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Center,
|
||||
self.text_shaping,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
@ -127,6 +128,7 @@ where
|
|||
self.spacing,
|
||||
self.align_items,
|
||||
&self.children,
|
||||
&tree.children,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
@ -240,7 +241,11 @@ where
|
|||
self.height,
|
||||
&self.direction,
|
||||
|renderer, limits| {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
self.content.as_widget().layout(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
limits,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::core::keyboard;
|
|||
use crate::core::layout;
|
||||
use crate::core::mouse::{self, click};
|
||||
use crate::core::renderer;
|
||||
use crate::core::text::{self, Text};
|
||||
use crate::core::text::{self, Paragraph as _, Text};
|
||||
use crate::core::time::{Duration, Instant};
|
||||
use crate::core::touch;
|
||||
use crate::core::widget;
|
||||
|
|
@ -30,6 +30,8 @@ use crate::core::{
|
|||
};
|
||||
use crate::runtime::Command;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub use iced_style::text_input::{Appearance, StyleSheet};
|
||||
|
||||
/// A field that can be filled with text.
|
||||
|
|
@ -67,7 +69,7 @@ where
|
|||
font: Option<Renderer::Font>,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
size: Option<Pixels>,
|
||||
line_height: text::LineHeight,
|
||||
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||
|
|
@ -178,7 +180,7 @@ where
|
|||
|
||||
/// Sets the text size of the [`TextInput`].
|
||||
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
||||
self.size = Some(size.into().0);
|
||||
self.size = Some(size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -218,12 +220,8 @@ where
|
|||
theme,
|
||||
layout,
|
||||
cursor,
|
||||
tree.state.downcast_ref::<State>(),
|
||||
tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
|
||||
value.unwrap_or(&self.value),
|
||||
&self.placeholder,
|
||||
self.size,
|
||||
self.line_height,
|
||||
self.font,
|
||||
self.on_input.is_none(),
|
||||
self.is_secure,
|
||||
self.icon.as_ref(),
|
||||
|
|
@ -240,15 +238,15 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
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 diff(&self, tree: &mut Tree) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
|
||||
// Unfocus text input if it becomes disabled
|
||||
if self.on_input.is_none() {
|
||||
|
|
@ -269,6 +267,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
@ -278,8 +277,13 @@ where
|
|||
self.width,
|
||||
self.padding,
|
||||
self.size,
|
||||
self.font,
|
||||
self.line_height,
|
||||
self.icon.as_ref(),
|
||||
tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
|
||||
&self.value,
|
||||
&self.placeholder,
|
||||
self.is_secure,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +294,7 @@ where
|
|||
_renderer: &Renderer,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||
|
||||
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
|
||||
operation.text_input(state, self.id.as_ref().map(|id| &id.0));
|
||||
|
|
@ -322,7 +326,7 @@ where
|
|||
self.on_input.as_deref(),
|
||||
self.on_paste.as_deref(),
|
||||
&self.on_submit,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
|| tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -341,12 +345,8 @@ where
|
|||
theme,
|
||||
layout,
|
||||
cursor,
|
||||
tree.state.downcast_ref::<State>(),
|
||||
tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
|
||||
&self.value,
|
||||
&self.placeholder,
|
||||
self.size,
|
||||
self.line_height,
|
||||
self.font,
|
||||
self.on_input.is_none(),
|
||||
self.is_secure,
|
||||
self.icon.as_ref(),
|
||||
|
|
@ -388,7 +388,7 @@ pub struct Icon<Font> {
|
|||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// The font size of the content.
|
||||
pub size: Option<f32>,
|
||||
pub size: Option<Pixels>,
|
||||
/// The spacing between the [`Icon`] and the text in a [`TextInput`].
|
||||
pub spacing: f32,
|
||||
/// The side of a [`TextInput`] where to display the [`Icon`].
|
||||
|
|
@ -465,30 +465,66 @@ pub fn layout<Renderer>(
|
|||
limits: &layout::Limits,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
size: Option<Pixels>,
|
||||
font: Option<Renderer::Font>,
|
||||
line_height: text::LineHeight,
|
||||
icon: Option<&Icon<Renderer::Font>>,
|
||||
state: &State<Renderer::Paragraph>,
|
||||
value: &Value,
|
||||
placeholder: &str,
|
||||
is_secure: bool,
|
||||
) -> layout::Node
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let padding = padding.fit(Size::ZERO, limits.max());
|
||||
let limits = limits
|
||||
.width(width)
|
||||
.pad(padding)
|
||||
.height(line_height.to_absolute(Pixels(text_size)));
|
||||
.height(line_height.to_absolute(text_size));
|
||||
|
||||
let text_bounds = limits.resolve(Size::ZERO);
|
||||
|
||||
if let Some(icon) = icon {
|
||||
let icon_width = renderer.measure_width(
|
||||
&icon.code_point.to_string(),
|
||||
icon.size.unwrap_or_else(|| renderer.default_size()),
|
||||
icon.font,
|
||||
text::Shaping::Advanced,
|
||||
let placeholder_text = Text {
|
||||
font,
|
||||
line_height,
|
||||
content: placeholder,
|
||||
bounds: Size::new(f32::INFINITY, text_bounds.height),
|
||||
size: text_size,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
};
|
||||
|
||||
renderer.update_paragraph(
|
||||
&mut state.placeholder_paragraph.borrow_mut(),
|
||||
placeholder_text,
|
||||
);
|
||||
|
||||
if is_secure {
|
||||
renderer.update_paragraph(
|
||||
&mut state.paragraph.borrow_mut(),
|
||||
Text {
|
||||
content: &value.secure().to_string(),
|
||||
..placeholder_text
|
||||
},
|
||||
);
|
||||
} else {
|
||||
renderer.update_paragraph(
|
||||
&mut state.paragraph.borrow_mut(),
|
||||
Text {
|
||||
content: &value.to_string(),
|
||||
..placeholder_text
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(icon) = icon {
|
||||
let icon_width = 0.0; // TODO
|
||||
|
||||
let mut text_node = layout::Node::new(
|
||||
text_bounds - Size::new(icon_width + icon.spacing, 0.0),
|
||||
);
|
||||
|
|
@ -537,19 +573,31 @@ pub fn update<'a, Message, Renderer>(
|
|||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
value: &mut Value,
|
||||
size: Option<f32>,
|
||||
size: Option<Pixels>,
|
||||
line_height: text::LineHeight,
|
||||
font: Option<Renderer::Font>,
|
||||
is_secure: bool,
|
||||
on_input: Option<&dyn Fn(String) -> Message>,
|
||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||
on_submit: &Option<Message>,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
state: impl FnOnce() -> &'a mut State<Renderer::Paragraph>,
|
||||
) -> event::Status
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let update_cache = |state, value| {
|
||||
replace_paragraph(
|
||||
renderer,
|
||||
state,
|
||||
layout,
|
||||
value,
|
||||
font,
|
||||
size,
|
||||
line_height,
|
||||
)
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
|
|
@ -592,11 +640,7 @@ where
|
|||
};
|
||||
|
||||
find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
font,
|
||||
size,
|
||||
line_height,
|
||||
&value,
|
||||
state,
|
||||
target,
|
||||
|
|
@ -621,11 +665,7 @@ where
|
|||
state.cursor.select_all(value);
|
||||
} else {
|
||||
let position = find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
font,
|
||||
size,
|
||||
line_height,
|
||||
value,
|
||||
state,
|
||||
target,
|
||||
|
|
@ -671,11 +711,7 @@ where
|
|||
};
|
||||
|
||||
let position = find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
font,
|
||||
size,
|
||||
line_height,
|
||||
&value,
|
||||
state,
|
||||
target,
|
||||
|
|
@ -710,6 +746,8 @@ where
|
|||
|
||||
focus.updated_at = Instant::now();
|
||||
|
||||
update_cache(state, value);
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
|
@ -749,6 +787,8 @@ where
|
|||
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
|
||||
update_cache(state, value);
|
||||
}
|
||||
keyboard::KeyCode::Delete => {
|
||||
if platform::is_jump_modifier_pressed(modifiers)
|
||||
|
|
@ -769,6 +809,8 @@ where
|
|||
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
|
||||
update_cache(state, value);
|
||||
}
|
||||
keyboard::KeyCode::Left => {
|
||||
if platform::is_jump_modifier_pressed(modifiers)
|
||||
|
|
@ -844,6 +886,8 @@ where
|
|||
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
|
||||
update_cache(state, value);
|
||||
}
|
||||
keyboard::KeyCode::V => {
|
||||
if state.keyboard_modifiers.command()
|
||||
|
|
@ -876,6 +920,8 @@ where
|
|||
shell.publish(message);
|
||||
|
||||
state.is_pasting = Some(content);
|
||||
|
||||
update_cache(state, value);
|
||||
} else {
|
||||
state.is_pasting = None;
|
||||
}
|
||||
|
|
@ -979,12 +1025,8 @@ pub fn draw<Renderer>(
|
|||
theme: &Renderer::Theme,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
state: &State,
|
||||
state: &State<Renderer::Paragraph>,
|
||||
value: &Value,
|
||||
placeholder: &str,
|
||||
size: Option<f32>,
|
||||
line_height: text::LineHeight,
|
||||
font: Option<Renderer::Font>,
|
||||
is_disabled: bool,
|
||||
is_secure: bool,
|
||||
icon: Option<&Icon<Renderer::Font>>,
|
||||
|
|
@ -1023,28 +1065,14 @@ pub fn draw<Renderer>(
|
|||
appearance.background,
|
||||
);
|
||||
|
||||
if let Some(icon) = icon {
|
||||
let icon_layout = children_layout.next().unwrap();
|
||||
if let Some(_icon) = icon {
|
||||
let _icon_layout = children_layout.next().unwrap();
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: &icon.code_point.to_string(),
|
||||
size: icon.size.unwrap_or_else(|| renderer.default_size()),
|
||||
line_height: text::LineHeight::default(),
|
||||
font: icon.font,
|
||||
color: appearance.icon_color,
|
||||
bounds: Rectangle {
|
||||
y: text_bounds.center_y(),
|
||||
..icon_layout.bounds()
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
});
|
||||
// TODO
|
||||
}
|
||||
|
||||
let text = value.to_string();
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
let paragraph = &state.paragraph.borrow() as &Renderer::Paragraph;
|
||||
|
||||
let (cursor, offset) = if let Some(focus) = state
|
||||
.is_focused
|
||||
|
|
@ -1055,12 +1083,9 @@ pub fn draw<Renderer>(
|
|||
cursor::State::Index(position) => {
|
||||
let (text_value_width, offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
paragraph,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
position,
|
||||
font,
|
||||
);
|
||||
|
||||
let is_cursor_visible = ((focus.now - focus.updated_at)
|
||||
|
|
@ -1096,22 +1121,16 @@ pub fn draw<Renderer>(
|
|||
|
||||
let (left_position, left_offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
paragraph,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
left,
|
||||
font,
|
||||
);
|
||||
|
||||
let (right_position, right_offset) =
|
||||
measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
paragraph,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
right,
|
||||
font,
|
||||
);
|
||||
|
||||
let width = right_position - left_position;
|
||||
|
|
@ -1143,12 +1162,7 @@ pub fn draw<Renderer>(
|
|||
(None, 0.0)
|
||||
};
|
||||
|
||||
let text_width = renderer.measure_width(
|
||||
if text.is_empty() { placeholder } else { &text },
|
||||
size,
|
||||
font,
|
||||
text::Shaping::Advanced,
|
||||
);
|
||||
let text_width = paragraph.min_width();
|
||||
|
||||
let render = |renderer: &mut Renderer| {
|
||||
if let Some((cursor, color)) = cursor {
|
||||
|
|
@ -1157,27 +1171,23 @@ pub fn draw<Renderer>(
|
|||
renderer.with_translation(Vector::ZERO, |_| {});
|
||||
}
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: if text.is_empty() { placeholder } else { &text },
|
||||
color: if text.is_empty() {
|
||||
let placeholder_paragraph = state.placeholder_paragraph.borrow();
|
||||
|
||||
renderer.fill_paragraph(
|
||||
if text.is_empty() {
|
||||
&placeholder_paragraph
|
||||
} else {
|
||||
paragraph
|
||||
},
|
||||
Point::new(text_bounds.x, text_bounds.center_y()),
|
||||
if text.is_empty() {
|
||||
theme.placeholder_color(style)
|
||||
} else if is_disabled {
|
||||
theme.disabled_color(style)
|
||||
} else {
|
||||
theme.value_color(style)
|
||||
},
|
||||
font,
|
||||
bounds: Rectangle {
|
||||
y: text_bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
..text_bounds
|
||||
},
|
||||
size,
|
||||
line_height,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
shaping: text::Shaping::Advanced,
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
if text_width > text_bounds.width {
|
||||
|
|
@ -1208,7 +1218,9 @@ pub fn mouse_interaction(
|
|||
|
||||
/// The state of a [`TextInput`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct State {
|
||||
pub struct State<P: text::Paragraph> {
|
||||
paragraph: RefCell<P>,
|
||||
placeholder_paragraph: RefCell<P>,
|
||||
is_focused: Option<Focus>,
|
||||
is_dragging: bool,
|
||||
is_pasting: Option<Value>,
|
||||
|
|
@ -1225,7 +1237,7 @@ struct Focus {
|
|||
is_window_focused: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl<P: text::Paragraph> State<P> {
|
||||
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
|
|
@ -1234,6 +1246,8 @@ impl State {
|
|||
/// Creates a new [`State`], representing a focused [`TextInput`].
|
||||
pub fn focused() -> Self {
|
||||
Self {
|
||||
paragraph: RefCell::new(P::default()),
|
||||
placeholder_paragraph: RefCell::new(P::default()),
|
||||
is_focused: None,
|
||||
is_dragging: false,
|
||||
is_pasting: None,
|
||||
|
|
@ -1292,7 +1306,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
impl operation::Focusable for State {
|
||||
impl<P: text::Paragraph> operation::Focusable for State<P> {
|
||||
fn is_focused(&self) -> bool {
|
||||
State::is_focused(self)
|
||||
}
|
||||
|
|
@ -1306,7 +1320,7 @@ impl operation::Focusable for State {
|
|||
}
|
||||
}
|
||||
|
||||
impl operation::TextInput for State {
|
||||
impl<P: text::Paragraph> operation::TextInput for State<P> {
|
||||
fn move_cursor_to_front(&mut self) {
|
||||
State::move_cursor_to_front(self)
|
||||
}
|
||||
|
|
@ -1336,17 +1350,11 @@ mod platform {
|
|||
}
|
||||
}
|
||||
|
||||
fn offset<Renderer>(
|
||||
renderer: &Renderer,
|
||||
fn offset<P: text::Paragraph>(
|
||||
text_bounds: Rectangle,
|
||||
font: Renderer::Font,
|
||||
size: f32,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
) -> f32
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
state: &State<P>,
|
||||
) -> f32 {
|
||||
if state.is_focused() {
|
||||
let cursor = state.cursor();
|
||||
|
||||
|
|
@ -1356,12 +1364,9 @@ where
|
|||
};
|
||||
|
||||
let (_, offset) = measure_cursor_and_scroll_offset(
|
||||
renderer,
|
||||
&state.paragraph.borrow() as &P,
|
||||
text_bounds,
|
||||
value,
|
||||
size,
|
||||
focus_position,
|
||||
font,
|
||||
);
|
||||
|
||||
offset
|
||||
|
|
@ -1370,63 +1375,35 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn measure_cursor_and_scroll_offset<Renderer>(
|
||||
renderer: &Renderer,
|
||||
fn measure_cursor_and_scroll_offset(
|
||||
paragraph: &impl text::Paragraph,
|
||||
text_bounds: Rectangle,
|
||||
value: &Value,
|
||||
size: f32,
|
||||
cursor_index: usize,
|
||||
font: Renderer::Font,
|
||||
) -> (f32, f32)
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let text_before_cursor = value.until(cursor_index).to_string();
|
||||
) -> (f32, f32) {
|
||||
let grapheme_position = paragraph
|
||||
.grapheme_position(0, cursor_index)
|
||||
.unwrap_or(Point::ORIGIN);
|
||||
|
||||
let text_value_width = renderer.measure_width(
|
||||
&text_before_cursor,
|
||||
size,
|
||||
font,
|
||||
text::Shaping::Advanced,
|
||||
);
|
||||
let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);
|
||||
|
||||
let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0);
|
||||
|
||||
(text_value_width, offset)
|
||||
(grapheme_position.x, offset)
|
||||
}
|
||||
|
||||
/// Computes the position of the text cursor at the given X coordinate of
|
||||
/// a [`TextInput`].
|
||||
fn find_cursor_position<Renderer>(
|
||||
renderer: &Renderer,
|
||||
fn find_cursor_position<P: text::Paragraph>(
|
||||
text_bounds: Rectangle,
|
||||
font: Option<Renderer::Font>,
|
||||
size: Option<f32>,
|
||||
line_height: text::LineHeight,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
state: &State<P>,
|
||||
x: f32,
|
||||
) -> Option<usize>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let offset = offset(renderer, text_bounds, font, size, value, state);
|
||||
) -> Option<usize> {
|
||||
let offset = offset(text_bounds, value, state);
|
||||
let value = value.to_string();
|
||||
|
||||
let char_offset = renderer
|
||||
.hit_test(
|
||||
&value,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
Size::INFINITY,
|
||||
text::Shaping::Advanced,
|
||||
Point::new(x + offset, text_bounds.height / 2.0),
|
||||
true,
|
||||
)
|
||||
let char_offset = state
|
||||
.paragraph
|
||||
.borrow()
|
||||
.hit_test(Point::new(x + offset, text_bounds.height / 2.0))
|
||||
.map(text::Hit::cursor)?;
|
||||
|
||||
Some(
|
||||
|
|
@ -1438,4 +1415,33 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn replace_paragraph<Renderer>(
|
||||
renderer: &Renderer,
|
||||
state: &mut State<Renderer::Paragraph>,
|
||||
layout: Layout<'_>,
|
||||
value: &Value,
|
||||
font: Option<Renderer::Font>,
|
||||
text_size: Option<Pixels>,
|
||||
line_height: text::LineHeight,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let text_size = text_size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let mut children_layout = layout.children();
|
||||
let text_bounds = children_layout.next().unwrap().bounds();
|
||||
|
||||
*state.paragraph.get_mut() = renderer.create_paragraph(Text {
|
||||
font,
|
||||
line_height,
|
||||
content: &value.to_string(),
|
||||
bounds: Size::new(f32::INFINITY, text_bounds.height),
|
||||
size: text_size,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Advanced,
|
||||
});
|
||||
}
|
||||
|
||||
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use crate::core::mouse;
|
|||
use crate::core::renderer;
|
||||
use crate::core::text;
|
||||
use crate::core::touch;
|
||||
use crate::core::widget::Tree;
|
||||
use crate::core::widget;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle,
|
||||
Shell, Widget,
|
||||
Clipboard, Element, Event, Layout, Length, Pixels, Rectangle, Shell, Size,
|
||||
Widget,
|
||||
};
|
||||
use crate::{Row, Text};
|
||||
|
||||
pub use crate::style::toggler::{Appearance, StyleSheet};
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ where
|
|||
label: Option<String>,
|
||||
width: Length,
|
||||
size: f32,
|
||||
text_size: Option<f32>,
|
||||
text_size: Option<Pixels>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_alignment: alignment::Horizontal,
|
||||
text_shaping: text::Shaping,
|
||||
|
|
@ -105,7 +105,7 @@ where
|
|||
|
||||
/// Sets the text size o the [`Toggler`].
|
||||
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(text_size.into().0);
|
||||
self.text_size = Some(text_size.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +160,14 @@ where
|
|||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet + crate::text::StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<widget::text::State<Renderer::Paragraph>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(widget::text::State::<Renderer::Paragraph>::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -170,32 +178,41 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let mut row = Row::<(), Renderer>::new()
|
||||
.width(self.width)
|
||||
.spacing(self.spacing)
|
||||
.align_items(Alignment::Center);
|
||||
let limits = limits.width(self.width);
|
||||
|
||||
if let Some(label) = &self.label {
|
||||
row = row.push(
|
||||
Text::new(label)
|
||||
.horizontal_alignment(self.text_alignment)
|
||||
.font(self.font.unwrap_or_else(|| renderer.default_font()))
|
||||
.width(self.width)
|
||||
.size(
|
||||
self.text_size
|
||||
.unwrap_or_else(|| renderer.default_size()),
|
||||
layout::next_to_each_other(
|
||||
&limits,
|
||||
self.spacing,
|
||||
|_| layout::Node::new(Size::new(2.0 * self.size, self.size)),
|
||||
|limits| {
|
||||
if let Some(label) = self.label.as_deref() {
|
||||
let state = tree
|
||||
.state
|
||||
.downcast_ref::<widget::text::State<Renderer::Paragraph>>();
|
||||
|
||||
widget::text::layout(
|
||||
state,
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
Length::Shrink,
|
||||
label,
|
||||
self.text_line_height,
|
||||
self.text_size,
|
||||
self.font,
|
||||
self.text_alignment,
|
||||
alignment::Vertical::Top,
|
||||
self.text_shaping,
|
||||
)
|
||||
.line_height(self.text_line_height)
|
||||
.shaping(self.text_shaping),
|
||||
);
|
||||
} else {
|
||||
layout::Node::new(Size::ZERO)
|
||||
}
|
||||
|
||||
row = row.push(Row::new().width(2.0 * self.size).height(self.size));
|
||||
|
||||
row.layout(renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -243,7 +260,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -259,28 +276,21 @@ where
|
|||
const SPACE_RATIO: f32 = 0.05;
|
||||
|
||||
let mut children = layout.children();
|
||||
let toggler_layout = children.next().unwrap();
|
||||
|
||||
if let Some(label) = &self.label {
|
||||
if self.label.is_some() {
|
||||
let label_layout = children.next().unwrap();
|
||||
|
||||
crate::text::draw(
|
||||
renderer,
|
||||
style,
|
||||
label_layout,
|
||||
label,
|
||||
self.text_size,
|
||||
self.text_line_height,
|
||||
self.font,
|
||||
tree.state.downcast_ref(),
|
||||
Default::default(),
|
||||
self.text_alignment,
|
||||
alignment::Vertical::Center,
|
||||
self.text_shaping,
|
||||
);
|
||||
}
|
||||
|
||||
let toggler_layout = children.next().unwrap();
|
||||
let bounds = toggler_layout.bounds();
|
||||
|
||||
let is_mouse_over = cursor.is_over(layout.bounds());
|
||||
|
||||
let style = if is_mouse_over {
|
||||
|
|
|
|||
|
|
@ -107,11 +107,14 @@ where
|
|||
Renderer::Theme: container::StyleSheet + crate::text::StyleSheet,
|
||||
{
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
vec![widget::Tree::new(&self.content)]
|
||||
vec![
|
||||
widget::Tree::new(&self.content),
|
||||
widget::Tree::new(&self.tooltip as &dyn Widget<Message, _>),
|
||||
]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut widget::Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
tree.diff_children(&[self.content.as_widget(), &self.tooltip])
|
||||
}
|
||||
|
||||
fn state(&self) -> widget::tree::State {
|
||||
|
|
@ -132,10 +135,11 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
self.content.as_widget().layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -214,8 +218,10 @@ where
|
|||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let mut children = tree.children.iter_mut();
|
||||
|
||||
let content = self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
children.next().unwrap(),
|
||||
layout,
|
||||
renderer,
|
||||
);
|
||||
|
|
@ -225,6 +231,7 @@ where
|
|||
layout.position(),
|
||||
Box::new(Overlay {
|
||||
tooltip: &self.tooltip,
|
||||
state: children.next().unwrap(),
|
||||
cursor_position,
|
||||
content_bounds: layout.bounds(),
|
||||
snap_within_viewport: self.snap_within_viewport,
|
||||
|
|
@ -295,6 +302,7 @@ where
|
|||
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
tooltip: &'b Text<'a, Renderer>,
|
||||
state: &'b widget::Tree,
|
||||
cursor_position: Point,
|
||||
content_bounds: Rectangle,
|
||||
snap_within_viewport: bool,
|
||||
|
|
@ -320,6 +328,7 @@ where
|
|||
|
||||
let text_layout = Widget::<(), Renderer>::layout(
|
||||
self.tooltip,
|
||||
self.state,
|
||||
renderer,
|
||||
&layout::Limits::new(
|
||||
Size::ZERO,
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ where
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue