mirror of https://github.com/linebender/xilem
Move to a single global `parley::LayoutContext` (#405)
Based on a suggestion from @dfrg. This appears to have improved performance with the 30_000 hello's example from ~$\frac{1}{20}$ms to $\frac{1}{160}$ms So an approx 8x increase in throughput for that scene.
This commit is contained in:
parent
dd196c7134
commit
9299b67986
|
@ -102,7 +102,7 @@ impl Widget for CustomWidget {
|
|||
|
||||
// To render text, we first create a LayoutBuilder and set the text properties.
|
||||
let mut lcx = parley::LayoutContext::new();
|
||||
let mut text_layout_builder = lcx.ranged_builder(ctx.font_ctx(), &self.0, 1.0);
|
||||
let mut text_layout_builder = lcx.ranged_builder(ctx.text_contexts().0, &self.0, 1.0);
|
||||
|
||||
text_layout_builder.push_default(&StyleProperty::FontStack(FontStack::Single(
|
||||
FontFamily::Generic(parley::style::GenericFamily::Serif),
|
||||
|
|
|
@ -7,13 +7,14 @@ use std::any::Any;
|
|||
use std::time::Duration;
|
||||
|
||||
use accesskit::{NodeBuilder, TreeUpdate};
|
||||
use parley::FontContext;
|
||||
use parley::{FontContext, LayoutContext};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use crate::action::Action;
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::promise::PromiseToken;
|
||||
use crate::render_root::{RenderRootSignal, RenderRootState};
|
||||
use crate::text2::TextBrush;
|
||||
use crate::text_helpers::{ImeChangeSignal, TextFieldRegistration};
|
||||
use crate::widget::{CursorChange, WidgetMut, WidgetState};
|
||||
use crate::{CursorIcon, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod};
|
||||
|
@ -662,8 +663,12 @@ impl LayoutCtx<'_> {
|
|||
}
|
||||
|
||||
impl_context_method!(LayoutCtx<'_>, PaintCtx<'_>, {
|
||||
pub fn font_ctx(&mut self) -> &mut FontContext {
|
||||
&mut self.global_state.font_context
|
||||
/// Get the contexts needed to build and paint text sections.
|
||||
pub fn text_contexts(&mut self) -> (&mut FontContext, &mut LayoutContext<TextBrush>) {
|
||||
(
|
||||
&mut self.global_state.font_context,
|
||||
&mut self.global_state.text_layout_context,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::collections::VecDeque;
|
|||
|
||||
use accesskit::{ActionRequest, NodeBuilder, Tree, TreeUpdate};
|
||||
use kurbo::Affine;
|
||||
use parley::FontContext;
|
||||
use parley::{FontContext, LayoutContext};
|
||||
use tracing::{debug, info_span, warn};
|
||||
use vello::peniko::{Color, Fill};
|
||||
use vello::Scene;
|
||||
|
@ -21,6 +21,7 @@ use crate::debug_logger::DebugLogger;
|
|||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
||||
use crate::event::{PointerEvent, TextEvent, WindowEvent};
|
||||
use crate::kurbo::Point;
|
||||
use crate::text2::TextBrush;
|
||||
use crate::widget::{WidgetMut, WidgetState};
|
||||
use crate::{
|
||||
AccessCtx, AccessEvent, Action, BoxConstraints, CursorIcon, Handled, InternalLifeCycle,
|
||||
|
@ -51,6 +52,7 @@ pub(crate) struct RenderRootState {
|
|||
pub(crate) focused_widget: Option<WidgetId>,
|
||||
pub(crate) next_focused_widget: Option<WidgetId>,
|
||||
pub(crate) font_context: FontContext,
|
||||
pub(crate) text_layout_context: LayoutContext<TextBrush>,
|
||||
}
|
||||
|
||||
/// Defines how a windows size should be determined
|
||||
|
@ -99,6 +101,7 @@ impl RenderRoot {
|
|||
focused_widget: None,
|
||||
next_focused_widget: None,
|
||||
font_context: FontContext::default(),
|
||||
text_layout_context: LayoutContext::new(),
|
||||
},
|
||||
rebuild_access_tree: true,
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use std::ops::{Deref, DerefMut, Range};
|
||||
|
||||
use kurbo::Point;
|
||||
use parley::FontContext;
|
||||
use parley::{FontContext, LayoutContext};
|
||||
use vello::Scene;
|
||||
use winit::{
|
||||
event::Ime,
|
||||
|
@ -19,7 +19,7 @@ use crate::{
|
|||
use super::{
|
||||
offset_for_delete_backwards,
|
||||
selection::{Affinity, Selection},
|
||||
Selectable, TextWithSelection,
|
||||
Selectable, TextBrush, TextWithSelection,
|
||||
};
|
||||
|
||||
/// Text which can be edited
|
||||
|
@ -73,18 +73,24 @@ impl<T: EditableText> TextEditor<T> {
|
|||
self.preedit_range = None;
|
||||
}
|
||||
|
||||
pub fn rebuild(&mut self, fcx: &mut FontContext) {
|
||||
// TODO: Add the pre-edit range as an underlined region in the text attributes
|
||||
|
||||
self.inner.rebuild_with_attributes(fcx, |mut builder| {
|
||||
if let Some(range) = self.preedit_range.as_ref() {
|
||||
builder.push(
|
||||
&parley::style::StyleProperty::Underline(true),
|
||||
range.clone(),
|
||||
);
|
||||
}
|
||||
builder
|
||||
});
|
||||
/// Rebuild the text.
|
||||
///
|
||||
/// See also [TextLayout::rebuild](crate::text2::TextLayout::rebuild) for more comprehensive docs.
|
||||
pub fn rebuild(
|
||||
&mut self,
|
||||
font_ctx: &mut FontContext,
|
||||
layout_ctx: &mut LayoutContext<TextBrush>,
|
||||
) {
|
||||
self.inner
|
||||
.rebuild_with_attributes(font_ctx, layout_ctx, |mut builder| {
|
||||
if let Some(range) = self.preedit_range.as_ref() {
|
||||
builder.push(
|
||||
&parley::style::StyleProperty::Underline(true),
|
||||
range.clone(),
|
||||
);
|
||||
}
|
||||
builder
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &mut Scene, point: impl Into<Point>) {
|
||||
|
|
|
@ -56,7 +56,6 @@ pub struct TextLayout<T> {
|
|||
needs_layout: bool,
|
||||
needs_line_breaks: bool,
|
||||
layout: Layout<TextBrush>,
|
||||
layout_context: LayoutContext<TextBrush>,
|
||||
scratch_scene: Scene,
|
||||
}
|
||||
|
||||
|
@ -131,7 +130,6 @@ impl<T> TextLayout<T> {
|
|||
needs_layout: true,
|
||||
needs_line_breaks: true,
|
||||
layout: Layout::new(),
|
||||
layout_context: LayoutContext::new(),
|
||||
scratch_scene: Scene::new(),
|
||||
}
|
||||
}
|
||||
|
@ -422,8 +420,12 @@ impl<T: TextStorage> TextLayout<T> {
|
|||
/// This method should be called whenever any of these things may have changed.
|
||||
/// A simple way to ensure this is correct is to always call this method
|
||||
/// as part of your widget's [`layout`][crate::Widget::layout] method.
|
||||
pub fn rebuild(&mut self, fcx: &mut FontContext) {
|
||||
self.rebuild_with_attributes(fcx, |builder| builder);
|
||||
pub fn rebuild(
|
||||
&mut self,
|
||||
font_ctx: &mut FontContext,
|
||||
layout_ctx: &mut LayoutContext<TextBrush>,
|
||||
) {
|
||||
self.rebuild_with_attributes(font_ctx, layout_ctx, |builder| builder);
|
||||
}
|
||||
|
||||
/// Rebuild the inner layout as needed, adding attributes to the underlying layout.
|
||||
|
@ -431,7 +433,8 @@ impl<T: TextStorage> TextLayout<T> {
|
|||
/// See [`Self::rebuild`] for more information
|
||||
pub fn rebuild_with_attributes(
|
||||
&mut self,
|
||||
fcx: &mut FontContext,
|
||||
font_ctx: &mut FontContext,
|
||||
layout_ctx: &mut LayoutContext<TextBrush>,
|
||||
attributes: impl for<'b> FnOnce(
|
||||
RangedBuilder<'b, TextBrush, &'b str>,
|
||||
) -> RangedBuilder<'b, TextBrush, &'b str>,
|
||||
|
@ -439,9 +442,7 @@ impl<T: TextStorage> TextLayout<T> {
|
|||
if self.needs_layout {
|
||||
self.needs_layout = false;
|
||||
|
||||
let mut builder =
|
||||
self.layout_context
|
||||
.ranged_builder(fcx, self.text.as_str(), self.scale);
|
||||
let mut builder = layout_ctx.ranged_builder(font_ctx, self.text.as_str(), self.scale);
|
||||
builder.push_default(&StyleProperty::Brush(self.brush.clone()));
|
||||
builder.push_default(&StyleProperty::FontSize(self.text_size));
|
||||
builder.push_default(&StyleProperty::FontStack(self.font));
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut, Range};
|
|||
|
||||
use kurbo::{Affine, Line, Point, Stroke};
|
||||
use parley::context::RangedBuilder;
|
||||
use parley::FontContext;
|
||||
use parley::{FontContext, LayoutContext};
|
||||
use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
|
||||
use vello::peniko::{Brush, Color};
|
||||
use vello::Scene;
|
||||
|
@ -209,10 +209,25 @@ impl<T: Selectable> TextWithSelection<T> {
|
|||
self.needs_selection_update = true;
|
||||
}
|
||||
|
||||
/// Rebuild the text layout.
|
||||
///
|
||||
/// See also [TextLayout::rebuild] for more comprehensive docs.
|
||||
pub fn rebuild(
|
||||
&mut self,
|
||||
font_ctx: &mut FontContext,
|
||||
layout_ctx: &mut LayoutContext<TextBrush>,
|
||||
) {
|
||||
self.rebuild_with_attributes(font_ctx, layout_ctx, |builder| builder);
|
||||
}
|
||||
|
||||
// Intentionally aliases the method on `TextLayout`
|
||||
/// Rebuild the text layout, adding attributes to the builder.
|
||||
///
|
||||
/// See also [TextLayout::rebuild_with_attributes] for more comprehensive docs.
|
||||
pub fn rebuild_with_attributes(
|
||||
&mut self,
|
||||
fcx: &mut FontContext,
|
||||
font_ctx: &mut FontContext,
|
||||
layout_ctx: &mut LayoutContext<TextBrush>,
|
||||
attributes: impl for<'b> FnOnce(
|
||||
RangedBuilder<'b, TextBrush, &'b str>,
|
||||
) -> RangedBuilder<'b, TextBrush, &'b str>,
|
||||
|
@ -221,26 +236,23 @@ impl<T: Selectable> TextWithSelection<T> {
|
|||
// selected range was previously or currently non-zero size (i.e. there is a selected range)
|
||||
if self.needs_selection_update || self.layout.needs_rebuild() {
|
||||
self.layout.invalidate();
|
||||
self.layout.rebuild_with_attributes(fcx, |mut builder| {
|
||||
if let Some(selection) = self.selection {
|
||||
let range = selection.range();
|
||||
if !range.is_empty() {
|
||||
builder.push(
|
||||
&parley::style::StyleProperty::Brush(self.highlight_brush.clone()),
|
||||
range,
|
||||
);
|
||||
self.layout
|
||||
.rebuild_with_attributes(font_ctx, layout_ctx, |mut builder| {
|
||||
if let Some(selection) = self.selection {
|
||||
let range = selection.range();
|
||||
if !range.is_empty() {
|
||||
builder.push(
|
||||
&parley::style::StyleProperty::Brush(self.highlight_brush.clone()),
|
||||
range,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
attributes(builder)
|
||||
});
|
||||
attributes(builder)
|
||||
});
|
||||
self.needs_selection_update = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rebuild(&mut self, fcx: &mut FontContext) {
|
||||
self.rebuild_with_attributes(fcx, |builder| builder);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &mut Scene, point: impl Into<Point>) {
|
||||
// TODO: Calculate the location for this in layout lazily?
|
||||
if let Some(selection) = self.selection {
|
||||
|
|
|
@ -214,7 +214,8 @@ impl Widget for Label {
|
|||
};
|
||||
self.text_layout.set_max_advance(max_advance);
|
||||
if self.text_layout.needs_rebuild() {
|
||||
self.text_layout.rebuild(ctx.font_ctx());
|
||||
let (font_ctx, layout_ctx) = ctx.text_contexts();
|
||||
self.text_layout.rebuild(font_ctx, layout_ctx);
|
||||
}
|
||||
// We ignore trailing whitespace for a label
|
||||
let text_size = self.text_layout.size();
|
||||
|
|
|
@ -248,7 +248,8 @@ impl Widget for Prose {
|
|||
};
|
||||
self.text_layout.set_max_advance(max_advance);
|
||||
if self.text_layout.needs_rebuild() {
|
||||
self.text_layout.rebuild(ctx.font_ctx());
|
||||
let (font_ctx, layout_ctx) = ctx.text_contexts();
|
||||
self.text_layout.rebuild(font_ctx, layout_ctx);
|
||||
}
|
||||
// We ignore trailing whitespace for a label
|
||||
let text_size = self.text_layout.size();
|
||||
|
|
|
@ -273,7 +273,8 @@ impl Widget for Textbox {
|
|||
};
|
||||
self.editor.set_max_advance(max_advance);
|
||||
if self.editor.needs_rebuild() {
|
||||
self.editor.rebuild(ctx.font_ctx());
|
||||
let (font_ctx, layout_ctx) = ctx.text_contexts();
|
||||
self.editor.rebuild(font_ctx, layout_ctx);
|
||||
}
|
||||
let text_size = self.editor.size();
|
||||
let width = if bc.max().width.is_finite() {
|
||||
|
|
Loading…
Reference in New Issue