feat: buffer setter methods are now lazy
This commit is contained in:
parent
e5926aec74
commit
626f44dad8
14 changed files with 388 additions and 240 deletions
|
|
@ -10,7 +10,7 @@ fn load_font_system(c: &mut Criterion) {
|
|||
fn layout(c: &mut Criterion) {
|
||||
let mut fs = ct::FontSystem::new();
|
||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0));
|
||||
buffer.set_size(&mut fs, Some(80.0), None);
|
||||
buffer.set_size(Some(80.0), None);
|
||||
|
||||
for (wrap_name, wrap) in &[
|
||||
("None", ct::Wrap::None),
|
||||
|
|
@ -22,11 +22,11 @@ fn layout(c: &mut Criterion) {
|
|||
("Advanced", ct::Shaping::Advanced),
|
||||
] {
|
||||
let mut group = c.benchmark_group(format!("Wrap({wrap_name}, {shape_name})"));
|
||||
buffer.set_wrap(&mut fs, *wrap);
|
||||
buffer.set_wrap(*wrap);
|
||||
|
||||
let mut run_on_text = |text: &str| {
|
||||
buffer.lines.clear();
|
||||
buffer.set_text(&mut fs, text, &ct::Attrs::new(), *shape, None);
|
||||
buffer.set_text(text, &ct::Attrs::new(), *shape, None);
|
||||
buffer.shape_until_scroll(&mut fs, false);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|||
fn bench_ascii_fast_path(c: &mut Criterion) {
|
||||
let mut fs = ct::FontSystem::new();
|
||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||
buffer.set_size(&mut fs, Some(500.0), None);
|
||||
buffer.set_size(Some(500.0), None);
|
||||
|
||||
let ascii_text = "Pure ASCII text for BidiParagraphs optimization testing.\n".repeat(50);
|
||||
|
||||
c.bench_function("ShapeLine/ASCII Fast Path", |b| {
|
||||
b.iter(|| {
|
||||
buffer.set_text(
|
||||
&mut fs,
|
||||
black_box(&ascii_text),
|
||||
&ct::Attrs::new(),
|
||||
ct::Shaping::Advanced,
|
||||
|
|
@ -26,14 +25,13 @@ fn bench_ascii_fast_path(c: &mut Criterion) {
|
|||
fn bench_bidi_processing(c: &mut Criterion) {
|
||||
let mut fs = ct::FontSystem::new();
|
||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||
buffer.set_size(&mut fs, Some(500.0), None);
|
||||
buffer.set_size(Some(500.0), None);
|
||||
|
||||
let bidi_text = "Mixed English and العربية النص العربي text for BiDi testing.\nThis tests adjust_levels and combined BiDi optimizations.\n".repeat(30);
|
||||
|
||||
c.bench_function("ShapeLine/BiDi Processing", |b| {
|
||||
b.iter(|| {
|
||||
buffer.set_text(
|
||||
&mut fs,
|
||||
black_box(&bidi_text),
|
||||
&ct::Attrs::new(),
|
||||
ct::Shaping::Advanced,
|
||||
|
|
@ -47,7 +45,7 @@ fn bench_bidi_processing(c: &mut Criterion) {
|
|||
fn bench_lang_mixed(c: &mut Criterion) {
|
||||
let mut fs = ct::FontSystem::new();
|
||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||
buffer.set_size(&mut fs, Some(500.0), None);
|
||||
buffer.set_size(Some(500.0), None);
|
||||
|
||||
let bidi_text = include_str!("../sample/hello.txt");
|
||||
|
||||
|
|
@ -56,7 +54,6 @@ fn bench_lang_mixed(c: &mut Criterion) {
|
|||
.bench_function("ShapeLine/Mixed-Language Text", |b| {
|
||||
b.iter(|| {
|
||||
buffer.set_text(
|
||||
&mut fs,
|
||||
black_box(&bidi_text),
|
||||
&ct::Attrs::new(),
|
||||
ct::Shaping::Advanced,
|
||||
|
|
@ -70,14 +67,13 @@ fn bench_lang_mixed(c: &mut Criterion) {
|
|||
fn bench_layout_heavy(c: &mut Criterion) {
|
||||
let mut fs = ct::FontSystem::new();
|
||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||
buffer.set_size(&mut fs, Some(500.0), None);
|
||||
buffer.set_size(Some(500.0), None);
|
||||
|
||||
let layout_text = "This is a very long line that will wrap multiple times and stress the reorder optimization through intensive layout processing with comprehensive buffer reuse testing. ".repeat(30);
|
||||
|
||||
c.bench_function("ShapeLine/Layout Heavy", |b| {
|
||||
b.iter(|| {
|
||||
buffer.set_text(
|
||||
&mut fs,
|
||||
black_box(&layout_text),
|
||||
&ct::Attrs::new(),
|
||||
ct::Shaping::Advanced,
|
||||
|
|
@ -91,7 +87,7 @@ fn bench_layout_heavy(c: &mut Criterion) {
|
|||
fn bench_combined_stress(c: &mut Criterion) {
|
||||
let mut fs = ct::FontSystem::new();
|
||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||
buffer.set_size(&mut fs, Some(500.0), None);
|
||||
buffer.set_size(Some(500.0), None);
|
||||
|
||||
let stress_text = format!("{}\n{}\n{}\n{}\n",
|
||||
"ASCII line for BidiParagraphs optimization. ".repeat(15),
|
||||
|
|
@ -103,7 +99,6 @@ fn bench_combined_stress(c: &mut Criterion) {
|
|||
c.bench_function("ShapeLine/Combined Stress", |b| {
|
||||
b.iter(|| {
|
||||
buffer.set_text(
|
||||
&mut fs,
|
||||
black_box(&stress_text),
|
||||
&ct::Attrs::new(),
|
||||
ct::Shaping::Advanced,
|
||||
|
|
|
|||
466
src/buffer.rs
466
src/buffer.rs
|
|
@ -16,6 +16,21 @@ use crate::{
|
|||
Wrap,
|
||||
};
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Tracks which buffer-wide properties have changed since the last layout.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
struct DirtyFlags: u8 {
|
||||
/// Layout caches are stale (wrap, size, metrics, hinting, ellipsize, monospace_width changed)
|
||||
const RELAYOUT = 0b0001;
|
||||
/// tab_width changed — lines containing tabs need reshape
|
||||
const TAB_SHAPE = 0b0010;
|
||||
/// Text was replaced via set_text/set_rich_text — lines are fresh, just need shape_until_scroll
|
||||
const TEXT_SET = 0b0100;
|
||||
/// Scroll position changed — visible region may have shifted to unshaped lines
|
||||
const SCROLL = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
/// A line of visible text for rendering
|
||||
#[derive(Debug)]
|
||||
pub struct LayoutRun<'a> {
|
||||
|
|
@ -330,6 +345,8 @@ pub struct Buffer {
|
|||
monospace_width: Option<f32>,
|
||||
tab_width: u16,
|
||||
hinting: Hinting,
|
||||
/// Dirty flags tracking which properties changed since last layout
|
||||
dirty: DirtyFlags,
|
||||
}
|
||||
|
||||
impl Clone for Buffer {
|
||||
|
|
@ -346,6 +363,7 @@ impl Clone for Buffer {
|
|||
monospace_width: self.monospace_width,
|
||||
tab_width: self.tab_width,
|
||||
hinting: self.hinting,
|
||||
dirty: self.dirty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -376,6 +394,7 @@ impl Buffer {
|
|||
monospace_width: None,
|
||||
tab_width: 8,
|
||||
hinting: Hinting::default(),
|
||||
dirty: DirtyFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,7 +405,8 @@ impl Buffer {
|
|||
/// Will panic if `metrics.line_height` is zero.
|
||||
pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
|
||||
let mut buffer = Self::new_empty(metrics);
|
||||
buffer.set_text(font_system, "", &Attrs::new(), Shaping::Advanced, None);
|
||||
buffer.set_text("", &Attrs::new(), Shaping::Advanced, None);
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
buffer
|
||||
}
|
||||
|
||||
|
|
@ -401,30 +421,36 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
fn relayout(&mut self, font_system: &mut FontSystem) {
|
||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
||||
let instant = std::time::Instant::now();
|
||||
/// Process dirty flags: invalidate shape/layout caches as needed, then clear flags.
|
||||
/// Returns `true` if any flags were set (i.e., work may be needed).
|
||||
fn resolve_dirty(&mut self) -> bool {
|
||||
let dirty = self.dirty;
|
||||
if dirty.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for line in &mut self.lines {
|
||||
if line.shape_opt().is_some() {
|
||||
line.reset_layout();
|
||||
line.layout(
|
||||
font_system,
|
||||
self.metrics.font_size,
|
||||
self.width_opt,
|
||||
self.wrap,
|
||||
self.ellipsize,
|
||||
self.monospace_width,
|
||||
self.tab_width,
|
||||
self.hinting,
|
||||
);
|
||||
if dirty.contains(DirtyFlags::TEXT_SET) {
|
||||
// Lines were replaced — already fresh, no cache to invalidate.
|
||||
} else {
|
||||
if dirty.contains(DirtyFlags::TAB_SHAPE) {
|
||||
for line in &mut self.lines {
|
||||
if line.shape_opt().is_some() && line.text().contains('\t') {
|
||||
line.reset_shaping();
|
||||
}
|
||||
}
|
||||
}
|
||||
if dirty.contains(DirtyFlags::RELAYOUT) {
|
||||
for line in &mut self.lines {
|
||||
if line.shape_opt().is_some() {
|
||||
line.reset_layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.redraw = true;
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
||||
log::debug!("relayout: {:?}", instant.elapsed());
|
||||
self.dirty = DirtyFlags::empty();
|
||||
true
|
||||
}
|
||||
|
||||
/// Shape lines until cursor, also scrolling to include cursor in view
|
||||
|
|
@ -435,6 +461,7 @@ impl Buffer {
|
|||
cursor: Cursor,
|
||||
prune: bool,
|
||||
) {
|
||||
self.shape_until_scroll(font_system, prune);
|
||||
let metrics = self.metrics;
|
||||
let old_scroll = self.scroll;
|
||||
|
||||
|
|
@ -490,7 +517,7 @@ impl Buffer {
|
|||
}
|
||||
|
||||
if old_scroll != self.scroll {
|
||||
self.redraw = true;
|
||||
self.dirty |= DirtyFlags::SCROLL;
|
||||
}
|
||||
|
||||
self.shape_until_scroll(font_system, prune);
|
||||
|
|
@ -524,9 +551,22 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Shape lines until scroll
|
||||
/// Shape lines until scroll, resolving any pending dirty state first.
|
||||
///
|
||||
/// This processes dirty flags (invalidating caches for lines that need
|
||||
/// reshaping or relayout) and then shapes/layouts visible lines.
|
||||
///
|
||||
/// Call this before reading layout results via [`layout_runs`] or [`hit`]
|
||||
/// when working with the `Buffer` directly. The [`BorrowedWithFontSystem`]
|
||||
/// wrapper calls this automatically.
|
||||
///
|
||||
/// [`layout_runs`]: Self::layout_runs
|
||||
/// [`hit`]: Self::hit
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
|
||||
if !self.resolve_dirty() {
|
||||
return;
|
||||
}
|
||||
let metrics = self.metrics;
|
||||
let old_scroll = self.scroll;
|
||||
|
||||
|
|
@ -670,13 +710,19 @@ impl Buffer {
|
|||
self.metrics
|
||||
}
|
||||
|
||||
/// Set the current [`Metrics`]
|
||||
/// Set the current [`Metrics`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if `metrics.font_size` is zero.
|
||||
pub fn set_metrics(&mut self, font_system: &mut FontSystem, metrics: Metrics) {
|
||||
self.set_metrics_and_size(font_system, metrics, self.width_opt, self.height_opt);
|
||||
pub fn set_metrics(&mut self, metrics: Metrics) {
|
||||
if metrics != self.metrics {
|
||||
assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
|
||||
assert_ne!(metrics.line_height, 0.0, "line height cannot be 0");
|
||||
self.metrics = metrics;
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current [`Hinting`] strategy.
|
||||
|
|
@ -685,11 +731,11 @@ impl Buffer {
|
|||
}
|
||||
|
||||
/// Set the current [`Hinting`] strategy.
|
||||
pub fn set_hinting(&mut self, font_system: &mut FontSystem, hinting: Hinting) {
|
||||
pub fn set_hinting(&mut self, hinting: Hinting) {
|
||||
if hinting != self.hinting {
|
||||
self.hinting = hinting;
|
||||
self.relayout(font_system);
|
||||
self.shape_until_scroll(font_system, false);
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -698,12 +744,12 @@ impl Buffer {
|
|||
self.wrap
|
||||
}
|
||||
|
||||
/// Set the current [`Wrap`]
|
||||
pub fn set_wrap(&mut self, font_system: &mut FontSystem, wrap: Wrap) {
|
||||
/// Set the current [`Wrap`].
|
||||
pub fn set_wrap(&mut self, wrap: Wrap) {
|
||||
if wrap != self.wrap {
|
||||
self.wrap = wrap;
|
||||
self.relayout(font_system);
|
||||
self.shape_until_scroll(font_system, false);
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -712,12 +758,12 @@ impl Buffer {
|
|||
self.ellipsize
|
||||
}
|
||||
|
||||
/// Set the current [`Ellipsize`]
|
||||
pub fn set_ellipsize(&mut self, font_system: &mut FontSystem, ellipsize: Ellipsize) {
|
||||
/// Set the current [`Ellipsize`].
|
||||
pub fn set_ellipsize(&mut self, ellipsize: Ellipsize) {
|
||||
if ellipsize != self.ellipsize {
|
||||
self.ellipsize = ellipsize;
|
||||
self.relayout(font_system);
|
||||
self.shape_until_scroll(font_system, false);
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -726,16 +772,12 @@ impl Buffer {
|
|||
self.monospace_width
|
||||
}
|
||||
|
||||
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize
|
||||
pub fn set_monospace_width(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
monospace_width: Option<f32>,
|
||||
) {
|
||||
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize.
|
||||
pub fn set_monospace_width(&mut self, monospace_width: Option<f32>) {
|
||||
if monospace_width != self.monospace_width {
|
||||
self.monospace_width = monospace_width;
|
||||
self.relayout(font_system);
|
||||
self.shape_until_scroll(font_system, false);
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -744,22 +786,15 @@ impl Buffer {
|
|||
self.tab_width
|
||||
}
|
||||
|
||||
/// Set tab width (number of spaces between tab stops)
|
||||
pub fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
||||
// A tab width of 0 is not allowed
|
||||
/// Set tab width (number of spaces between tab stops).
|
||||
pub fn set_tab_width(&mut self, tab_width: u16) {
|
||||
if tab_width == 0 {
|
||||
return;
|
||||
}
|
||||
if tab_width != self.tab_width {
|
||||
self.tab_width = tab_width;
|
||||
// Shaping must be reset when tab width is changed
|
||||
for line in &mut self.lines {
|
||||
if line.shape_opt().is_some() && line.text().contains('\t') {
|
||||
line.reset_shaping();
|
||||
}
|
||||
}
|
||||
self.dirty |= DirtyFlags::TAB_SHAPE | DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
self.shape_until_scroll(font_system, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -768,42 +803,35 @@ impl Buffer {
|
|||
(self.width_opt, self.height_opt)
|
||||
}
|
||||
|
||||
/// Set the current buffer dimensions
|
||||
pub fn set_size(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
width_opt: Option<f32>,
|
||||
height_opt: Option<f32>,
|
||||
) {
|
||||
self.set_metrics_and_size(font_system, self.metrics, width_opt, height_opt);
|
||||
/// Set the current buffer dimensions.
|
||||
pub fn set_size(&mut self, width_opt: Option<f32>, height_opt: Option<f32>) {
|
||||
let width_clamped = width_opt.map(|v| v.max(0.0));
|
||||
let height_clamped = height_opt.map(|v| v.max(0.0));
|
||||
if width_clamped != self.width_opt {
|
||||
self.width_opt = width_clamped;
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
if height_clamped != self.height_opt {
|
||||
self.height_opt = height_clamped;
|
||||
self.dirty |= DirtyFlags::RELAYOUT;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the current [`Metrics`] and buffer dimensions at the same time
|
||||
/// Set the current [`Metrics`] and buffer dimensions at the same time.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if `metrics.font_size` is zero.
|
||||
pub fn set_metrics_and_size(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
metrics: Metrics,
|
||||
width_opt: Option<f32>,
|
||||
height_opt: Option<f32>,
|
||||
) {
|
||||
let clamped_width_opt = width_opt.map(|width| width.max(0.0));
|
||||
let clamped_height_opt = height_opt.map(|height| height.max(0.0));
|
||||
|
||||
if metrics != self.metrics
|
||||
|| clamped_width_opt != self.width_opt
|
||||
|| clamped_height_opt != self.height_opt
|
||||
{
|
||||
assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
|
||||
self.metrics = metrics;
|
||||
self.width_opt = clamped_width_opt;
|
||||
self.height_opt = clamped_height_opt;
|
||||
self.relayout(font_system);
|
||||
self.shape_until_scroll(font_system, false);
|
||||
}
|
||||
self.set_metrics(metrics);
|
||||
self.set_size(width_opt, height_opt);
|
||||
}
|
||||
|
||||
/// Get the current scroll location
|
||||
|
|
@ -815,45 +843,73 @@ impl Buffer {
|
|||
pub fn set_scroll(&mut self, scroll: Scroll) {
|
||||
if scroll != self.scroll {
|
||||
self.scroll = scroll;
|
||||
self.dirty |= DirtyFlags::SCROLL;
|
||||
self.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set text of buffer, using provided attributes for each line by default
|
||||
pub fn set_text(
|
||||
/// Internal: set text of buffer, reusing existing line allocations.
|
||||
///
|
||||
/// Does NOT call `shape_until_scroll` — the caller is responsible for that.
|
||||
fn set_text_impl(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
text: &str,
|
||||
attrs: &Attrs,
|
||||
shaping: Shaping,
|
||||
alignment: Option<Align>,
|
||||
) {
|
||||
self.lines.clear();
|
||||
let mut line_count = 0;
|
||||
for (range, ending) in LineIter::new(text) {
|
||||
self.lines.push(BufferLine::new(
|
||||
&text[range],
|
||||
ending,
|
||||
AttrsList::new(attrs),
|
||||
shaping,
|
||||
));
|
||||
let line_text = &text[range];
|
||||
if line_count < self.lines.len() {
|
||||
// Reuse existing line: reclaim String/AttrsList allocations
|
||||
let mut reused_text = self.lines[line_count].reclaim_text();
|
||||
reused_text.push_str(line_text);
|
||||
let reused_attrs = self.lines[line_count].reclaim_attrs().reset(attrs);
|
||||
self.lines[line_count].reset_new(reused_text, ending, reused_attrs, shaping);
|
||||
} else {
|
||||
self.lines.push(BufferLine::new(
|
||||
line_text,
|
||||
ending,
|
||||
AttrsList::new(attrs),
|
||||
shaping,
|
||||
));
|
||||
}
|
||||
line_count += 1;
|
||||
}
|
||||
|
||||
// Ensure there is an ending line with no line ending
|
||||
if self
|
||||
.lines
|
||||
.last()
|
||||
.map(|line| line.ending())
|
||||
.unwrap_or_default()
|
||||
!= LineEnding::None
|
||||
{
|
||||
self.lines.push(BufferLine::new(
|
||||
"",
|
||||
LineEnding::None,
|
||||
AttrsList::new(attrs),
|
||||
shaping,
|
||||
));
|
||||
// Ensure there is an ending line with no line ending.
|
||||
// When no lines were produced (empty text), unwrap_or_default() returns
|
||||
// LineEnding::Lf (the Default), which is != None, so we add an empty line.
|
||||
let last_ending = if line_count > 0 {
|
||||
self.lines[line_count - 1].ending()
|
||||
} else {
|
||||
LineEnding::default()
|
||||
};
|
||||
if last_ending != LineEnding::None {
|
||||
if line_count < self.lines.len() {
|
||||
let reused_text = self.lines[line_count].reclaim_text();
|
||||
let reused_attrs = self.lines[line_count].reclaim_attrs().reset(attrs);
|
||||
self.lines[line_count].reset_new(
|
||||
reused_text,
|
||||
LineEnding::None,
|
||||
reused_attrs,
|
||||
shaping,
|
||||
);
|
||||
} else {
|
||||
self.lines.push(BufferLine::new(
|
||||
"",
|
||||
LineEnding::None,
|
||||
AttrsList::new(attrs),
|
||||
shaping,
|
||||
));
|
||||
}
|
||||
line_count += 1;
|
||||
}
|
||||
|
||||
// Discard excess lines now that we have reused as much of the existing allocations as possible.
|
||||
self.lines.truncate(line_count);
|
||||
|
||||
if alignment.is_some() {
|
||||
self.lines.iter_mut().for_each(|line| {
|
||||
line.set_align(alignment);
|
||||
|
|
@ -861,30 +917,26 @@ impl Buffer {
|
|||
}
|
||||
|
||||
self.scroll = Scroll::default();
|
||||
self.shape_until_scroll(font_system, false);
|
||||
}
|
||||
|
||||
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
|
||||
///
|
||||
/// ```
|
||||
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
|
||||
/// # let mut font_system = FontSystem::new();
|
||||
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
|
||||
/// let attrs = Attrs::new().family(Family::Serif);
|
||||
/// buffer.set_rich_text(
|
||||
/// &mut font_system,
|
||||
/// [
|
||||
/// ("hello, ", attrs.clone()),
|
||||
/// ("cosmic\ntext", attrs.clone().family(Family::Monospace)),
|
||||
/// ],
|
||||
/// &attrs,
|
||||
/// Shaping::Advanced,
|
||||
/// None,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn set_rich_text<'r, 's, I>(
|
||||
/// Set text of buffer, using provided attributes for each line by default.
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
text: &str,
|
||||
attrs: &Attrs,
|
||||
shaping: Shaping,
|
||||
alignment: Option<Align>,
|
||||
) {
|
||||
self.set_text_impl(text, attrs, shaping, alignment);
|
||||
self.dirty |= DirtyFlags::TEXT_SET;
|
||||
self.redraw = true;
|
||||
}
|
||||
|
||||
/// Internal: set rich text of buffer, reusing existing line allocations.
|
||||
///
|
||||
/// Does NOT call `shape_until_scroll` — the caller is responsible for that.
|
||||
fn set_rich_text_impl<'r, 's, I>(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
spans: I,
|
||||
default_attrs: &Attrs,
|
||||
shaping: Shaping,
|
||||
|
|
@ -1017,8 +1069,37 @@ impl Buffer {
|
|||
});
|
||||
|
||||
self.scroll = Scroll::default();
|
||||
}
|
||||
|
||||
self.shape_until_scroll(font_system, false);
|
||||
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes).
|
||||
///
|
||||
/// ```
|
||||
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
|
||||
/// # let mut font_system = FontSystem::new();
|
||||
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
|
||||
/// let attrs = Attrs::new().family(Family::Serif);
|
||||
/// buffer.set_rich_text(
|
||||
/// [
|
||||
/// ("hello, ", attrs.clone()),
|
||||
/// ("cosmic\ntext", attrs.clone().family(Family::Monospace)),
|
||||
/// ],
|
||||
/// &attrs,
|
||||
/// Shaping::Advanced,
|
||||
/// None,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn set_rich_text<'r, 's, I>(
|
||||
&mut self,
|
||||
spans: I,
|
||||
default_attrs: &Attrs,
|
||||
shaping: Shaping,
|
||||
alignment: Option<Align>,
|
||||
) where
|
||||
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
|
||||
{
|
||||
self.set_rich_text_impl(spans, default_attrs, shaping, alignment);
|
||||
self.dirty |= DirtyFlags::TEXT_SET;
|
||||
self.redraw = true;
|
||||
}
|
||||
|
||||
/// True if a redraw is needed
|
||||
|
|
@ -1031,12 +1112,24 @@ impl Buffer {
|
|||
self.redraw = redraw;
|
||||
}
|
||||
|
||||
/// Get the visible layout runs for rendering and other tasks
|
||||
/// Get the visible layout runs for rendering and other tasks.
|
||||
///
|
||||
/// This returns an iterator over the laid-out runs that are visible in the
|
||||
/// current scroll region. Call [`shape_until_scroll`] first to ensure the buffer
|
||||
/// is up to date, or use [`BorrowedWithFontSystem`] which calls it
|
||||
/// automatically.
|
||||
///
|
||||
/// [`shape_until_scroll`]: Self::shape_until_scroll
|
||||
pub fn layout_runs(&self) -> LayoutRunIter<'_> {
|
||||
LayoutRunIter::new(self)
|
||||
}
|
||||
|
||||
/// Convert x, y position to Cursor (hit detection)
|
||||
/// Convert x, y position to Cursor (hit detection).
|
||||
///
|
||||
/// Call [`shape_until_scroll`] first to ensure the buffer is up to date,
|
||||
/// or use [`BorrowedWithFontSystem`] which calls it automatically.
|
||||
///
|
||||
/// [`shape_until_scroll`]: Self::shape_until_scroll
|
||||
pub fn hit(&self, x: f32, y: f32) -> Option<Cursor> {
|
||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
||||
let instant = std::time::Instant::now();
|
||||
|
|
@ -1478,10 +1571,12 @@ impl Buffer {
|
|||
Some((cursor, cursor_x_opt))
|
||||
}
|
||||
|
||||
/// Draw the buffer
|
||||
/// Draw the buffer.
|
||||
///
|
||||
/// Automatically resolves any pending dirty state before drawing.
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(
|
||||
&self,
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
cache: &mut crate::SwashCache,
|
||||
color: Color,
|
||||
|
|
@ -1489,15 +1584,32 @@ impl Buffer {
|
|||
) where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
self.shape_until_scroll(font_system, false);
|
||||
let mut renderer = crate::LegacyRenderer {
|
||||
font_system,
|
||||
cache,
|
||||
callback,
|
||||
};
|
||||
self.render(&mut renderer, color);
|
||||
for run in self.layout_runs() {
|
||||
for glyph in run.glyphs {
|
||||
let physical_glyph = glyph.physical((0., run.line_y), 1.0);
|
||||
let glyph_color = glyph.color_opt.map_or(color, |some| some);
|
||||
renderer.glyph(physical_glyph, glyph_color);
|
||||
}
|
||||
render_decoration(&mut renderer, &run, color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<R: Renderer>(&self, renderer: &mut R, color: Color) {
|
||||
/// Render the buffer using the provided renderer.
|
||||
///
|
||||
/// Automatically resolves any pending dirty state before rendering.
|
||||
pub fn render<R: Renderer>(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
renderer: &mut R,
|
||||
color: Color,
|
||||
) {
|
||||
self.shape_until_scroll(font_system, false);
|
||||
for run in self.layout_runs() {
|
||||
for glyph in run.glyphs {
|
||||
let physical_glyph = glyph.physical((0., run.line_y), 1.0);
|
||||
|
|
@ -1517,11 +1629,6 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
|||
.shape_until_cursor(self.font_system, cursor, prune);
|
||||
}
|
||||
|
||||
/// Shape lines until scroll
|
||||
pub fn shape_until_scroll(&mut self, prune: bool) {
|
||||
self.inner.shape_until_scroll(self.font_system, prune);
|
||||
}
|
||||
|
||||
/// Shape the provided line index and return the result
|
||||
pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
|
||||
self.inner.line_shape(self.font_system, line_i)
|
||||
|
|
@ -1532,31 +1639,36 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
|||
self.inner.line_layout(self.font_system, line_i)
|
||||
}
|
||||
|
||||
/// Set the current [`Metrics`]
|
||||
/// Set the current [`Metrics`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if `metrics.font_size` is zero.
|
||||
pub fn set_metrics(&mut self, metrics: Metrics) {
|
||||
self.inner.set_metrics(self.font_system, metrics);
|
||||
self.inner.set_metrics(metrics);
|
||||
}
|
||||
|
||||
/// Set the current [`Wrap`]
|
||||
/// Set the current [`Hinting`] strategy.
|
||||
pub fn set_hinting(&mut self, hinting: Hinting) {
|
||||
self.inner.set_hinting(hinting);
|
||||
}
|
||||
|
||||
/// Set the current [`Wrap`].
|
||||
pub fn set_wrap(&mut self, wrap: Wrap) {
|
||||
self.inner.set_wrap(self.font_system, wrap);
|
||||
self.inner.set_wrap(wrap);
|
||||
}
|
||||
|
||||
/// Set the current [`Ellipsize`]
|
||||
/// Set the current [`Ellipsize`].
|
||||
pub fn set_ellipsize(&mut self, ellipsize: Ellipsize) {
|
||||
self.inner.set_ellipsize(self.font_system, ellipsize);
|
||||
self.inner.set_ellipsize(ellipsize);
|
||||
}
|
||||
|
||||
/// Set the current buffer dimensions
|
||||
/// Set the current buffer dimensions.
|
||||
pub fn set_size(&mut self, width_opt: Option<f32>, height_opt: Option<f32>) {
|
||||
self.inner.set_size(self.font_system, width_opt, height_opt);
|
||||
self.inner.set_size(width_opt, height_opt);
|
||||
}
|
||||
|
||||
/// Set the current [`Metrics`] and buffer dimensions at the same time
|
||||
/// Set the current [`Metrics`] and buffer dimensions at the same time.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
|
@ -1568,15 +1680,22 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
|||
height_opt: Option<f32>,
|
||||
) {
|
||||
self.inner
|
||||
.set_metrics_and_size(self.font_system, metrics, width_opt, height_opt);
|
||||
.set_metrics_and_size(metrics, width_opt, height_opt);
|
||||
}
|
||||
|
||||
/// Set tab width (number of spaces between tab stops)
|
||||
/// Set tab width (number of spaces between tab stops).
|
||||
///
|
||||
/// A `tab_width` of 0 is ignored.
|
||||
pub fn set_tab_width(&mut self, tab_width: u16) {
|
||||
self.inner.set_tab_width(self.font_system, tab_width);
|
||||
self.inner.set_tab_width(tab_width);
|
||||
}
|
||||
|
||||
/// Set text of buffer, using provided attributes for each line by default
|
||||
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize.
|
||||
pub fn set_monospace_width(&mut self, monospace_width: Option<f32>) {
|
||||
self.inner.set_monospace_width(monospace_width);
|
||||
}
|
||||
|
||||
/// Set text of buffer, using provided attributes for each line by default.
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
text: &str,
|
||||
|
|
@ -1584,28 +1703,10 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
|||
shaping: Shaping,
|
||||
alignment: Option<Align>,
|
||||
) {
|
||||
self.inner
|
||||
.set_text(self.font_system, text, attrs, shaping, alignment);
|
||||
self.inner.set_text(text, attrs, shaping, alignment);
|
||||
}
|
||||
|
||||
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
|
||||
///
|
||||
/// ```
|
||||
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
|
||||
/// # let mut font_system = FontSystem::new();
|
||||
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
|
||||
/// let attrs = Attrs::new().family(Family::Serif);
|
||||
/// buffer.set_rich_text(
|
||||
/// &mut font_system,
|
||||
/// [
|
||||
/// ("hello, ", attrs.clone()),
|
||||
/// ("cosmic\ntext", attrs.clone().family(Family::Monospace)),
|
||||
/// ],
|
||||
/// &attrs,
|
||||
/// Shaping::Advanced,
|
||||
/// None,
|
||||
/// );
|
||||
/// ```
|
||||
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes).
|
||||
pub fn set_rich_text<'r, 's, I>(
|
||||
&mut self,
|
||||
spans: I,
|
||||
|
|
@ -1616,7 +1717,30 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
|||
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
|
||||
{
|
||||
self.inner
|
||||
.set_rich_text(self.font_system, spans, default_attrs, shaping, alignment);
|
||||
.set_rich_text(spans, default_attrs, shaping, alignment);
|
||||
}
|
||||
|
||||
/// Shape lines until scroll, resolving any pending dirty state first.
|
||||
///
|
||||
/// See [`Buffer::shape_until_scroll`].
|
||||
pub fn shape_until_scroll(&mut self, prune: bool) {
|
||||
self.inner.shape_until_scroll(self.font_system, prune);
|
||||
}
|
||||
|
||||
/// Get the visible layout runs for rendering and other tasks.
|
||||
///
|
||||
/// Automatically resolves any pending dirty state.
|
||||
pub fn layout_runs(&mut self) -> LayoutRunIter<'_> {
|
||||
self.inner.shape_until_scroll(self.font_system, false);
|
||||
self.inner.layout_runs()
|
||||
}
|
||||
|
||||
/// Convert x, y position to Cursor (hit detection).
|
||||
///
|
||||
/// Automatically resolves any pending dirty state.
|
||||
pub fn hit(&mut self, x: f32, y: f32) -> Option<Cursor> {
|
||||
self.inner.shape_until_scroll(self.font_system, false);
|
||||
self.inner.hit(x, y)
|
||||
}
|
||||
|
||||
/// Apply a [`Motion`] to a [`Cursor`]
|
||||
|
|
@ -1630,7 +1754,9 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
|||
.cursor_motion(self.font_system, cursor, cursor_x_opt, motion)
|
||||
}
|
||||
|
||||
/// Draw the buffer
|
||||
/// Draw the buffer.
|
||||
///
|
||||
/// Automatically resolves any pending dirty state.
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
|
||||
where
|
||||
|
|
|
|||
|
|
@ -47,10 +47,12 @@ impl<'buffer> Editor<'buffer> {
|
|||
}
|
||||
|
||||
/// Draw the editor
|
||||
///
|
||||
/// Automatically resolves any pending dirty state before drawing.
|
||||
#[cfg(feature = "swash")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw<F>(
|
||||
&self,
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
cache: &mut crate::SwashCache,
|
||||
text_color: Color,
|
||||
|
|
@ -61,6 +63,7 @@ impl<'buffer> Editor<'buffer> {
|
|||
) where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, false));
|
||||
let mut renderer = crate::LegacyRenderer {
|
||||
font_system,
|
||||
cache,
|
||||
|
|
@ -75,6 +78,10 @@ impl<'buffer> Editor<'buffer> {
|
|||
);
|
||||
}
|
||||
|
||||
/// Render the editor using the provided renderer.
|
||||
///
|
||||
/// The caller is responsible for calling [`Edit::shape_as_needed`] first
|
||||
/// to ensure layout is up to date.
|
||||
pub fn render<R: Renderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
|
|
@ -207,8 +214,8 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
|||
self.with_buffer(super::super::buffer::Buffer::tab_width)
|
||||
}
|
||||
|
||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
||||
self.with_buffer_mut(|buffer| buffer.set_tab_width(font_system, tab_width));
|
||||
fn set_tab_width(&mut self, tab_width: u16) {
|
||||
self.with_buffer_mut(|buffer| buffer.set_tab_width(tab_width));
|
||||
}
|
||||
|
||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
|
||||
|
|
@ -764,8 +771,10 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
|||
Action::Click { x, y } => {
|
||||
self.set_selection(Selection::None);
|
||||
|
||||
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
|
||||
{
|
||||
if let Some(new_cursor) = self.with_buffer_mut(|buffer| {
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
buffer.hit(x as f32, y as f32)
|
||||
}) {
|
||||
if new_cursor != self.cursor {
|
||||
self.cursor = new_cursor;
|
||||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||
|
|
@ -775,8 +784,10 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
|||
Action::DoubleClick { x, y } => {
|
||||
self.set_selection(Selection::None);
|
||||
|
||||
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
|
||||
{
|
||||
if let Some(new_cursor) = self.with_buffer_mut(|buffer| {
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
buffer.hit(x as f32, y as f32)
|
||||
}) {
|
||||
if new_cursor != self.cursor {
|
||||
self.cursor = new_cursor;
|
||||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||
|
|
@ -788,8 +799,10 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
|||
Action::TripleClick { x, y } => {
|
||||
self.set_selection(Selection::None);
|
||||
|
||||
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
|
||||
{
|
||||
if let Some(new_cursor) = self.with_buffer_mut(|buffer| {
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
buffer.hit(x as f32, y as f32)
|
||||
}) {
|
||||
if new_cursor != self.cursor {
|
||||
self.cursor = new_cursor;
|
||||
}
|
||||
|
|
@ -803,8 +816,10 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
|||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||
}
|
||||
|
||||
if let Some(new_cursor) = self.with_buffer(|buffer| buffer.hit(x as f32, y as f32))
|
||||
{
|
||||
if let Some(new_cursor) = self.with_buffer_mut(|buffer| {
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
buffer.hit(x as f32, y as f32)
|
||||
}) {
|
||||
if new_cursor != self.cursor {
|
||||
self.cursor = new_cursor;
|
||||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ pub trait Edit<'buffer> {
|
|||
fn tab_width(&self) -> u16;
|
||||
|
||||
/// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored
|
||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16);
|
||||
fn set_tab_width(&mut self, tab_width: u16);
|
||||
|
||||
/// Shape lines until scroll, after adjusting scroll if the cursor moved
|
||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool);
|
||||
|
|
@ -351,7 +351,7 @@ impl<'buffer, E: Edit<'buffer>> BorrowedWithFontSystem<'_, E> {
|
|||
|
||||
/// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored
|
||||
pub fn set_tab_width(&mut self, tab_width: u16) {
|
||||
self.inner.set_tab_width(self.font_system, tab_width);
|
||||
self.inner.set_tab_width(tab_width);
|
||||
}
|
||||
|
||||
/// Shape lines until scroll, after adjusting scroll if the cursor moved
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
|||
#[cfg(feature = "std")]
|
||||
pub fn load_text<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
_font_system: &mut FontSystem,
|
||||
path: P,
|
||||
mut attrs: crate::Attrs,
|
||||
) -> io::Result<()> {
|
||||
|
|
@ -125,7 +125,7 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
|||
|
||||
// Clear buffer first (allows sane handling of non-existant files)
|
||||
self.editor.with_buffer_mut(|buffer| {
|
||||
buffer.set_text(font_system, "", &attrs, Shaping::Advanced, None);
|
||||
buffer.set_text("", &attrs, Shaping::Advanced, None);
|
||||
});
|
||||
|
||||
// Update syntax based on file name
|
||||
|
|
@ -147,7 +147,7 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
|||
// Set text
|
||||
let text = fs::read_to_string(path)?;
|
||||
self.editor.with_buffer_mut(|buffer| {
|
||||
buffer.set_text(font_system, &text, &attrs, Shaping::Advanced, None);
|
||||
buffer.set_text(&text, &attrs, Shaping::Advanced, None);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
@ -218,19 +218,32 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
|||
}
|
||||
|
||||
/// Draw the editor
|
||||
///
|
||||
/// Automatically resolves any pending dirty state before drawing.
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, callback: F)
|
||||
where
|
||||
pub fn draw<F>(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
cache: &mut crate::SwashCache,
|
||||
callback: F,
|
||||
) where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
let mut renderer = crate::LegacyRenderer {
|
||||
self.editor.draw(
|
||||
font_system,
|
||||
cache,
|
||||
self.foreground_color(),
|
||||
self.cursor_color(),
|
||||
self.selection_color(),
|
||||
self.foreground_color(),
|
||||
callback,
|
||||
};
|
||||
self.render(&mut renderer);
|
||||
);
|
||||
}
|
||||
|
||||
/// Render the editor using the provided renderer.
|
||||
///
|
||||
/// The caller is responsible for calling [`Edit::shape_as_needed`] first
|
||||
/// to ensure layout is up to date.
|
||||
pub fn render<R: Renderer>(&self, renderer: &mut R) {
|
||||
let size = self.with_buffer(|buffer| buffer.size());
|
||||
if let Some(width) = size.0 {
|
||||
|
|
@ -285,8 +298,8 @@ impl<'buffer> Edit<'buffer> for SyntaxEditor<'_, 'buffer> {
|
|||
self.editor.tab_width()
|
||||
}
|
||||
|
||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
||||
self.editor.set_tab_width(font_system, tab_width);
|
||||
fn set_tab_width(&mut self, tab_width: u16) {
|
||||
self.editor.set_tab_width(tab_width);
|
||||
}
|
||||
|
||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
|
||||
|
|
|
|||
|
|
@ -302,11 +302,19 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
|||
self.changed = eval_changed(&self.commands, self.save_pivot);
|
||||
}
|
||||
|
||||
/// Draw the editor.
|
||||
///
|
||||
/// Automatically resolves any pending dirty state before drawing.
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, callback: F)
|
||||
where
|
||||
pub fn draw<F>(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
cache: &mut crate::SwashCache,
|
||||
callback: F,
|
||||
) where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, false));
|
||||
let mut renderer = crate::LegacyRenderer {
|
||||
font_system,
|
||||
cache,
|
||||
|
|
@ -315,6 +323,10 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
|||
self.render(&mut renderer);
|
||||
}
|
||||
|
||||
/// Render the editor using the provided renderer.
|
||||
///
|
||||
/// The caller is responsible for calling [`Edit::shape_as_needed`] first
|
||||
/// to ensure layout is up to date.
|
||||
pub fn render<R: Renderer>(&self, renderer: &mut R) {
|
||||
let background_color = self.background_color();
|
||||
let foreground_color = self.foreground_color();
|
||||
|
|
@ -553,8 +565,8 @@ impl<'buffer> Edit<'buffer> for ViEditor<'_, 'buffer> {
|
|||
self.editor.tab_width()
|
||||
}
|
||||
|
||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
||||
self.editor.set_tab_width(font_system, tab_width);
|
||||
fn set_tab_width(&mut self, tab_width: u16) {
|
||||
self.editor.set_tab_width(tab_width);
|
||||
}
|
||||
|
||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
|
||||
|
|
|
|||
|
|
@ -29,18 +29,13 @@
|
|||
//! // Borrow buffer together with the font system for more convenient method calls
|
||||
//! let mut buffer = buffer.borrow_with(&mut font_system);
|
||||
//!
|
||||
//! // Set a size for the text buffer, in pixels
|
||||
//! buffer.set_size(Some(80.0), Some(25.0));
|
||||
//!
|
||||
//! // Attributes indicate what font to choose
|
||||
//! let attrs = Attrs::new();
|
||||
//!
|
||||
//! // Add some text!
|
||||
//! // Set size and text
|
||||
//! buffer.set_size(Some(80.0), Some(25.0));
|
||||
//! buffer.set_text("Hello, Rust! 🦀\n", &attrs, Shaping::Advanced, None);
|
||||
//!
|
||||
//! // Perform shaping as desired
|
||||
//! buffer.shape_until_scroll(true);
|
||||
//!
|
||||
//! // Inspect the output runs
|
||||
//! for run in buffer.layout_runs() {
|
||||
//! for glyph in run.glyphs.iter() {
|
||||
|
|
|
|||
|
|
@ -1262,6 +1262,11 @@ impl ShapeLine {
|
|||
shaping: Shaping,
|
||||
tab_width: u16,
|
||||
) {
|
||||
// Clear stale ellipsis span so it gets recomputed with the current attrs.
|
||||
// Without this, reusing a ShapeLine from a previous text (via Cached::Unused)
|
||||
// would keep an ellipsis shaped with the old attrs.
|
||||
self.ellipsis_span = None;
|
||||
|
||||
let mut spans = mem::take(&mut self.spans);
|
||||
|
||||
// Cache the shape spans in reverse order so they can be popped for reuse in the same order.
|
||||
|
|
|
|||
|
|
@ -149,7 +149,6 @@ impl DrawTestCfg {
|
|||
self.alignment,
|
||||
);
|
||||
}
|
||||
buffer.shape_until_scroll(true);
|
||||
|
||||
// Black
|
||||
let text_color = Color::rgb(0x00, 0x00, 0x00);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ fn empty_lines_use_span_metrics() {
|
|||
None,
|
||||
);
|
||||
buffer.set_size(Some(500.0), Some(500.0));
|
||||
buffer.shape_until_scroll(false);
|
||||
|
||||
let line_heights: Vec<f32> = buffer.layout_runs().map(|run| run.line_height).collect();
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ fn test_ligature_segmentation() {
|
|||
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||
|
||||
buffer.set_text("|>", &Attrs::new(), Shaping::Advanced, None);
|
||||
buffer.shape_until_scroll(false);
|
||||
let _ = buffer.layout_runs();
|
||||
|
||||
let line = &buffer.lines[0];
|
||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||
|
|
@ -105,7 +105,7 @@ fn test_ligature_segmentation() {
|
|||
|
||||
// Test -> (Arrow), which is a common ligature.
|
||||
buffer.set_text("->", &Attrs::new(), Shaping::Advanced, None);
|
||||
buffer.shape_until_scroll(false);
|
||||
let _ = buffer.layout_runs();
|
||||
let line = &buffer.lines[0];
|
||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ fn test_ligature_segmentation() {
|
|||
|
||||
// Test !=
|
||||
buffer.set_text("!=", &Attrs::new(), Shaping::Advanced, None);
|
||||
buffer.shape_until_scroll(false);
|
||||
let _ = buffer.layout_runs();
|
||||
let line = &buffer.lines[0];
|
||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||
// Inter has a contextual alternate for != too.
|
||||
|
|
@ -131,7 +131,7 @@ fn test_ligature_segmentation() {
|
|||
|
||||
// Test ++
|
||||
buffer.set_text("++", &Attrs::new(), Shaping::Advanced, None);
|
||||
buffer.shape_until_scroll(false);
|
||||
let _ = buffer.layout_runs();
|
||||
let line = &buffer.lines[0];
|
||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||
// Inter does not have a ++ ligature.
|
||||
|
|
|
|||
|
|
@ -117,15 +117,10 @@ fn wrap_extra_line() {
|
|||
|
||||
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||
|
||||
// Add some text!
|
||||
// Configure wrap and size, then add text
|
||||
buffer.set_wrap(Wrap::Word);
|
||||
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing\n\nweeewoooo minim sint cillum sint consectetur cupidatat.", &Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced, None);
|
||||
|
||||
// Set a size for the text buffer, in pixels
|
||||
buffer.set_size(Some(50.0), Some(1000.0));
|
||||
|
||||
// Perform shaping as desired
|
||||
buffer.shape_until_scroll(false);
|
||||
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing\n\nweeewoooo minim sint cillum sint consectetur cupidatat.", &Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced, None);
|
||||
|
||||
let empty_lines = buffer.layout_runs().filter(|x| x.line_w == 0.).count();
|
||||
let overflow_lines = buffer.layout_runs().filter(|x| x.line_w > 50.).count();
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ fn wrap_word_fallback() {
|
|||
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||
|
||||
buffer.set_wrap(Wrap::WordOrGlyph);
|
||||
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", &Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced, None);
|
||||
buffer.set_size(Some(50.0), Some(1000.0));
|
||||
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", &Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced, None);
|
||||
|
||||
buffer.shape_until_scroll(false);
|
||||
|
||||
let measured_size = measure(&buffer);
|
||||
let measured_size = buffer
|
||||
.layout_runs()
|
||||
.fold(0.0f32, |width, run| width.max(run.line_w));
|
||||
|
||||
assert!(
|
||||
measured_size <= buffer.size().0.unwrap_or(0.0),
|
||||
|
|
@ -29,9 +29,3 @@ fn wrap_word_fallback() {
|
|||
buffer.size().0.unwrap_or(0.0)
|
||||
);
|
||||
}
|
||||
|
||||
fn measure(buffer: &Buffer) -> f32 {
|
||||
buffer
|
||||
.layout_runs()
|
||||
.fold(0.0f32, |width, run| width.max(run.line_w))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue