Disable hinting whilst a `VariableLabel` animation is ongoing (#535)

If using hinting during an animation, a shimmering effect can occur.
This commit is contained in:
Daniel McNab 2024-08-23 18:34:11 +01:00 committed by GitHub
parent ff7635e4c2
commit c77c6ecb68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 76 additions and 17 deletions

View File

@ -59,41 +59,72 @@ pub struct TextLayout<T> {
scratch_scene: Scene,
}
/// Whether a section of text should be hinted.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub enum Hinting {
#[default]
Yes,
No,
}
impl Hinting {
/// Whether the
pub fn should_hint(self) -> bool {
match self {
Hinting::Yes => true,
Hinting::No => false,
}
}
}
/// A custom brush for `Parley`, enabling using Parley to pass-through
/// which glyphs are selected/highlighted
#[derive(Clone, Debug, PartialEq)]
pub enum TextBrush {
Normal(peniko::Brush),
Normal(peniko::Brush, Hinting),
Highlight {
text: peniko::Brush,
fill: peniko::Brush,
hinting: Hinting,
},
}
impl TextBrush {
pub fn set_hinting(&mut self, hinting: Hinting) {
match self {
TextBrush::Normal(_, should_hint) => *should_hint = hinting,
TextBrush::Highlight {
hinting: should_hint,
..
} => *should_hint = hinting,
}
}
}
impl BrushTrait for TextBrush {}
impl From<peniko::Brush> for TextBrush {
fn from(value: peniko::Brush) -> Self {
Self::Normal(value)
Self::Normal(value, Hinting::default())
}
}
impl From<Gradient> for TextBrush {
fn from(value: Gradient) -> Self {
Self::Normal(value.into())
Self::Normal(value.into(), Hinting::default())
}
}
impl From<Color> for TextBrush {
fn from(value: Color) -> Self {
Self::Normal(value.into())
Self::Normal(value.into(), Hinting::default())
}
}
// Parley requires their Brush implementations to implement Default
impl Default for TextBrush {
fn default() -> Self {
Self::Normal(Default::default())
Self::Normal(Default::default(), Hinting::default())
}
}

View File

@ -14,7 +14,7 @@ mod store;
pub use store::{Link, TextStorage};
mod layout;
pub use layout::{LayoutMetrics, TextBrush, TextLayout};
pub use layout::{Hinting, LayoutMetrics, TextBrush, TextLayout};
mod selection;
pub use selection::{

View File

@ -42,6 +42,7 @@ impl<T: Selectable> TextWithSelection<T> {
highlight_brush: TextBrush::Highlight {
text: Color::WHITE.into(),
fill: Color::LIGHT_BLUE.into(),
hinting: Default::default(),
},
}
}

View File

@ -76,9 +76,13 @@ pub fn render_text(
.iter()
.map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord))
.collect::<Vec<_>>();
let text_brush = match &style.brush {
TextBrush::Normal(text_brush) => text_brush,
TextBrush::Highlight { text, fill } => {
let (text_brush, hinting) = match &style.brush {
TextBrush::Normal(text_brush, hinting) => (text_brush, hinting),
TextBrush::Highlight {
text,
fill,
hinting,
} => {
scene.fill(
Fill::EvenOdd,
transform,
@ -95,13 +99,13 @@ pub fn render_text(
),
);
text
(text, hinting)
}
};
scratch_scene
.draw_glyphs(font)
.brush(text_brush)
.hint(true)
.hint(hinting.should_hint())
.transform(transform)
.glyph_transform(glyph_xform)
.font_size(font_size)
@ -121,7 +125,8 @@ pub fn render_text(
);
if let Some(underline) = &style.underline {
let underline_brush = match &underline.brush {
TextBrush::Normal(text) => text,
// Underlines aren't hinted
TextBrush::Normal(text, _) => text,
// It doesn't make sense for an underline to have a highlight colour, so we
// just use the text colour for the colour
TextBrush::Highlight { text, .. } => text,
@ -154,7 +159,8 @@ pub fn render_text(
}
if let Some(strikethrough) = &style.strikethrough {
let strikethrough_brush = match &strikethrough.brush {
TextBrush::Normal(text) => text,
// Strikethroughs aren't hinted
TextBrush::Normal(text, _) => text,
// It doesn't make sense for an underline to have a highlight colour, so we
// just use the text colour for the colour
TextBrush::Highlight { text, .. } => text,

View File

@ -15,7 +15,7 @@ use vello::kurbo::{Affine, Point, Size};
use vello::peniko::BlendMode;
use vello::Scene;
use crate::text::{TextBrush, TextLayout, TextStorage};
use crate::text::{Hinting, TextBrush, TextLayout, TextStorage};
use crate::widget::WidgetMut;
use crate::{
AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
@ -50,6 +50,11 @@ impl AnimatedF32 {
}
}
/// Is this animation finished?
pub fn is_completed(&self) -> bool {
self.target == self.value
}
/// Move this value to the `target` over `over_millis` milliseconds.
/// Might change the current value, if `over_millis` is zero.
///
@ -186,7 +191,7 @@ impl VariableLabel {
}
/// Set the initial font weight for this text.
pub fn with_initial_weight(mut self, weight: f32) -> Self {
self.weight.value = weight;
self.weight = AnimatedF32::stable(weight);
self
}
@ -194,6 +199,19 @@ impl VariableLabel {
pub fn empty() -> Self {
Self::new("")
}
fn brush(&self, disabled: bool) -> TextBrush {
if disabled {
crate::theme::DISABLED_TEXT_COLOR.into()
} else {
let mut brush = self.brush.clone();
if !self.weight.is_completed() {
brush.set_hinting(Hinting::No);
}
// N.B. if hinting is No externally, we don't want to overwrite it to yes.
brush
}
}
}
// --- MARK: WIDGETMUT ---
@ -227,8 +245,9 @@ impl WidgetMut<'_, VariableLabel> {
let brush = brush.into();
self.widget.brush = brush;
if !self.ctx.is_disabled() {
let brush = self.widget.brush.clone();
self.set_text_properties(|layout| layout.set_brush(brush));
self.widget.text_layout.invalidate();
self.ctx.request_layout();
self.ctx.request_paint();
}
}
/// Set the font size for this text.
@ -343,6 +362,8 @@ impl Widget for VariableLabel {
};
self.text_layout.set_max_advance(max_advance);
if self.text_layout.needs_rebuild() {
self.text_layout
.set_brush(self.brush(ctx.widget_state.is_disabled()));
let (font_ctx, layout_ctx) = ctx.text_contexts();
self.text_layout
.rebuild_with_attributes(font_ctx, layout_ctx, |mut builder| {