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) {
|
fn layout(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(10.0, 10.0));
|
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 &[
|
for (wrap_name, wrap) in &[
|
||||||
("None", ct::Wrap::None),
|
("None", ct::Wrap::None),
|
||||||
|
|
@ -22,11 +22,11 @@ fn layout(c: &mut Criterion) {
|
||||||
("Advanced", ct::Shaping::Advanced),
|
("Advanced", ct::Shaping::Advanced),
|
||||||
] {
|
] {
|
||||||
let mut group = c.benchmark_group(format!("Wrap({wrap_name}, {shape_name})"));
|
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| {
|
let mut run_on_text = |text: &str| {
|
||||||
buffer.lines.clear();
|
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);
|
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) {
|
fn bench_ascii_fast_path(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
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);
|
let ascii_text = "Pure ASCII text for BidiParagraphs optimization testing.\n".repeat(50);
|
||||||
|
|
||||||
c.bench_function("ShapeLine/ASCII Fast Path", |b| {
|
c.bench_function("ShapeLine/ASCII Fast Path", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
&mut fs,
|
|
||||||
black_box(&ascii_text),
|
black_box(&ascii_text),
|
||||||
&ct::Attrs::new(),
|
&ct::Attrs::new(),
|
||||||
ct::Shaping::Advanced,
|
ct::Shaping::Advanced,
|
||||||
|
|
@ -26,14 +25,13 @@ fn bench_ascii_fast_path(c: &mut Criterion) {
|
||||||
fn bench_bidi_processing(c: &mut Criterion) {
|
fn bench_bidi_processing(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
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);
|
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| {
|
c.bench_function("ShapeLine/BiDi Processing", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
&mut fs,
|
|
||||||
black_box(&bidi_text),
|
black_box(&bidi_text),
|
||||||
&ct::Attrs::new(),
|
&ct::Attrs::new(),
|
||||||
ct::Shaping::Advanced,
|
ct::Shaping::Advanced,
|
||||||
|
|
@ -47,7 +45,7 @@ fn bench_bidi_processing(c: &mut Criterion) {
|
||||||
fn bench_lang_mixed(c: &mut Criterion) {
|
fn bench_lang_mixed(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
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");
|
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| {
|
.bench_function("ShapeLine/Mixed-Language Text", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
&mut fs,
|
|
||||||
black_box(&bidi_text),
|
black_box(&bidi_text),
|
||||||
&ct::Attrs::new(),
|
&ct::Attrs::new(),
|
||||||
ct::Shaping::Advanced,
|
ct::Shaping::Advanced,
|
||||||
|
|
@ -70,14 +67,13 @@ fn bench_lang_mixed(c: &mut Criterion) {
|
||||||
fn bench_layout_heavy(c: &mut Criterion) {
|
fn bench_layout_heavy(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
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);
|
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| {
|
c.bench_function("ShapeLine/Layout Heavy", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
&mut fs,
|
|
||||||
black_box(&layout_text),
|
black_box(&layout_text),
|
||||||
&ct::Attrs::new(),
|
&ct::Attrs::new(),
|
||||||
ct::Shaping::Advanced,
|
ct::Shaping::Advanced,
|
||||||
|
|
@ -91,7 +87,7 @@ fn bench_layout_heavy(c: &mut Criterion) {
|
||||||
fn bench_combined_stress(c: &mut Criterion) {
|
fn bench_combined_stress(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
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",
|
let stress_text = format!("{}\n{}\n{}\n{}\n",
|
||||||
"ASCII line for BidiParagraphs optimization. ".repeat(15),
|
"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| {
|
c.bench_function("ShapeLine/Combined Stress", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
&mut fs,
|
|
||||||
black_box(&stress_text),
|
black_box(&stress_text),
|
||||||
&ct::Attrs::new(),
|
&ct::Attrs::new(),
|
||||||
ct::Shaping::Advanced,
|
ct::Shaping::Advanced,
|
||||||
|
|
|
||||||
466
src/buffer.rs
466
src/buffer.rs
|
|
@ -16,6 +16,21 @@ use crate::{
|
||||||
Wrap,
|
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
|
/// A line of visible text for rendering
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LayoutRun<'a> {
|
pub struct LayoutRun<'a> {
|
||||||
|
|
@ -330,6 +345,8 @@ pub struct Buffer {
|
||||||
monospace_width: Option<f32>,
|
monospace_width: Option<f32>,
|
||||||
tab_width: u16,
|
tab_width: u16,
|
||||||
hinting: Hinting,
|
hinting: Hinting,
|
||||||
|
/// Dirty flags tracking which properties changed since last layout
|
||||||
|
dirty: DirtyFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Buffer {
|
impl Clone for Buffer {
|
||||||
|
|
@ -346,6 +363,7 @@ impl Clone for Buffer {
|
||||||
monospace_width: self.monospace_width,
|
monospace_width: self.monospace_width,
|
||||||
tab_width: self.tab_width,
|
tab_width: self.tab_width,
|
||||||
hinting: self.hinting,
|
hinting: self.hinting,
|
||||||
|
dirty: self.dirty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -376,6 +394,7 @@ impl Buffer {
|
||||||
monospace_width: None,
|
monospace_width: None,
|
||||||
tab_width: 8,
|
tab_width: 8,
|
||||||
hinting: Hinting::default(),
|
hinting: Hinting::default(),
|
||||||
|
dirty: DirtyFlags::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +405,8 @@ impl Buffer {
|
||||||
/// Will panic if `metrics.line_height` is zero.
|
/// Will panic if `metrics.line_height` is zero.
|
||||||
pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
|
pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
|
||||||
let mut buffer = Self::new_empty(metrics);
|
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
|
buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,30 +421,36 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn relayout(&mut self, font_system: &mut FontSystem) {
|
/// Process dirty flags: invalidate shape/layout caches as needed, then clear flags.
|
||||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
/// Returns `true` if any flags were set (i.e., work may be needed).
|
||||||
let instant = std::time::Instant::now();
|
fn resolve_dirty(&mut self) -> bool {
|
||||||
|
let dirty = self.dirty;
|
||||||
|
if dirty.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for line in &mut self.lines {
|
if dirty.contains(DirtyFlags::TEXT_SET) {
|
||||||
if line.shape_opt().is_some() {
|
// Lines were replaced — already fresh, no cache to invalidate.
|
||||||
line.reset_layout();
|
} else {
|
||||||
line.layout(
|
if dirty.contains(DirtyFlags::TAB_SHAPE) {
|
||||||
font_system,
|
for line in &mut self.lines {
|
||||||
self.metrics.font_size,
|
if line.shape_opt().is_some() && line.text().contains('\t') {
|
||||||
self.width_opt,
|
line.reset_shaping();
|
||||||
self.wrap,
|
}
|
||||||
self.ellipsize,
|
}
|
||||||
self.monospace_width,
|
}
|
||||||
self.tab_width,
|
if dirty.contains(DirtyFlags::RELAYOUT) {
|
||||||
self.hinting,
|
for line in &mut self.lines {
|
||||||
);
|
if line.shape_opt().is_some() {
|
||||||
|
line.reset_layout();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
|
self.dirty = DirtyFlags::empty();
|
||||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
true
|
||||||
log::debug!("relayout: {:?}", instant.elapsed());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shape lines until cursor, also scrolling to include cursor in view
|
/// Shape lines until cursor, also scrolling to include cursor in view
|
||||||
|
|
@ -435,6 +461,7 @@ impl Buffer {
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
prune: bool,
|
prune: bool,
|
||||||
) {
|
) {
|
||||||
|
self.shape_until_scroll(font_system, prune);
|
||||||
let metrics = self.metrics;
|
let metrics = self.metrics;
|
||||||
let old_scroll = self.scroll;
|
let old_scroll = self.scroll;
|
||||||
|
|
||||||
|
|
@ -490,7 +517,7 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if old_scroll != self.scroll {
|
if old_scroll != self.scroll {
|
||||||
self.redraw = true;
|
self.dirty |= DirtyFlags::SCROLL;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.shape_until_scroll(font_system, prune);
|
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)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
|
pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
|
||||||
|
if !self.resolve_dirty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let metrics = self.metrics;
|
let metrics = self.metrics;
|
||||||
let old_scroll = self.scroll;
|
let old_scroll = self.scroll;
|
||||||
|
|
||||||
|
|
@ -670,13 +710,19 @@ impl Buffer {
|
||||||
self.metrics
|
self.metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current [`Metrics`]
|
/// Set the current [`Metrics`].
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Will panic if `metrics.font_size` is zero.
|
/// Will panic if `metrics.font_size` is zero.
|
||||||
pub fn set_metrics(&mut self, font_system: &mut FontSystem, metrics: Metrics) {
|
pub fn set_metrics(&mut self, metrics: Metrics) {
|
||||||
self.set_metrics_and_size(font_system, metrics, self.width_opt, self.height_opt);
|
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.
|
/// Get the current [`Hinting`] strategy.
|
||||||
|
|
@ -685,11 +731,11 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current [`Hinting`] strategy.
|
/// 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 {
|
if hinting != self.hinting {
|
||||||
self.hinting = hinting;
|
self.hinting = hinting;
|
||||||
self.relayout(font_system);
|
self.dirty |= DirtyFlags::RELAYOUT;
|
||||||
self.shape_until_scroll(font_system, false);
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -698,12 +744,12 @@ impl Buffer {
|
||||||
self.wrap
|
self.wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current [`Wrap`]
|
/// Set the current [`Wrap`].
|
||||||
pub fn set_wrap(&mut self, font_system: &mut FontSystem, wrap: Wrap) {
|
pub fn set_wrap(&mut self, wrap: Wrap) {
|
||||||
if wrap != self.wrap {
|
if wrap != self.wrap {
|
||||||
self.wrap = wrap;
|
self.wrap = wrap;
|
||||||
self.relayout(font_system);
|
self.dirty |= DirtyFlags::RELAYOUT;
|
||||||
self.shape_until_scroll(font_system, false);
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -712,12 +758,12 @@ impl Buffer {
|
||||||
self.ellipsize
|
self.ellipsize
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current [`Ellipsize`]
|
/// Set the current [`Ellipsize`].
|
||||||
pub fn set_ellipsize(&mut self, font_system: &mut FontSystem, ellipsize: Ellipsize) {
|
pub fn set_ellipsize(&mut self, ellipsize: Ellipsize) {
|
||||||
if ellipsize != self.ellipsize {
|
if ellipsize != self.ellipsize {
|
||||||
self.ellipsize = ellipsize;
|
self.ellipsize = ellipsize;
|
||||||
self.relayout(font_system);
|
self.dirty |= DirtyFlags::RELAYOUT;
|
||||||
self.shape_until_scroll(font_system, false);
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,16 +772,12 @@ impl Buffer {
|
||||||
self.monospace_width
|
self.monospace_width
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize
|
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize.
|
||||||
pub fn set_monospace_width(
|
pub fn set_monospace_width(&mut self, monospace_width: Option<f32>) {
|
||||||
&mut self,
|
|
||||||
font_system: &mut FontSystem,
|
|
||||||
monospace_width: Option<f32>,
|
|
||||||
) {
|
|
||||||
if monospace_width != self.monospace_width {
|
if monospace_width != self.monospace_width {
|
||||||
self.monospace_width = monospace_width;
|
self.monospace_width = monospace_width;
|
||||||
self.relayout(font_system);
|
self.dirty |= DirtyFlags::RELAYOUT;
|
||||||
self.shape_until_scroll(font_system, false);
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -744,22 +786,15 @@ impl Buffer {
|
||||||
self.tab_width
|
self.tab_width
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set tab width (number of spaces between tab stops)
|
/// Set tab width (number of spaces between tab stops).
|
||||||
pub fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
pub fn set_tab_width(&mut self, tab_width: u16) {
|
||||||
// A tab width of 0 is not allowed
|
|
||||||
if tab_width == 0 {
|
if tab_width == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if tab_width != self.tab_width {
|
if tab_width != self.tab_width {
|
||||||
self.tab_width = tab_width;
|
self.tab_width = tab_width;
|
||||||
// Shaping must be reset when tab width is changed
|
self.dirty |= DirtyFlags::TAB_SHAPE | DirtyFlags::RELAYOUT;
|
||||||
for line in &mut self.lines {
|
|
||||||
if line.shape_opt().is_some() && line.text().contains('\t') {
|
|
||||||
line.reset_shaping();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
self.shape_until_scroll(font_system, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -768,42 +803,35 @@ impl Buffer {
|
||||||
(self.width_opt, self.height_opt)
|
(self.width_opt, self.height_opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current buffer dimensions
|
/// Set the current buffer dimensions.
|
||||||
pub fn set_size(
|
pub fn set_size(&mut self, width_opt: Option<f32>, height_opt: Option<f32>) {
|
||||||
&mut self,
|
let width_clamped = width_opt.map(|v| v.max(0.0));
|
||||||
font_system: &mut FontSystem,
|
let height_clamped = height_opt.map(|v| v.max(0.0));
|
||||||
width_opt: Option<f32>,
|
if width_clamped != self.width_opt {
|
||||||
height_opt: Option<f32>,
|
self.width_opt = width_clamped;
|
||||||
) {
|
self.dirty |= DirtyFlags::RELAYOUT;
|
||||||
self.set_metrics_and_size(font_system, self.metrics, width_opt, height_opt);
|
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
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Will panic if `metrics.font_size` is zero.
|
/// Will panic if `metrics.font_size` is zero.
|
||||||
pub fn set_metrics_and_size(
|
pub fn set_metrics_and_size(
|
||||||
&mut self,
|
&mut self,
|
||||||
font_system: &mut FontSystem,
|
|
||||||
metrics: Metrics,
|
metrics: Metrics,
|
||||||
width_opt: Option<f32>,
|
width_opt: Option<f32>,
|
||||||
height_opt: Option<f32>,
|
height_opt: Option<f32>,
|
||||||
) {
|
) {
|
||||||
let clamped_width_opt = width_opt.map(|width| width.max(0.0));
|
self.set_metrics(metrics);
|
||||||
let clamped_height_opt = height_opt.map(|height| height.max(0.0));
|
self.set_size(width_opt, height_opt);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current scroll location
|
/// Get the current scroll location
|
||||||
|
|
@ -815,45 +843,73 @@ impl Buffer {
|
||||||
pub fn set_scroll(&mut self, scroll: Scroll) {
|
pub fn set_scroll(&mut self, scroll: Scroll) {
|
||||||
if scroll != self.scroll {
|
if scroll != self.scroll {
|
||||||
self.scroll = scroll;
|
self.scroll = scroll;
|
||||||
|
self.dirty |= DirtyFlags::SCROLL;
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set text of buffer, using provided attributes for each line by default
|
/// Internal: set text of buffer, reusing existing line allocations.
|
||||||
pub fn set_text(
|
///
|
||||||
|
/// Does NOT call `shape_until_scroll` — the caller is responsible for that.
|
||||||
|
fn set_text_impl(
|
||||||
&mut self,
|
&mut self,
|
||||||
font_system: &mut FontSystem,
|
|
||||||
text: &str,
|
text: &str,
|
||||||
attrs: &Attrs,
|
attrs: &Attrs,
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
alignment: Option<Align>,
|
alignment: Option<Align>,
|
||||||
) {
|
) {
|
||||||
self.lines.clear();
|
let mut line_count = 0;
|
||||||
for (range, ending) in LineIter::new(text) {
|
for (range, ending) in LineIter::new(text) {
|
||||||
self.lines.push(BufferLine::new(
|
let line_text = &text[range];
|
||||||
&text[range],
|
if line_count < self.lines.len() {
|
||||||
ending,
|
// Reuse existing line: reclaim String/AttrsList allocations
|
||||||
AttrsList::new(attrs),
|
let mut reused_text = self.lines[line_count].reclaim_text();
|
||||||
shaping,
|
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
|
// Ensure there is an ending line with no line ending.
|
||||||
if self
|
// When no lines were produced (empty text), unwrap_or_default() returns
|
||||||
.lines
|
// LineEnding::Lf (the Default), which is != None, so we add an empty line.
|
||||||
.last()
|
let last_ending = if line_count > 0 {
|
||||||
.map(|line| line.ending())
|
self.lines[line_count - 1].ending()
|
||||||
.unwrap_or_default()
|
} else {
|
||||||
!= LineEnding::None
|
LineEnding::default()
|
||||||
{
|
};
|
||||||
self.lines.push(BufferLine::new(
|
if last_ending != LineEnding::None {
|
||||||
"",
|
if line_count < self.lines.len() {
|
||||||
LineEnding::None,
|
let reused_text = self.lines[line_count].reclaim_text();
|
||||||
AttrsList::new(attrs),
|
let reused_attrs = self.lines[line_count].reclaim_attrs().reset(attrs);
|
||||||
shaping,
|
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() {
|
if alignment.is_some() {
|
||||||
self.lines.iter_mut().for_each(|line| {
|
self.lines.iter_mut().for_each(|line| {
|
||||||
line.set_align(alignment);
|
line.set_align(alignment);
|
||||||
|
|
@ -861,30 +917,26 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scroll = Scroll::default();
|
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)
|
/// Set text of buffer, using provided attributes for each line by default.
|
||||||
///
|
pub fn set_text(
|
||||||
/// ```
|
&mut self,
|
||||||
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
|
text: &str,
|
||||||
/// # let mut font_system = FontSystem::new();
|
attrs: &Attrs,
|
||||||
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
|
shaping: Shaping,
|
||||||
/// let attrs = Attrs::new().family(Family::Serif);
|
alignment: Option<Align>,
|
||||||
/// buffer.set_rich_text(
|
) {
|
||||||
/// &mut font_system,
|
self.set_text_impl(text, attrs, shaping, alignment);
|
||||||
/// [
|
self.dirty |= DirtyFlags::TEXT_SET;
|
||||||
/// ("hello, ", attrs.clone()),
|
self.redraw = true;
|
||||||
/// ("cosmic\ntext", attrs.clone().family(Family::Monospace)),
|
}
|
||||||
/// ],
|
|
||||||
/// &attrs,
|
/// Internal: set rich text of buffer, reusing existing line allocations.
|
||||||
/// Shaping::Advanced,
|
///
|
||||||
/// None,
|
/// Does NOT call `shape_until_scroll` — the caller is responsible for that.
|
||||||
/// );
|
fn set_rich_text_impl<'r, 's, I>(
|
||||||
/// ```
|
|
||||||
pub fn set_rich_text<'r, 's, I>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
font_system: &mut FontSystem,
|
|
||||||
spans: I,
|
spans: I,
|
||||||
default_attrs: &Attrs,
|
default_attrs: &Attrs,
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
|
|
@ -1017,8 +1069,37 @@ impl Buffer {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.scroll = Scroll::default();
|
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
|
/// True if a redraw is needed
|
||||||
|
|
@ -1031,12 +1112,24 @@ impl Buffer {
|
||||||
self.redraw = redraw;
|
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<'_> {
|
pub fn layout_runs(&self) -> LayoutRunIter<'_> {
|
||||||
LayoutRunIter::new(self)
|
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> {
|
pub fn hit(&self, x: f32, y: f32) -> Option<Cursor> {
|
||||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
||||||
let instant = std::time::Instant::now();
|
let instant = std::time::Instant::now();
|
||||||
|
|
@ -1478,10 +1571,12 @@ impl Buffer {
|
||||||
Some((cursor, cursor_x_opt))
|
Some((cursor, cursor_x_opt))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the buffer
|
/// Draw the buffer.
|
||||||
|
///
|
||||||
|
/// Automatically resolves any pending dirty state before drawing.
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(
|
pub fn draw<F>(
|
||||||
&self,
|
&mut self,
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
cache: &mut crate::SwashCache,
|
cache: &mut crate::SwashCache,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
|
@ -1489,15 +1584,32 @@ impl Buffer {
|
||||||
) where
|
) where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
|
self.shape_until_scroll(font_system, false);
|
||||||
let mut renderer = crate::LegacyRenderer {
|
let mut renderer = crate::LegacyRenderer {
|
||||||
font_system,
|
font_system,
|
||||||
cache,
|
cache,
|
||||||
callback,
|
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 run in self.layout_runs() {
|
||||||
for glyph in run.glyphs {
|
for glyph in run.glyphs {
|
||||||
let physical_glyph = glyph.physical((0., run.line_y), 1.0);
|
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_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
|
/// Shape the provided line index and return the result
|
||||||
pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
|
pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
|
||||||
self.inner.line_shape(self.font_system, line_i)
|
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)
|
self.inner.line_layout(self.font_system, line_i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current [`Metrics`]
|
/// Set the current [`Metrics`].
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Will panic if `metrics.font_size` is zero.
|
/// Will panic if `metrics.font_size` is zero.
|
||||||
pub fn set_metrics(&mut self, metrics: Metrics) {
|
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) {
|
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) {
|
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>) {
|
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
|
/// # Panics
|
||||||
///
|
///
|
||||||
|
|
@ -1568,15 +1680,22 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
||||||
height_opt: Option<f32>,
|
height_opt: Option<f32>,
|
||||||
) {
|
) {
|
||||||
self.inner
|
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) {
|
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(
|
pub fn set_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: &str,
|
text: &str,
|
||||||
|
|
@ -1584,28 +1703,10 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
alignment: Option<Align>,
|
alignment: Option<Align>,
|
||||||
) {
|
) {
|
||||||
self.inner
|
self.inner.set_text(text, attrs, shaping, alignment);
|
||||||
.set_text(self.font_system, text, attrs, shaping, alignment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
|
/// 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>(
|
pub fn set_rich_text<'r, 's, I>(
|
||||||
&mut self,
|
&mut self,
|
||||||
spans: I,
|
spans: I,
|
||||||
|
|
@ -1616,7 +1717,30 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
||||||
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
|
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
|
||||||
{
|
{
|
||||||
self.inner
|
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`]
|
/// Apply a [`Motion`] to a [`Cursor`]
|
||||||
|
|
@ -1630,7 +1754,9 @@ impl BorrowedWithFontSystem<'_, Buffer> {
|
||||||
.cursor_motion(self.font_system, cursor, cursor_x_opt, motion)
|
.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")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
|
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,12 @@ impl<'buffer> Editor<'buffer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the editor
|
/// Draw the editor
|
||||||
|
///
|
||||||
|
/// Automatically resolves any pending dirty state before drawing.
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn draw<F>(
|
pub fn draw<F>(
|
||||||
&self,
|
&mut self,
|
||||||
font_system: &mut FontSystem,
|
font_system: &mut FontSystem,
|
||||||
cache: &mut crate::SwashCache,
|
cache: &mut crate::SwashCache,
|
||||||
text_color: Color,
|
text_color: Color,
|
||||||
|
|
@ -61,6 +63,7 @@ impl<'buffer> Editor<'buffer> {
|
||||||
) where
|
) where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
|
self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, false));
|
||||||
let mut renderer = crate::LegacyRenderer {
|
let mut renderer = crate::LegacyRenderer {
|
||||||
font_system,
|
font_system,
|
||||||
cache,
|
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>(
|
pub fn render<R: Renderer>(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
|
|
@ -207,8 +214,8 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
||||||
self.with_buffer(super::super::buffer::Buffer::tab_width)
|
self.with_buffer(super::super::buffer::Buffer::tab_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
fn set_tab_width(&mut self, tab_width: u16) {
|
||||||
self.with_buffer_mut(|buffer| buffer.set_tab_width(font_system, tab_width));
|
self.with_buffer_mut(|buffer| buffer.set_tab_width(tab_width));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
|
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 } => {
|
Action::Click { x, y } => {
|
||||||
self.set_selection(Selection::None);
|
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 {
|
if new_cursor != self.cursor {
|
||||||
self.cursor = new_cursor;
|
self.cursor = new_cursor;
|
||||||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||||
|
|
@ -775,8 +784,10 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
||||||
Action::DoubleClick { x, y } => {
|
Action::DoubleClick { x, y } => {
|
||||||
self.set_selection(Selection::None);
|
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 {
|
if new_cursor != self.cursor {
|
||||||
self.cursor = new_cursor;
|
self.cursor = new_cursor;
|
||||||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||||
|
|
@ -788,8 +799,10 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
|
||||||
Action::TripleClick { x, y } => {
|
Action::TripleClick { x, y } => {
|
||||||
self.set_selection(Selection::None);
|
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 {
|
if new_cursor != self.cursor {
|
||||||
self.cursor = new_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));
|
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 {
|
if new_cursor != self.cursor {
|
||||||
self.cursor = new_cursor;
|
self.cursor = new_cursor;
|
||||||
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
self.with_buffer_mut(|buffer| buffer.set_redraw(true));
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ pub trait Edit<'buffer> {
|
||||||
fn tab_width(&self) -> u16;
|
fn tab_width(&self) -> u16;
|
||||||
|
|
||||||
/// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored
|
/// 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
|
/// Shape lines until scroll, after adjusting scroll if the cursor moved
|
||||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool);
|
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
|
/// 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) {
|
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
|
/// 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")]
|
#[cfg(feature = "std")]
|
||||||
pub fn load_text<P: AsRef<Path>>(
|
pub fn load_text<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
font_system: &mut FontSystem,
|
_font_system: &mut FontSystem,
|
||||||
path: P,
|
path: P,
|
||||||
mut attrs: crate::Attrs,
|
mut attrs: crate::Attrs,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
|
|
@ -125,7 +125,7 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
||||||
|
|
||||||
// Clear buffer first (allows sane handling of non-existant files)
|
// Clear buffer first (allows sane handling of non-existant files)
|
||||||
self.editor.with_buffer_mut(|buffer| {
|
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
|
// Update syntax based on file name
|
||||||
|
|
@ -147,7 +147,7 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
||||||
// Set text
|
// Set text
|
||||||
let text = fs::read_to_string(path)?;
|
let text = fs::read_to_string(path)?;
|
||||||
self.editor.with_buffer_mut(|buffer| {
|
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(())
|
Ok(())
|
||||||
|
|
@ -218,19 +218,32 @@ impl<'syntax_system, 'buffer> SyntaxEditor<'syntax_system, 'buffer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the editor
|
/// Draw the editor
|
||||||
|
///
|
||||||
|
/// Automatically resolves any pending dirty state before drawing.
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, callback: F)
|
pub fn draw<F>(
|
||||||
where
|
&mut self,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
cache: &mut crate::SwashCache,
|
||||||
|
callback: F,
|
||||||
|
) where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
let mut renderer = crate::LegacyRenderer {
|
self.editor.draw(
|
||||||
font_system,
|
font_system,
|
||||||
cache,
|
cache,
|
||||||
|
self.foreground_color(),
|
||||||
|
self.cursor_color(),
|
||||||
|
self.selection_color(),
|
||||||
|
self.foreground_color(),
|
||||||
callback,
|
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) {
|
pub fn render<R: Renderer>(&self, renderer: &mut R) {
|
||||||
let size = self.with_buffer(|buffer| buffer.size());
|
let size = self.with_buffer(|buffer| buffer.size());
|
||||||
if let Some(width) = size.0 {
|
if let Some(width) = size.0 {
|
||||||
|
|
@ -285,8 +298,8 @@ impl<'buffer> Edit<'buffer> for SyntaxEditor<'_, 'buffer> {
|
||||||
self.editor.tab_width()
|
self.editor.tab_width()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
fn set_tab_width(&mut self, tab_width: u16) {
|
||||||
self.editor.set_tab_width(font_system, tab_width);
|
self.editor.set_tab_width(tab_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
|
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);
|
self.changed = eval_changed(&self.commands, self.save_pivot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw the editor.
|
||||||
|
///
|
||||||
|
/// Automatically resolves any pending dirty state before drawing.
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, callback: F)
|
pub fn draw<F>(
|
||||||
where
|
&mut self,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
cache: &mut crate::SwashCache,
|
||||||
|
callback: F,
|
||||||
|
) where
|
||||||
F: FnMut(i32, i32, u32, u32, Color),
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
|
self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, false));
|
||||||
let mut renderer = crate::LegacyRenderer {
|
let mut renderer = crate::LegacyRenderer {
|
||||||
font_system,
|
font_system,
|
||||||
cache,
|
cache,
|
||||||
|
|
@ -315,6 +323,10 @@ impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
|
||||||
self.render(&mut renderer);
|
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) {
|
pub fn render<R: Renderer>(&self, renderer: &mut R) {
|
||||||
let background_color = self.background_color();
|
let background_color = self.background_color();
|
||||||
let foreground_color = self.foreground_color();
|
let foreground_color = self.foreground_color();
|
||||||
|
|
@ -553,8 +565,8 @@ impl<'buffer> Edit<'buffer> for ViEditor<'_, 'buffer> {
|
||||||
self.editor.tab_width()
|
self.editor.tab_width()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
|
fn set_tab_width(&mut self, tab_width: u16) {
|
||||||
self.editor.set_tab_width(font_system, tab_width);
|
self.editor.set_tab_width(tab_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
|
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
|
//! // Borrow buffer together with the font system for more convenient method calls
|
||||||
//! let mut buffer = buffer.borrow_with(&mut font_system);
|
//! 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
|
//! // Attributes indicate what font to choose
|
||||||
//! let attrs = Attrs::new();
|
//! 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);
|
//! buffer.set_text("Hello, Rust! 🦀\n", &attrs, Shaping::Advanced, None);
|
||||||
//!
|
//!
|
||||||
//! // Perform shaping as desired
|
|
||||||
//! buffer.shape_until_scroll(true);
|
|
||||||
//!
|
|
||||||
//! // Inspect the output runs
|
//! // Inspect the output runs
|
||||||
//! for run in buffer.layout_runs() {
|
//! for run in buffer.layout_runs() {
|
||||||
//! for glyph in run.glyphs.iter() {
|
//! for glyph in run.glyphs.iter() {
|
||||||
|
|
|
||||||
|
|
@ -1262,6 +1262,11 @@ impl ShapeLine {
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
tab_width: u16,
|
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);
|
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.
|
// 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,
|
self.alignment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
buffer.shape_until_scroll(true);
|
|
||||||
|
|
||||||
// Black
|
// Black
|
||||||
let text_color = Color::rgb(0x00, 0x00, 0x00);
|
let text_color = Color::rgb(0x00, 0x00, 0x00);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ fn empty_lines_use_span_metrics() {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
buffer.set_size(Some(500.0), Some(500.0));
|
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();
|
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);
|
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||||
|
|
||||||
buffer.set_text("|>", &Attrs::new(), Shaping::Advanced, None);
|
buffer.set_text("|>", &Attrs::new(), Shaping::Advanced, None);
|
||||||
buffer.shape_until_scroll(false);
|
let _ = buffer.layout_runs();
|
||||||
|
|
||||||
let line = &buffer.lines[0];
|
let line = &buffer.lines[0];
|
||||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||||
|
|
@ -105,7 +105,7 @@ fn test_ligature_segmentation() {
|
||||||
|
|
||||||
// Test -> (Arrow), which is a common ligature.
|
// Test -> (Arrow), which is a common ligature.
|
||||||
buffer.set_text("->", &Attrs::new(), Shaping::Advanced, None);
|
buffer.set_text("->", &Attrs::new(), Shaping::Advanced, None);
|
||||||
buffer.shape_until_scroll(false);
|
let _ = buffer.layout_runs();
|
||||||
let line = &buffer.lines[0];
|
let line = &buffer.lines[0];
|
||||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ fn test_ligature_segmentation() {
|
||||||
|
|
||||||
// Test !=
|
// Test !=
|
||||||
buffer.set_text("!=", &Attrs::new(), Shaping::Advanced, None);
|
buffer.set_text("!=", &Attrs::new(), Shaping::Advanced, None);
|
||||||
buffer.shape_until_scroll(false);
|
let _ = buffer.layout_runs();
|
||||||
let line = &buffer.lines[0];
|
let line = &buffer.lines[0];
|
||||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||||
// Inter has a contextual alternate for != too.
|
// Inter has a contextual alternate for != too.
|
||||||
|
|
@ -131,7 +131,7 @@ fn test_ligature_segmentation() {
|
||||||
|
|
||||||
// Test ++
|
// Test ++
|
||||||
buffer.set_text("++", &Attrs::new(), Shaping::Advanced, None);
|
buffer.set_text("++", &Attrs::new(), Shaping::Advanced, None);
|
||||||
buffer.shape_until_scroll(false);
|
let _ = buffer.layout_runs();
|
||||||
let line = &buffer.lines[0];
|
let line = &buffer.lines[0];
|
||||||
let shape = line.shape_opt().expect("ShapeLine not found");
|
let shape = line.shape_opt().expect("ShapeLine not found");
|
||||||
// Inter does not have a ++ ligature.
|
// Inter does not have a ++ ligature.
|
||||||
|
|
|
||||||
|
|
@ -117,15 +117,10 @@ fn wrap_extra_line() {
|
||||||
|
|
||||||
let mut buffer = buffer.borrow_with(&mut font_system);
|
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_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));
|
buffer.set_size(Some(50.0), Some(1000.0));
|
||||||
|
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);
|
||||||
// Perform shaping as desired
|
|
||||||
buffer.shape_until_scroll(false);
|
|
||||||
|
|
||||||
let empty_lines = buffer.layout_runs().filter(|x| x.line_w == 0.).count();
|
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();
|
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);
|
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||||
|
|
||||||
buffer.set_wrap(Wrap::WordOrGlyph);
|
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_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 = buffer
|
||||||
|
.layout_runs()
|
||||||
let measured_size = measure(&buffer);
|
.fold(0.0f32, |width, run| width.max(run.line_w));
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
measured_size <= buffer.size().0.unwrap_or(0.0),
|
measured_size <= buffer.size().0.unwrap_or(0.0),
|
||||||
|
|
@ -29,9 +29,3 @@ fn wrap_word_fallback() {
|
||||||
buffer.size().0.unwrap_or(0.0)
|
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