Clip text to viewport bounds instead of layout bounds

This commit is contained in:
Héctor Ramón Jiménez 2023-12-01 16:04:27 +01:00
parent 99899d49cc
commit 936d480267
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
23 changed files with 177 additions and 115 deletions

View file

@ -64,6 +64,7 @@ impl text::Renderer for Null {
_paragraph: &Self::Paragraph,
_position: Point,
_color: Color,
_viewport: Rectangle,
) {
}
@ -72,6 +73,7 @@ impl text::Renderer for Null {
_editor: &Self::Editor,
_position: Point,
_color: Color,
_viewport: Rectangle,
) {
}
@ -80,6 +82,7 @@ impl text::Renderer for Null {
_paragraph: Text<'_, Self::Font>,
_position: Point,
_color: Color,
_viewport: Rectangle,
) {
}
}

View file

@ -9,7 +9,7 @@ pub use highlighter::Highlighter;
pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Size};
use crate::{Color, Pixels, Point, Rectangle, Size};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
@ -202,6 +202,7 @@ pub trait Renderer: crate::Renderer {
text: &Self::Paragraph,
position: Point,
color: Color,
viewport: Rectangle,
);
/// Draws the given [`Editor`] at the given position and with the given
@ -211,6 +212,7 @@ pub trait Renderer: crate::Renderer {
editor: &Self::Editor,
position: Point,
color: Color,
viewport: Rectangle,
);
/// Draws the given [`Text`] at the given position and with the given
@ -220,5 +222,6 @@ pub trait Renderer: crate::Renderer {
text: Text<'_, Self::Font>,
position: Point,
color: Color,
viewport: Rectangle,
);
}

View file

@ -172,7 +172,7 @@ where
style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
@ -182,6 +182,7 @@ where
layout,
state,
theme.appearance(self.style.clone()),
viewport,
);
}
}
@ -244,6 +245,7 @@ pub fn draw<Renderer>(
layout: Layout<'_>,
state: &State<Renderer::Paragraph>,
appearance: Appearance,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
{
@ -266,6 +268,7 @@ pub fn draw<Renderer>(
paragraph,
Point::new(x, y),
appearance.color.unwrap_or(style.text_color),
*viewport,
);
}

View file

@ -14,24 +14,26 @@ use std::sync::Arc;
pub enum Primitive<T> {
/// A text primitive
Text {
/// The contents of the text
/// The contents of the text.
content: String,
/// The bounds of the text
/// The bounds of the text.
bounds: Rectangle,
/// The color of the text
/// The color of the text.
color: Color,
/// The size of the text in logical pixels
/// The size of the text in logical pixels.
size: Pixels,
/// The line height of the text
/// The line height of the text.
line_height: text::LineHeight,
/// The font of the text
/// The font of the text.
font: Font,
/// The horizontal alignment of the text
/// The horizontal alignment of the text.
horizontal_alignment: alignment::Horizontal,
/// The vertical alignment of the text
/// The vertical alignment of the text.
vertical_alignment: alignment::Vertical,
/// The shaping strategy of the text.
shaping: text::Shaping,
/// The viewport of the text.
viewport: Rectangle,
},
/// A paragraph primitive
Paragraph {
@ -41,15 +43,19 @@ pub enum Primitive<T> {
position: Point,
/// The color of the paragraph.
color: Color,
/// The viewport of the paragraph.
viewport: Rectangle,
},
/// An editor primitive
Editor {
/// The [`editor::Weak`] reference.
editor: editor::Weak,
/// The position of the paragraph.
/// The position of the editor.
position: Point,
/// The color of the paragraph.
/// The color of the editor.
color: Color,
/// The viewport of the editor.
viewport: Rectangle,
},
/// A quad primitive
Quad {

View file

@ -164,11 +164,13 @@ where
paragraph: &Self::Paragraph,
position: Point,
color: Color,
viewport: Rectangle,
) {
self.primitives.push(Primitive::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
viewport,
});
}
@ -177,11 +179,13 @@ where
editor: &Self::Editor,
position: Point,
color: Color,
viewport: Rectangle,
) {
self.primitives.push(Primitive::Editor {
editor: editor.downgrade(),
position,
color,
viewport,
});
}
@ -190,6 +194,7 @@ where
text: Text<'_, Self::Font>,
position: Point,
color: Color,
viewport: Rectangle,
) {
self.primitives.push(Primitive::Text {
content: text.content.to_string(),
@ -201,6 +206,7 @@ where
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
viewport,
});
}
}

View file

@ -175,11 +175,12 @@ impl<T> text::Renderer for Renderer<T> {
paragraph: &Self::Paragraph,
position: Point,
color: Color,
viewport: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_paragraph(paragraph, position, color)
renderer.fill_paragraph(paragraph, position, color, viewport)
);
}
@ -188,11 +189,12 @@ impl<T> text::Renderer for Renderer<T> {
editor: &Self::Editor,
position: Point,
color: Color,
viewport: Rectangle,
) {
delegate!(
self,
renderer,
renderer.fill_editor(editor, position, color)
renderer.fill_editor(editor, position, color, viewport)
);
}
@ -201,8 +203,13 @@ impl<T> text::Renderer for Renderer<T> {
text: Text<'_, Self::Font>,
position: Point,
color: Color,
viewport: Rectangle,
) {
delegate!(self, renderer, renderer.fill_text(text, position, color));
delegate!(
self,
renderer,
renderer.fill_text(text, position, color, viewport)
);
}
}

View file

@ -1,6 +1,6 @@
use crate::core::{Background, Color, Gradient, Rectangle, Vector};
use crate::graphics::backend;
use crate::graphics::{Damage, Viewport};
use crate::graphics::Viewport;
use crate::primitive::{self, Primitive};
use std::borrow::Cow;
@ -361,11 +361,9 @@ impl Backend {
paragraph,
position,
color,
viewport,
} => {
let physical_bounds =
(Rectangle::new(*position, paragraph.min_bounds)
+ translation)
* scale_factor;
let physical_bounds = (*viewport + translation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
@ -387,10 +385,9 @@ impl Backend {
editor,
position,
color,
viewport,
} => {
let physical_bounds =
(Rectangle::new(*position, editor.bounds) + translation)
* scale_factor;
let physical_bounds = (*viewport + translation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;
@ -418,9 +415,9 @@ impl Backend {
horizontal_alignment,
vertical_alignment,
shaping,
viewport,
} => {
let physical_bounds =
(primitive.bounds() + translation) * scale_factor;
let physical_bounds = (*viewport + translation) * scale_factor;
if !clip_bounds.intersects(&physical_bounds) {
return;

View file

@ -109,15 +109,17 @@ impl Frame {
Point::new(transformed[0].x, transformed[0].y)
};
let bounds = Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
};
// TODO: Use vectorial text instead of primitive
self.primitives.push(Primitive::Text {
content: text.content,
bounds: Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
},
bounds,
color: text.color,
size: text.size,
line_height: text.line_height,
@ -125,6 +127,7 @@ impl Frame {
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
viewport: bounds,
});
}

View file

@ -328,15 +328,17 @@ impl Frame {
Point::new(transformed.x, transformed.y)
};
let bounds = Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
};
// TODO: Use vectorial text instead of primitive
self.primitives.push(Primitive::Text {
content: text.content,
bounds: Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
},
bounds,
color: text.color,
size: text.size,
line_height: text.line_height,
@ -344,6 +346,7 @@ impl Frame {
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
viewport: bounds,
});
}

View file

@ -75,6 +75,7 @@ impl<'a> Layer<'a> {
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: core::text::Shaping::Basic,
viewport: Rectangle::with_size(Size::INFINITY),
};
overlay.text.push(Text::Cached(text.clone()));
@ -123,6 +124,7 @@ impl<'a> Layer<'a> {
paragraph,
position,
color,
viewport,
} => {
let layer = &mut layers[current_layer];
@ -130,12 +132,14 @@ impl<'a> Layer<'a> {
paragraph: paragraph.clone(),
position: *position + translation,
color: *color,
viewport: *viewport + translation,
});
}
Primitive::Editor {
editor,
position,
color,
viewport,
} => {
let layer = &mut layers[current_layer];
@ -143,6 +147,7 @@ impl<'a> Layer<'a> {
editor: editor.clone(),
position: *position + translation,
color: *color,
viewport: *viewport + translation,
});
}
Primitive::Text {
@ -155,6 +160,7 @@ impl<'a> Layer<'a> {
horizontal_alignment,
vertical_alignment,
shaping,
viewport,
} => {
let layer = &mut layers[current_layer];
@ -168,6 +174,7 @@ impl<'a> Layer<'a> {
horizontal_alignment: *horizontal_alignment,
vertical_alignment: *vertical_alignment,
shaping: *shaping,
viewport: *viewport + translation,
}));
}
Primitive::Quad {

View file

@ -13,6 +13,7 @@ pub enum Text<'a> {
paragraph: paragraph::Weak,
position: Point,
color: Color,
viewport: Rectangle,
},
/// An editor.
#[allow(missing_docs)]
@ -20,6 +21,7 @@ pub enum Text<'a> {
editor: editor::Weak,
position: Point,
color: Color,
viewport: Rectangle,
},
/// A cached text.
Cached(Cached<'a>),
@ -53,4 +55,7 @@ pub struct Cached<'a> {
/// The shaping strategy of the text.
pub shaping: text::Shaping,
/// The viewport of the text.
pub viewport: Rectangle,
}

View file

@ -120,9 +120,13 @@ impl Pipeline {
horizontal_alignment,
vertical_alignment,
color,
viewport,
) = match section {
Text::Paragraph {
position, color, ..
position,
color,
viewport,
..
} => {
use crate::core::text::Paragraph as _;
@ -137,10 +141,14 @@ impl Pipeline {
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
*viewport,
)
}
Text::Editor {
position, color, ..
position,
color,
viewport,
..
} => {
use crate::core::text::Editor as _;
@ -155,6 +163,7 @@ impl Pipeline {
alignment::Horizontal::Left,
alignment::Vertical::Top,
*color,
*viewport,
)
}
Text::Cached(text) => {
@ -173,6 +182,7 @@ impl Pipeline {
text.horizontal_alignment,
text.vertical_alignment,
text.color,
text.viewport,
)
}
};
@ -195,13 +205,8 @@ impl Pipeline {
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
let section_bounds = Rectangle {
x: left,
y: top,
..bounds
};
let clip_bounds = layer_bounds.intersection(&section_bounds)?;
let clip_bounds =
layer_bounds.intersection(&(viewport * scale_factor))?;
Some(glyphon::TextArea {
buffer,

View file

@ -266,7 +266,7 @@ where
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
let is_mouse_over = cursor.is_over(layout.bounds());
@ -315,6 +315,7 @@ where
},
bounds.center(),
custom_style.icon_color,
*viewport,
);
}
}
@ -330,6 +331,7 @@ where
crate::text::Appearance {
color: custom_style.text_color,
},
viewport,
);
}
}

View file

@ -224,15 +224,17 @@ where
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child
.as_widget()
.draw(state, renderer, theme, style, layout, cursor, viewport);
if let Some(viewport) = layout.bounds().intersection(viewport) {
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child.as_widget().draw(
state, renderer, theme, style, layout, cursor, &viewport,
);
}
}
}

View file

@ -622,7 +622,7 @@ where
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
let is_focused = {
let text_input_state = tree.children[0]
@ -645,6 +645,7 @@ where
layout,
cursor,
selection,
viewport,
);
}

View file

@ -252,21 +252,23 @@ where
) {
let style = theme.appearance(&self.style);
draw_background(renderer, &style, layout.bounds());
if let Some(viewport) = layout.bounds().intersection(viewport) {
draw_background(renderer, &style, layout.bounds());
self.content.as_widget().draw(
tree,
renderer,
theme,
&renderer::Style {
text_color: style
.text_color
.unwrap_or(renderer_style.text_color),
},
layout.children().next().unwrap(),
cursor,
viewport,
);
self.content.as_widget().draw(
tree,
renderer,
theme,
&renderer::Style {
text_color: style
.text_color
.unwrap_or(renderer_style.text_color),
},
layout.children().next().unwrap(),
cursor,
&viewport,
);
}
}
fn overlay<'b>(

View file

@ -544,6 +544,7 @@ where
} else {
appearance.text_color
},
*viewport,
);
}
}

View file

@ -235,7 +235,7 @@ where
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
draw(
@ -253,6 +253,7 @@ where
&self.handle,
&self.style,
|| tree.state.downcast_ref::<State<Renderer::Paragraph>>(),
viewport,
);
}
@ -631,6 +632,7 @@ pub fn draw<'a, T, Renderer>(
handle: &Handle<Renderer::Font>,
style: &<Renderer::Theme as StyleSheet>::Style,
state: impl FnOnce() -> &'a State<Renderer::Paragraph>,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@ -715,6 +717,7 @@ pub fn draw<'a, T, Renderer>(
bounds.center_y(),
),
style.handle_color,
*viewport,
);
}
@ -743,6 +746,7 @@ pub fn draw<'a, T, Renderer>(
} else {
style.placeholder_color
},
*viewport,
);
}
}

View file

@ -291,7 +291,7 @@ where
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
let is_mouse_over = cursor.is_over(layout.bounds());
@ -349,6 +349,7 @@ where
crate::text::Appearance {
color: custom_style.text_color,
},
viewport,
);
}
}

View file

@ -213,15 +213,17 @@ where
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child
.as_widget()
.draw(state, renderer, theme, style, layout, cursor, viewport);
if let Some(viewport) = layout.bounds().intersection(viewport) {
for ((child, state), layout) in self
.children
.iter()
.zip(&tree.children)
.zip(layout.children())
{
child.as_widget().draw(
state, renderer, theme, style, layout, cursor, &viewport,
);
}
}
}

View file

@ -429,7 +429,7 @@ where
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
let bounds = layout.bounds();
@ -470,6 +470,7 @@ where
bounds.position()
+ Vector::new(self.padding.left, self.padding.top),
style.text_color,
*viewport,
);
let translation = Vector::new(

View file

@ -238,6 +238,7 @@ where
layout: Layout<'_>,
cursor: mouse::Cursor,
value: Option<&Value>,
viewport: &Rectangle,
) {
draw(
renderer,
@ -250,6 +251,7 @@ where
self.is_secure,
self.icon.as_ref(),
&self.style,
viewport,
);
}
}
@ -362,7 +364,7 @@ where
_style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
draw(
renderer,
@ -375,6 +377,7 @@ where
self.is_secure,
self.icon.as_ref(),
&self.style,
viewport,
);
}
@ -1055,6 +1058,7 @@ pub fn draw<Renderer>(
is_secure: bool,
icon: Option<&Icon<Renderer::Font>>,
style: &<Renderer::Theme as StyleSheet>::Style,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@ -1096,6 +1100,7 @@ pub fn draw<Renderer>(
&state.icon,
icon_layout.bounds().center(),
appearance.icon_color,
*viewport,
);
}
@ -1189,39 +1194,31 @@ pub fn draw<Renderer>(
(None, 0.0)
};
let text_width = state.value.min_width();
let render = |renderer: &mut Renderer| {
if let Some((cursor, color)) = cursor {
renderer.fill_quad(cursor, color);
} else {
renderer.with_translation(Vector::ZERO, |_| {});
}
renderer.fill_paragraph(
if text.is_empty() {
&state.placeholder
} else {
&state.value
},
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)
},
);
};
if text_width > text_bounds.width {
renderer.with_layer(text_bounds, |renderer| {
renderer.with_translation(Vector::new(-offset, 0.0), render);
if let Some((cursor, color)) = cursor {
renderer.with_translation(Vector::new(-offset, 0.0), |renderer| {
renderer.fill_quad(cursor, color)
});
} else {
render(renderer);
renderer.with_translation(Vector::ZERO, |_| {});
}
renderer.fill_paragraph(
if text.is_empty() {
&state.placeholder
} else {
&state.value
},
Point::new(text_bounds.x, text_bounds.center_y())
- Vector::new(offset, 0.0),
if text.is_empty() {
theme.placeholder_color(style)
} else if is_disabled {
theme.disabled_color(style)
} else {
theme.value_color(style)
},
text_bounds,
);
}
/// Computes the current [`mouse::Interaction`] of the [`TextInput`].

View file

@ -266,7 +266,7 @@ where
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
_viewport: &Rectangle,
viewport: &Rectangle,
) {
/// Makes sure that the border radius of the toggler looks good at every size.
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
@ -287,6 +287,7 @@ where
label_layout,
tree.state.downcast_ref(),
crate::text::Appearance::default(),
viewport,
);
}