From 6bf709e03efaf9ee477a742a13feb8e9262c647d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 4 May 2025 03:54:42 +0200 Subject: [PATCH] Make anchoring explicit and improve reusability of text pipelines --- core/src/rectangle.rs | 29 +++++ core/src/renderer/null.rs | 24 ++++ core/src/text.rs | 44 +++++++ core/src/text/paragraph.rs | 59 ++++++--- core/src/widget/text.rs | 174 ++++++++++++-------------- graphics/src/text/paragraph.rs | 30 ++++- tiny_skia/src/text.rs | 74 ++++------- wgpu/src/text.rs | 216 +++++++++++++++------------------ widget/src/checkbox.rs | 24 ++-- widget/src/radio.rs | 24 ++-- widget/src/text/rich.rs | 2 +- widget/src/text_input.rs | 24 ++-- widget/src/toggler.rs | 24 ++-- 13 files changed, 423 insertions(+), 325 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 1984394c..0c4add05 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -1,3 +1,4 @@ +use crate::alignment; use crate::{Padding, Point, Radians, Size, Vector}; /// An axis-aligned rectangle. @@ -303,6 +304,34 @@ impl Rectangle { height: self.height * zoom, } } + + /// Returns the top-left position to render an object of the given [`Size`]. + /// inside the [`Rectangle`] that is anchored to the edge or corner + /// defined by the alignment arguments. + pub fn anchor( + &self, + size: Size, + align_x: impl Into, + align_y: impl Into, + ) -> Point { + let x = match align_x.into() { + alignment::Horizontal::Left => self.x, + alignment::Horizontal::Center => { + self.x + (self.width - size.width) / 2.0 + } + alignment::Horizontal::Right => self.x + self.width - size.width, + }; + + let y = match align_y.into() { + alignment::Vertical::Top => self.y, + alignment::Vertical::Center => { + self.y + (self.height - size.height) / 2.0 + } + alignment::Vertical::Bottom => self.y + self.height - size.height, + }; + + Point::new(x, y) + } } impl std::ops::Mul for Rectangle { diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 92fdd660..2251e527 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -88,6 +88,18 @@ impl text::Paragraph for () { text::Difference::None } + fn size(&self) -> Pixels { + Pixels(16.0) + } + + fn font(&self) -> Font { + Font::DEFAULT + } + + fn line_height(&self) -> text::LineHeight { + text::LineHeight::default() + } + fn align_x(&self) -> text::Alignment { text::Alignment::Default } @@ -96,10 +108,22 @@ impl text::Paragraph for () { alignment::Vertical::Top } + fn wrapping(&self) -> text::Wrapping { + text::Wrapping::default() + } + + fn shaping(&self) -> text::Shaping { + text::Shaping::default() + } + fn grapheme_position(&self, _line: usize, _index: usize) -> Option { None } + fn bounds(&self) -> Size { + Size::ZERO + } + fn min_bounds(&self) -> Size { Size::ZERO } diff --git a/core/src/text.rs b/core/src/text.rs index e2e75e58..0b51f244 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -46,6 +46,38 @@ pub struct Text { pub wrapping: Wrapping, } +impl Text +where + Font: Copy, +{ + /// Returns a new [`Text`] replacing only the content with the + /// given value. + pub fn with_content(&self, content: T) -> Text { + Text { + content, + bounds: self.bounds, + size: self.size, + line_height: self.line_height, + font: self.font, + align_x: self.align_x, + align_y: self.align_y, + shaping: self.shaping, + wrapping: self.wrapping, + } + } +} + +impl Text +where + Content: AsRef, + Font: Copy, +{ + /// Returns a borrowed version of [`Text`]. + pub fn as_ref(&self) -> Text<&str, Font> { + self.with_content(self.content.as_ref()) + } +} + /// The alignment of some text. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Alignment { @@ -85,6 +117,18 @@ impl From for Alignment { } } +impl From for alignment::Horizontal { + fn from(alignment: Alignment) -> Self { + match alignment { + Alignment::Default | Alignment::Left | Alignment::Justified => { + alignment::Horizontal::Left + } + Alignment::Center => alignment::Horizontal::Center, + Alignment::Right => alignment::Horizontal::Right, + } + } +} + /// The shaping strategy of some text. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Shaping { diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index 2b905dbc..ff859470 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -1,7 +1,9 @@ //! Draw paragraphs. use crate::alignment; -use crate::text::{Alignment, Difference, Hit, Span, Text}; -use crate::{Point, Rectangle, Size}; +use crate::text::{ + Alignment, Difference, Hit, LineHeight, Shaping, Span, Text, Wrapping, +}; +use crate::{Pixels, Point, Rectangle, Size}; /// A text paragraph. pub trait Paragraph: Sized + Default { @@ -23,12 +25,30 @@ pub trait Paragraph: Sized + Default { /// [`Difference`]. fn compare(&self, text: Text<(), Self::Font>) -> Difference; + /// Returns the text size of the [`Paragraph`] in [`Pixels`]. + fn size(&self) -> Pixels; + + /// Returns the font of the [`Paragraph`]. + fn font(&self) -> Self::Font; + + /// Returns the [`LineHeight`] of the [`Paragraph`]. + fn line_height(&self) -> LineHeight; + /// Returns the horizontal alignment of the [`Paragraph`]. fn align_x(&self) -> Alignment; /// Returns the vertical alignment of the [`Paragraph`]. fn align_y(&self) -> alignment::Vertical; + /// Returns the [`Wrapping`] strategy of the [`Paragraph`]> + fn wrapping(&self) -> Wrapping; + + /// Returns the [`Shaping`] strategy of the [`Paragraph`]> + fn shaping(&self) -> Shaping; + + /// Returns the availalbe bounds used to layout the [`Paragraph`]. + fn bounds(&self) -> Size; + /// Returns the minimum boundaries that can fit the contents of the /// [`Paragraph`]. fn min_bounds(&self) -> Size; @@ -69,12 +89,10 @@ pub struct Plain { impl Plain

{ /// Creates a new [`Plain`] paragraph. - pub fn new(text: Text<&str, P::Font>) -> Self { - let content = text.content.to_owned(); - + pub fn new(text: Text) -> Self { Self { - raw: P::with_text(text), - content, + raw: P::with_text(text.as_ref()), + content: text.content, } } @@ -88,17 +106,7 @@ impl Plain

{ return true; } - match self.raw.compare(Text { - content: (), - bounds: text.bounds, - size: text.size, - line_height: text.line_height, - font: text.font, - align_x: text.align_x, - align_y: text.align_y, - shaping: text.shaping, - wrapping: text.wrapping, - }) { + match self.raw.compare(text.with_content(())) { Difference::None => false, Difference::Bounds => { self.raw.resize(text.bounds); @@ -148,4 +156,19 @@ impl Plain

{ pub fn content(&self) -> &str { &self.content } + + /// Returns the [`Paragraph`] as a [`Text`] definition. + pub fn as_text(&self) -> Text<&str, P::Font> { + Text { + content: &self.content, + bounds: self.raw.bounds(), + size: self.raw.size(), + line_height: self.raw.line_height(), + font: self.raw.font(), + align_x: self.raw.align_x(), + align_y: self.raw.align_y(), + shaping: self.raw.shaping(), + wrapping: self.raw.wrapping(), + } + } } diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 4f99c4b7..b5ed9bf0 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -28,8 +28,7 @@ use crate::text; use crate::text::paragraph::{self, Paragraph}; use crate::widget::tree::{self, Tree}; use crate::{ - Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme, - Widget, + Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget, }; pub use text::{Alignment, LineHeight, Shaping, Wrapping}; @@ -63,15 +62,7 @@ where Renderer: text::Renderer, { fragment: text::Fragment<'a>, - size: Option, - line_height: LineHeight, - width: Length, - height: Length, - align_x: text::Alignment, - align_y: alignment::Vertical, - font: Option, - shaping: Shaping, - wrapping: Wrapping, + format: Format, class: Theme::Class<'a>, } @@ -84,28 +75,20 @@ where pub fn new(fragment: impl text::IntoFragment<'a>) -> Self { Text { fragment: fragment.into_fragment(), - size: None, - line_height: LineHeight::default(), - font: None, - width: Length::Shrink, - height: Length::Shrink, - align_x: text::Alignment::Default, - align_y: alignment::Vertical::Top, - shaping: Shaping::default(), - wrapping: Wrapping::default(), + format: Format::default(), class: Theme::default(), } } /// Sets the size of the [`Text`]. pub fn size(mut self, size: impl Into) -> Self { - self.size = Some(size.into()); + self.format.size = Some(size.into()); self } /// Sets the [`LineHeight`] of the [`Text`]. pub fn line_height(mut self, line_height: impl Into) -> Self { - self.line_height = line_height.into(); + self.format.line_height = line_height.into(); self } @@ -113,19 +96,19 @@ where /// /// [`Font`]: crate::text::Renderer::Font pub fn font(mut self, font: impl Into) -> Self { - self.font = Some(font.into()); + self.format.font = Some(font.into()); self } /// Sets the width of the [`Text`] boundaries. pub fn width(mut self, width: impl Into) -> Self { - self.width = width.into(); + self.format.width = width.into(); self } /// Sets the height of the [`Text`] boundaries. pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); + self.format.height = height.into(); self } @@ -137,7 +120,7 @@ where /// Sets the [`alignment::Horizontal`] of the [`Text`]. pub fn align_x(mut self, alignment: impl Into) -> Self { - self.align_x = alignment.into(); + self.format.align_x = alignment.into(); self } @@ -146,19 +129,19 @@ where mut self, alignment: impl Into, ) -> Self { - self.align_y = alignment.into(); + self.format.align_y = alignment.into(); self } /// Sets the [`Shaping`] strategy of the [`Text`]. pub fn shaping(mut self, shaping: Shaping) -> Self { - self.shaping = shaping; + self.format.shaping = shaping; self } /// Sets the [`Wrapping`] strategy of the [`Text`]. pub fn wrapping(mut self, wrapping: Wrapping) -> Self { - self.wrapping = wrapping; + self.format.wrapping = wrapping; self } @@ -200,8 +183,7 @@ where } /// The internal state of a [`Text`] widget. -#[derive(Debug, Default)] -pub struct State(pub paragraph::Plain

); +pub type State

= paragraph::Plain

; impl Widget for Text<'_, Theme, Renderer> @@ -214,15 +196,13 @@ where } fn state(&self) -> tree::State { - tree::State::new(State::( - paragraph::Plain::default(), - )) + tree::State::new(paragraph::Plain::::default()) } fn size(&self) -> Size { Size { - width: self.width, - height: self.height, + width: self.format.width, + height: self.format.height, } } @@ -236,16 +216,8 @@ where tree.state.downcast_mut::>(), renderer, limits, - self.width, - self.height, &self.fragment, - self.line_height, - self.size, - self.font, - self.align_x, - self.align_y, - self.shaping, - self.wrapping, + self.format, ) } @@ -262,7 +234,14 @@ where let state = tree.state.downcast_ref::>(); let style = theme.style(&self.class); - draw(renderer, defaults, layout, state.0.raw(), style, viewport); + draw( + renderer, + defaults, + layout.bounds(), + state.raw(), + style, + viewport, + ); } fn operate( @@ -276,43 +255,67 @@ where } } +/// The format of some [`Text`]. +/// +/// Check out the methods of the [`Text`] widget +/// to learn more about each field. +#[derive(Debug, Clone, Copy)] +#[allow(missing_docs)] +pub struct Format { + pub width: Length, + pub height: Length, + pub size: Option, + pub font: Option, + pub line_height: LineHeight, + pub align_x: text::Alignment, + pub align_y: alignment::Vertical, + pub shaping: Shaping, + pub wrapping: Wrapping, +} + +impl Default for Format { + fn default() -> Self { + Self { + size: None, + line_height: LineHeight::default(), + font: None, + width: Length::Shrink, + height: Length::Shrink, + align_x: text::Alignment::Default, + align_y: alignment::Vertical::Top, + shaping: Shaping::default(), + wrapping: Wrapping::default(), + } + } +} + /// Produces the [`layout::Node`] of a [`Text`] widget. pub fn layout( - state: &mut State, + paragraph: &mut paragraph::Plain, renderer: &Renderer, limits: &layout::Limits, - width: Length, - height: Length, content: &str, - line_height: LineHeight, - size: Option, - font: Option, - align_x: text::Alignment, - align_y: alignment::Vertical, - shaping: Shaping, - wrapping: Wrapping, + format: Format, ) -> layout::Node where Renderer: text::Renderer, { - layout::sized(limits, width, height, |limits| { + layout::sized(limits, format.width, format.height, |limits| { let bounds = limits.max(); - let size = size.unwrap_or_else(|| renderer.default_size()); - let font = font.unwrap_or_else(|| renderer.default_font()); - - let State(paragraph) = state; + let size = format.size.unwrap_or_else(|| renderer.default_size()); + let font = format.font.unwrap_or_else(|| renderer.default_font()); let _ = paragraph.update(text::Text { content, bounds, size, - line_height, + line_height: format.line_height, font, - align_x, - align_y, - shaping, - wrapping, + align_x: format.align_x, + align_y: format.align_y, + shaping: format.shaping, + wrapping: format.wrapping, }); paragraph.min_bounds() @@ -320,27 +323,21 @@ where } /// Draws text using the same logic as the [`Text`] widget. -/// -/// Specifically: -/// -/// * If no `size` is provided, the default text size of the `Renderer` will be -/// used. -/// * If no `color` is provided, the [`renderer::Style::text_color`] will be -/// used. -/// * The alignment attributes do not affect the position of the bounds of the -/// [`Layout`]. pub fn draw( renderer: &mut Renderer, style: &renderer::Style, - layout: Layout<'_>, + bounds: Rectangle, paragraph: &Renderer::Paragraph, appearance: Style, viewport: &Rectangle, ) where Renderer: text::Renderer, { - let bounds = layout.bounds(); - let anchor = anchor(bounds, paragraph.align_x(), paragraph.align_y()); + let anchor = bounds.anchor( + paragraph.min_bounds(), + paragraph.align_x(), + paragraph.align_y(), + ); renderer.fill_paragraph( paragraph, @@ -350,27 +347,6 @@ pub fn draw( ); } -/// Returns the anchor position of a [`Text`] widget. -pub fn anchor( - bounds: Rectangle, - align_x: text::Alignment, - align_y: alignment::Vertical, -) -> Point { - let x = match align_x { - Alignment::Default | Alignment::Left | Alignment::Justified => bounds.x, - Alignment::Center => bounds.center_x(), - Alignment::Right => bounds.x + bounds.width, - }; - - let y = match align_y { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => bounds.center_y(), - alignment::Vertical::Bottom => bounds.y + bounds.height, - }; - - Point::new(x, y) -} - impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 6b8e7167..f2c6f404 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -1,8 +1,10 @@ //! Draw paragraphs. use crate::core; use crate::core::alignment; -use crate::core::text::{Alignment, Hit, Shaping, Span, Text, Wrapping}; -use crate::core::{Font, Point, Rectangle, Size}; +use crate::core::text::{ + Alignment, Hit, LineHeight, Shaping, Span, Text, Wrapping, +}; +use crate::core::{Font, Pixels, Point, Rectangle, Size}; use crate::text; use std::fmt; @@ -220,6 +222,18 @@ impl core::text::Paragraph for Paragraph { } } + fn size(&self) -> Pixels { + Pixels(self.0.buffer.metrics().font_size) + } + + fn font(&self) -> Font { + self.0.font + } + + fn line_height(&self) -> LineHeight { + LineHeight::Absolute(Pixels(self.0.buffer.metrics().line_height)) + } + fn align_x(&self) -> Alignment { self.internal().align_x } @@ -228,6 +242,18 @@ impl core::text::Paragraph for Paragraph { self.internal().align_y } + fn wrapping(&self) -> Wrapping { + self.0.wrapping + } + + fn shaping(&self) -> Shaping { + self.0.shaping + } + + fn bounds(&self) -> Size { + self.0.bounds + } + fn min_bounds(&self) -> Size { self.internal().min_bounds } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 1ef825ae..add4dee8 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,8 +1,6 @@ use crate::core::alignment; use crate::core::text::{Alignment, Shaping}; -use crate::core::{ - Color, Font, Pixels, Point, Rectangle, Size, Transformation, -}; +use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation}; use crate::graphics::text::cache::{self, Cache}; use crate::graphics::text::editor; use crate::graphics::text::font_system; @@ -47,8 +45,6 @@ impl Pipeline { clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, ) { - use crate::core::text::Paragraph as _; - let Some(paragraph) = paragraph.upgrade() else { return; }; @@ -59,10 +55,8 @@ impl Pipeline { font_system.raw(), &mut self.glyph_cache, paragraph.buffer(), - Rectangle::new(position, paragraph.min_bounds()), + position, color, - paragraph.align_x(), - paragraph.align_y(), pixels, clip_mask, transformation, @@ -78,8 +72,6 @@ impl Pipeline { clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, ) { - use crate::core::text::Editor as _; - let Some(editor) = editor.upgrade() else { return; }; @@ -90,10 +82,8 @@ impl Pipeline { font_system.raw(), &mut self.glyph_cache, editor.buffer(), - Rectangle::new(position, editor.bounds()), + position, color, - Alignment::Default, - alignment::Vertical::Top, pixels, clip_mask, transformation, @@ -135,18 +125,26 @@ impl Pipeline { let width = entry.min_bounds.width; let height = entry.min_bounds.height; + let x = match align_x { + Alignment::Default | Alignment::Left | Alignment::Justified => { + bounds.x + } + Alignment::Center => bounds.x - width / 2.0, + Alignment::Right => bounds.x - width, + }; + + let y = match align_y { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - height / 2.0, + alignment::Vertical::Bottom => bounds.y - height, + }; + draw( font_system, &mut self.glyph_cache, &entry.buffer, - Rectangle { - width, - height, - ..bounds - }, + Point::new(x, y), color, - align_x, - align_y, pixels, clip_mask, transformation, @@ -164,22 +162,12 @@ impl Pipeline { ) { let mut font_system = font_system().write().expect("Write font system"); - let (width, height) = buffer.size(); - draw( font_system.raw(), &mut self.glyph_cache, buffer, - Rectangle::new( - position, - Size::new( - width.unwrap_or(pixels.width() as f32), - height.unwrap_or(pixels.height() as f32), - ), - ), + position, color, - Alignment::Default, - alignment::Vertical::Top, pixels, clip_mask, transformation, @@ -196,34 +184,22 @@ fn draw( font_system: &mut cosmic_text::FontSystem, glyph_cache: &mut GlyphCache, buffer: &cosmic_text::Buffer, - bounds: Rectangle, + position: Point, color: Color, - align_x: Alignment, - align_y: alignment::Vertical, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, transformation: Transformation, ) { - let bounds = bounds * transformation; - - let x = match align_x { - Alignment::Default | Alignment::Left | Alignment::Justified => bounds.x, - Alignment::Center => bounds.x - bounds.width / 2.0, - Alignment::Right => bounds.x - bounds.width, - }; - - let y = match align_y { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => bounds.y - bounds.height / 2.0, - alignment::Vertical::Bottom => bounds.y - bounds.height, - }; + let position = position * transformation; let mut swash = cosmic_text::SwashCache::new(); for run in buffer.layout_runs() { for glyph in run.glyphs { - let physical_glyph = - glyph.physical((x, y), transformation.scale_factor()); + let physical_glyph = glyph.physical( + (position.x, position.y), + transformation.scale_factor(), + ); if let Some((buffer, placement)) = glyph_cache.allocate( physical_glyph.cache_key, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index b3b2ee68..f2908e71 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -506,130 +506,114 @@ fn prepare( let text_areas = sections.iter().zip(allocations.iter()).filter_map( |(section, allocation)| { - let ( - buffer, - bounds, - align_x, - align_y, - color, - clip_bounds, - transformation, - ) = match section { - Text::Paragraph { - position, - color, - clip_bounds, - transformation, - .. - } => { - use crate::core::text::Paragraph as _; + let (buffer, position, color, clip_bounds, transformation) = + match section { + Text::Paragraph { + position, + color, + clip_bounds, + transformation, + .. + } => { + let Some(Allocation::Paragraph(paragraph)) = allocation + else { + return None; + }; - let Some(Allocation::Paragraph(paragraph)) = allocation - else { - return None; - }; + ( + paragraph.buffer(), + *position, + *color, + *clip_bounds, + *transformation, + ) + } + Text::Editor { + position, + color, + clip_bounds, + transformation, + .. + } => { + let Some(Allocation::Editor(editor)) = allocation + else { + return None; + }; - ( - paragraph.buffer(), - Rectangle::new(*position, paragraph.min_bounds()), - paragraph.align_x(), - paragraph.align_y(), - *color, - *clip_bounds, - *transformation, - ) - } - Text::Editor { - position, - color, - clip_bounds, - transformation, - .. - } => { - use crate::core::text::Editor as _; + ( + editor.buffer(), + *position, + *color, + *clip_bounds, + *transformation, + ) + } + Text::Cached { + bounds, + align_x, + align_y, + color, + clip_bounds, + .. + } => { + let Some(Allocation::Cache(key)) = allocation else { + return None; + }; - let Some(Allocation::Editor(editor)) = allocation else { - return None; - }; + let entry = + buffer_cache.get(key).expect("Get cached buffer"); - ( - editor.buffer(), - Rectangle::new(*position, editor.bounds()), - Alignment::Default, - alignment::Vertical::Top, - *color, - *clip_bounds, - *transformation, - ) - } - Text::Cached { - bounds, - align_x, - align_y, - color, - clip_bounds, - .. - } => { - let Some(Allocation::Cache(key)) = allocation else { - return None; - }; + let mut position = bounds.position(); - let entry = - buffer_cache.get(key).expect("Get cached buffer"); + position.x = match align_x { + Alignment::Default + | Alignment::Left + | Alignment::Justified => position.x, + Alignment::Center => { + position.x - entry.min_bounds.width / 2.0 + } + Alignment::Right => { + position.x - entry.min_bounds.width + } + }; - ( - &entry.buffer, - Rectangle::new(bounds.position(), entry.min_bounds), - *align_x, - *align_y, - *color, - *clip_bounds, - Transformation::IDENTITY, - ) - } - Text::Raw { - raw, - transformation, - } => { - let Some(Allocation::Raw(buffer)) = allocation else { - return None; - }; + position.y = match align_y { + alignment::Vertical::Top => position.y, + alignment::Vertical::Center => { + position.y - entry.min_bounds.height / 2.0 + } + alignment::Vertical::Bottom => { + position.y - entry.min_bounds.height + } + }; - let (width, height) = buffer.size(); + ( + &entry.buffer, + position, + *color, + *clip_bounds, + Transformation::IDENTITY, + ) + } + Text::Raw { + raw, + transformation, + } => { + let Some(Allocation::Raw(buffer)) = allocation else { + return None; + }; - ( - buffer.as_ref(), - Rectangle::new( + ( + buffer.as_ref(), raw.position, - Size::new( - width.unwrap_or(layer_bounds.width), - height.unwrap_or(layer_bounds.height), - ), - ), - Alignment::Default, - alignment::Vertical::Top, - raw.color, - raw.clip_bounds, - *transformation, - ) - } - }; + raw.color, + raw.clip_bounds, + *transformation, + ) + } + }; - let bounds = bounds * transformation * layer_transformation; - - let left = match align_x { - Alignment::Default | Alignment::Left | Alignment::Justified => { - bounds.x - } - Alignment::Center => bounds.x - bounds.width / 2.0, - Alignment::Right => bounds.x - bounds.width, - }; - - let top = match align_y { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => bounds.y - bounds.height / 2.0, - alignment::Vertical::Bottom => bounds.y - bounds.height, - }; + let position = position * transformation * layer_transformation; let clip_bounds = layer_bounds.intersection( &(clip_bounds * transformation * layer_transformation), @@ -637,8 +621,8 @@ fn prepare( Some(cryoglyph::TextArea { buffer, - left, - top, + left: position.x, + top: position.y, scale: transformation.scale_factor() * layer_transformation.scale_factor(), bounds: cryoglyph::TextBounds { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index b84e083d..a38f5945 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -287,16 +287,18 @@ where state, renderer, limits, - self.width, - Length::Shrink, &self.label, - self.text_line_height, - self.text_size, - self.font, - text::Alignment::Default, - alignment::Vertical::Top, - self.text_shaping, - self.text_wrapping, + widget::text::Format { + width: self.width, + height: Length::Shrink, + line_height: self.text_line_height, + size: self.text_size, + font: self.font, + align_x: text::Alignment::Default, + align_y: alignment::Vertical::Top, + shaping: self.text_shaping, + wrapping: self.text_wrapping, + }, ) }, ) @@ -436,8 +438,8 @@ where crate::text::draw( renderer, defaults, - label_layout, - state.0.raw(), + label_layout.bounds(), + state.raw(), crate::text::Style { color: style.text_color, }, diff --git a/widget/src/radio.rs b/widget/src/radio.rs index fcbb358b..fab29f56 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -308,16 +308,18 @@ where state, renderer, limits, - self.width, - Length::Shrink, &self.label, - self.text_line_height, - self.text_size, - self.font, - text::Alignment::Default, - alignment::Vertical::Top, - self.text_shaping, - self.text_wrapping, + widget::text::Format { + width: self.width, + height: Length::Shrink, + line_height: self.text_line_height, + size: self.text_size, + font: self.font, + align_x: text::Alignment::Default, + align_y: alignment::Vertical::Top, + shaping: self.text_shaping, + wrapping: self.text_wrapping, + }, ) }, ) @@ -445,8 +447,8 @@ where crate::text::draw( renderer, defaults, - label_layout, - state.0.raw(), + label_layout.bounds(), + state.raw(), crate::text::Style { color: style.text_color, }, diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index f1b2c75e..9e7eaddf 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -365,7 +365,7 @@ where text::draw( renderer, defaults, - layout, + layout.bounds(), &state.paragraph, style, viewport, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index a1f6e560..ba15e916 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -57,8 +57,9 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Event, InputMethod, Layout, Length, - Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + Alignment, Background, Border, Color, Element, Event, InputMethod, Layout, + Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, + Widget, }; use crate::runtime::Action; use crate::runtime::task::{self, Task}; @@ -481,9 +482,15 @@ where if self.icon.is_some() { let icon_layout = children_layout.next().unwrap(); + let icon = state.icon.raw(); + renderer.fill_paragraph( - state.icon.raw(), - icon_layout.bounds().center(), + icon, + icon_layout.bounds().anchor( + icon.min_bounds(), + Alignment::Center, + Alignment::Center, + ), style.icon, *viewport, ); @@ -609,8 +616,11 @@ where renderer.fill_paragraph( paragraph, - Point::new(text_bounds.x, text_bounds.center_y()) - + Vector::new(alignment_offset - offset, 0.0), + text_bounds.anchor( + paragraph.min_bounds(), + Alignment::Start, + Alignment::Center, + ) + Vector::new(alignment_offset - offset, 0.0), if text.is_empty() { style.placeholder } else { @@ -1724,7 +1734,7 @@ fn replace_paragraph( state.value = paragraph::Plain::new(Text { font, line_height, - content: &value.to_string(), + content: value.to_string(), bounds: Size::new(f32::INFINITY, text_bounds.height), size: text_size, align_x: text::Alignment::Default, diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 69678f12..2e6597f4 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -291,16 +291,18 @@ where 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, - self.text_wrapping, + widget::text::Format { + width: self.width, + height: Length::Shrink, + line_height: self.text_line_height, + size: self.text_size, + font: self.font, + align_x: self.text_alignment, + align_y: alignment::Vertical::Top, + shaping: self.text_shaping, + wrapping: self.text_wrapping, + }, ) } else { layout::Node::new(Size::ZERO) @@ -406,8 +408,8 @@ where crate::text::draw( renderer, style, - label_layout, - state.0.raw(), + label_layout.bounds(), + state.raw(), crate::text::Style::default(), viewport, );