Refactor of scroll and shaping

- Scroll is identified by line index and layout index, instead of just
  layout index
- Shaping has the option to prune, where caches outside of the scroll
  view are cleared
- Syntax editor no longer requires layout of all lines, only of lines
  inside scroll
- BufferLine has a metadata field that can be used by other abstractions
  to know when text was changed
This commit is contained in:
Jeremy Soller 2023-12-15 13:37:59 -07:00
parent e7261fc06e
commit d0b4b4635e
16 changed files with 213 additions and 159 deletions

View file

@ -131,12 +131,10 @@ where
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node { fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let limits = limits.width(Length::Fill).height(Length::Fill); let limits = limits.width(Length::Fill).height(Length::Fill);
//TODO: allow lazy shape
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
editor editor
.borrow_with(&mut FONT_SYSTEM.lock().unwrap()) .borrow_with(&mut FONT_SYSTEM.lock().unwrap())
.buffer_mut() .shape_as_needed(true);
.shape_until(i32::max_value());
let mut layout_lines = 0; let mut layout_lines = 0;
for line in editor.buffer().lines.iter() { for line in editor.buffer().lines.iter() {
@ -148,7 +146,6 @@ where
let height = layout_lines as f32 * editor.buffer().metrics().line_height; let height = layout_lines as f32 * editor.buffer().metrics().line_height;
let size = Size::new(limits.max().width, height); let size = Size::new(limits.max().width, height);
log::info!("size {:?}", size);
layout::Node::new(limits.resolve(size)) layout::Node::new(limits.resolve(size))
} }
@ -228,7 +225,7 @@ where
editor.buffer_mut().set_size(image_w as f32, image_h as f32); editor.buffer_mut().set_size(image_w as f32, image_h as f32);
// Shape and layout // Shape and layout
editor.shape_as_needed(); editor.shape_as_needed(true);
// Draw to pixel buffer // Draw to pixel buffer
let mut pixels = vec![0; image_w as usize * image_h as usize * 4]; let mut pixels = vec![0; image_w as usize * image_h as usize * 4];

View file

@ -88,7 +88,7 @@ fn main() {
let mut mouse_y = -1; let mut mouse_y = -1;
let mut mouse_left = false; let mut mouse_left = false;
loop { loop {
editor.shape_as_needed(); editor.shape_as_needed(true);
if editor.buffer().redraw() { if editor.buffer().redraw() {
let instant = Instant::now(); let instant = Instant::now();

View file

@ -16,7 +16,7 @@ fn redraw(
let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34); let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34);
let font_color = Color::rgb(0xFF, 0xFF, 0xFF); let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
editor.shape_as_needed(); editor.shape_as_needed(true);
if editor.buffer().redraw() { if editor.buffer().redraw() {
let instant = Instant::now(); let instant = Instant::now();

View file

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, Shaping, SwashCache}; use cosmic_text::{
Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, Scroll, Shaping, SwashCache,
};
use std::{collections::HashMap, env, fs, num::NonZeroU32, rc::Rc, slice}; use std::{collections::HashMap, env, fs, num::NonZeroU32, rc::Rc, slice};
use tiny_skia::{Color, Paint, PixmapMut, Rect, Transform}; use tiny_skia::{Color, Paint, PixmapMut, Rect, Transform};
use winit::{ use winit::{
@ -41,7 +43,7 @@ fn main() {
window: Rc<WinitWindow>, window: Rc<WinitWindow>,
context: softbuffer::Context<Rc<WinitWindow>>, context: softbuffer::Context<Rc<WinitWindow>>,
surface: softbuffer::Surface<Rc<WinitWindow>, Rc<WinitWindow>>, surface: softbuffer::Surface<Rc<WinitWindow>, Rc<WinitWindow>>,
scroll: i32, scroll: Scroll,
} }
let mut windows = HashMap::new(); let mut windows = HashMap::new();
for _ in 0..2 { for _ in 0..2 {
@ -54,7 +56,7 @@ fn main() {
window, window,
context, context,
surface, surface,
scroll: 0, scroll: Scroll::default(),
}, },
); );
} }
@ -102,7 +104,8 @@ fn main() {
// Set size, will relayout and shape until scroll if changed // Set size, will relayout and shape until scroll if changed
buffer.set_size(width as f32, height as f32); buffer.set_size(width as f32, height as f32);
// Shape until scroll, ensures scroll is clamped // Shape until scroll, ensures scroll is clamped
buffer.shape_until_scroll(); //TODO: ability to prune with multiple views?
buffer.shape_until_scroll(true);
// Update scroll after buffer clamps it // Update scroll after buffer clamps it
*scroll = buffer.scroll(); *scroll = buffer.scroll();
@ -145,16 +148,16 @@ fn main() {
if state == ElementState::Pressed { if state == ElementState::Pressed {
match logical_key { match logical_key {
Key::Named(NamedKey::ArrowDown) => { Key::Named(NamedKey::ArrowDown) => {
*scroll += 1; scroll.layout += 1;
} }
Key::Named(NamedKey::ArrowUp) => { Key::Named(NamedKey::ArrowUp) => {
*scroll -= 1; scroll.layout -= 1;
} }
Key::Named(NamedKey::PageDown) => { Key::Named(NamedKey::PageDown) => {
*scroll += buffer.visible_lines(); scroll.layout += buffer.visible_lines();
} }
Key::Named(NamedKey::PageUp) => { Key::Named(NamedKey::PageUp) => {
*scroll -= buffer.visible_lines(); scroll.layout -= buffer.visible_lines();
} }
_ => {} _ => {}
} }

View file

@ -131,7 +131,7 @@ fn main() {
let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34); let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34);
let font_color = Color::rgb(0xFF, 0xFF, 0xFF); let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
editor.shape_as_needed(); editor.shape_as_needed(true);
if editor.buffer().redraw() { if editor.buffer().redraw() {
let instant = Instant::now(); let instant = Instant::now();

View file

@ -39,7 +39,7 @@ fn main() {
buffer.set_text(&text, attrs, Shaping::Advanced); buffer.set_text(&text, attrs, Shaping::Advanced);
// Perform shaping as desired // Perform shaping as desired
buffer.shape_until_scroll(); buffer.shape_until_scroll(true);
// Default text color (0xFF, 0xFF, 0xFF is white) // Default text color (0xFF, 0xFF, 0xFF is white)
const TEXT_COLOR: Color = Color::rgb(0xFF, 0xFF, 0xFF); const TEXT_COLOR: Color = Color::rgb(0xFF, 0xFF, 0xFF);

View file

@ -7,8 +7,8 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
Affinity, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, Cursor, Affinity, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, Cursor,
FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, Motion, ShapeBuffer, ShapeLine, Shaping, FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, Motion, Scroll, ShapeBuffer, ShapeLine,
Wrap, Shaping, Wrap,
}; };
/// A line of visible text for rendering /// A line of visible text for rendering
@ -101,6 +101,7 @@ impl<'b> LayoutRunIter<'b> {
let total_layout_lines: usize = buffer let total_layout_lines: usize = buffer
.lines .lines
.iter() .iter()
.skip(buffer.scroll.line)
.map(|line| { .map(|line| {
line.layout_opt() line.layout_opt()
.as_ref() .as_ref()
@ -109,7 +110,7 @@ impl<'b> LayoutRunIter<'b> {
}) })
.sum(); .sum();
let top_cropped_layout_lines = let top_cropped_layout_lines =
total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default()); total_layout_lines.saturating_sub(buffer.scroll.layout.try_into().unwrap_or_default());
let maximum_lines = if buffer.metrics.line_height == 0.0 { let maximum_lines = if buffer.metrics.line_height == 0.0 {
0 0
} else { } else {
@ -124,7 +125,7 @@ impl<'b> LayoutRunIter<'b> {
Self { Self {
buffer, buffer,
line_i: 0, line_i: buffer.scroll.line,
layout_i: 0, layout_i: 0,
remaining_len: bottom_cropped_layout_lines, remaining_len: bottom_cropped_layout_lines,
total_layout: 0, total_layout: 0,
@ -146,7 +147,7 @@ impl<'b> Iterator for LayoutRunIter<'b> {
while let Some(layout_line) = layout.get(self.layout_i) { while let Some(layout_line) = layout.get(self.layout_i) {
self.layout_i += 1; self.layout_i += 1;
let scrolled = self.total_layout < self.buffer.scroll; let scrolled = self.total_layout < self.buffer.scroll.layout;
self.total_layout += 1; self.total_layout += 1;
if scrolled { if scrolled {
continue; continue;
@ -154,7 +155,7 @@ impl<'b> Iterator for LayoutRunIter<'b> {
let line_top = self let line_top = self
.total_layout .total_layout
.saturating_sub(self.buffer.scroll) .saturating_sub(self.buffer.scroll.layout)
.saturating_sub(1) as f32 .saturating_sub(1) as f32
* self.buffer.metrics.line_height; * self.buffer.metrics.line_height;
let glyph_height = layout_line.max_ascent + layout_line.max_descent; let glyph_height = layout_line.max_ascent + layout_line.max_descent;
@ -227,7 +228,7 @@ pub struct Buffer {
metrics: Metrics, metrics: Metrics,
width: f32, width: f32,
height: f32, height: f32,
scroll: i32, scroll: Scroll,
/// True if a redraw is requires. Set to false after processing /// True if a redraw is requires. Set to false after processing
redraw: bool, redraw: bool,
wrap: Wrap, wrap: Wrap,
@ -255,7 +256,7 @@ impl Buffer {
metrics, metrics,
width: 0.0, width: 0.0,
height: 0.0, height: 0.0,
scroll: 0, scroll: Scroll::default(),
redraw: false, redraw: false,
wrap: Wrap::Word, wrap: Wrap::Word,
scratch: ShapeBuffer::default(), scratch: ShapeBuffer::default(),
@ -307,96 +308,119 @@ impl Buffer {
log::debug!("relayout: {:?}", instant.elapsed()); log::debug!("relayout: {:?}", instant.elapsed());
} }
/// Pre-shape lines in the buffer, up to `lines`, return actual number of layout lines
pub fn shape_until(&mut self, font_system: &mut FontSystem, lines: i32) -> i32 {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
let instant = std::time::Instant::now();
let mut reshaped = 0;
let mut total_layout = 0;
for line in &mut self.lines {
if total_layout >= lines {
break;
}
if line.shape_opt().is_none() {
reshaped += 1;
}
let layout = line.layout_in_buffer(
&mut self.scratch,
font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
total_layout += layout.len() as i32;
}
if reshaped > 0 {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
log::debug!("shape_until {}: {:?}", reshaped, instant.elapsed());
self.redraw = true;
}
total_layout
}
/// Shape lines until cursor, also scrolling to include cursor in view /// Shape lines until cursor, also scrolling to include cursor in view
pub fn shape_until_cursor(&mut self, font_system: &mut FontSystem, cursor: Cursor) { pub fn shape_until_cursor(
#[cfg(all(feature = "std", not(target_arch = "wasm32")))] &mut self,
let instant = std::time::Instant::now(); font_system: &mut FontSystem,
cursor: Cursor,
prune: bool,
) {
let old_scroll = self.scroll;
let mut reshaped = 0; let layout_cursor = self
let mut layout_i = 0; .layout_cursor(font_system, cursor)
for (line_i, line) in self.lines.iter_mut().enumerate() { .expect("shape_until_cursor invalid cursor");
if line_i > cursor.line {
break;
}
if line.shape_opt().is_none() { if self.scroll.line > layout_cursor.line
reshaped += 1; || (self.scroll.line == layout_cursor.line
} && self.scroll.layout > layout_cursor.layout as i32)
let layout = line.layout_in_buffer( {
&mut self.scratch, // Adjust scroll backwards if cursor is before it
font_system, self.scroll.line = layout_cursor.line;
self.metrics.font_size, self.scroll.layout = layout_cursor.layout as i32;
self.width, } else {
self.wrap, // Adjust scroll forwards if cursor is after it
); let visible_lines = self.visible_lines();
if line_i == cursor.line { let mut line_i = layout_cursor.line;
if let Some(layout_cursor) = self.layout_cursor(font_system, cursor) { let mut total_layout = layout_cursor.layout as i32 + 1;
layout_i += layout_cursor.layout as i32; while line_i > self.scroll.line {
line_i -= 1;
let layout = self
.line_layout(font_system, line_i)
.expect("shape_until_cursor failed to scroll forwards");
for layout_i in (0..layout.len()).rev() {
total_layout += 1;
if total_layout >= visible_lines {
self.scroll.line = line_i;
self.scroll.layout = layout_i as i32;
break;
}
} }
break;
} else {
layout_i += layout.len() as i32;
} }
} }
if reshaped > 0 { if old_scroll != self.scroll {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
log::debug!("shape_until_cursor {}: {:?}", reshaped, instant.elapsed());
self.redraw = true; self.redraw = true;
} }
let lines = self.visible_lines(); self.shape_until_scroll(font_system, prune);
if layout_i < self.scroll {
self.scroll = layout_i;
} else if layout_i >= self.scroll + lines {
self.scroll = layout_i - (lines - 1);
}
self.shape_until_scroll(font_system);
} }
/// Shape lines until scroll /// Shape lines until scroll
pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem) { pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
let lines = self.visible_lines(); let old_scroll = self.scroll;
let scroll_end = self.scroll + lines; loop {
let total_layout = self.shape_until(font_system, scroll_end); // Adjust scroll.layout to be positive by moving scroll.line backwards
while self.scroll.layout < 0 {
if self.scroll.line > 0 {
self.scroll.line -= 1;
let layout = self
.line_layout(font_system, self.scroll.line)
.expect("shape_until_scroll invalid scroll.line");
self.scroll.layout += layout.len() as i32;
} else {
self.scroll.layout = 0;
break;
}
}
self.scroll = cmp::max(0, cmp::min(total_layout - lines, self.scroll)); let visible_lines = self.visible_lines();
let scroll_start = self.scroll.layout;
let scroll_end = scroll_start + visible_lines;
let mut total_layout = 0;
for line_i in 0..self.lines.len() {
if line_i < self.scroll.line {
if prune {
self.lines[line_i].reset_shaping();
}
continue;
}
if total_layout >= scroll_end {
if prune {
self.lines[line_i].reset_shaping();
continue;
} else {
break;
}
}
let layout = self
.line_layout(font_system, line_i)
.expect("shape_until_scroll invalid line");
for layout_i in 0..layout.len() {
if total_layout == scroll_start {
// Adjust scroll.line and scroll.layout
self.scroll.line = line_i;
self.scroll.layout = layout_i as i32;
}
total_layout += 1;
}
}
if total_layout < scroll_end && self.scroll.line > 0 {
// Need to scroll up to stay inside of buffer
self.scroll.layout -= scroll_end - total_layout;
} else {
// Done adjusting scroll
break;
}
}
if old_scroll != self.scroll {
self.redraw = true;
}
} }
/// Convert a [`Cursor`] to a [`LayoutCursor`] /// Convert a [`Cursor`] to a [`LayoutCursor`]
@ -481,7 +505,7 @@ impl Buffer {
if wrap != self.wrap { if wrap != self.wrap {
self.wrap = wrap; self.wrap = wrap;
self.relayout(font_system); self.relayout(font_system);
self.shape_until_scroll(font_system); self.shape_until_scroll(font_system, false);
} }
} }
@ -516,17 +540,17 @@ impl Buffer {
self.width = clamped_width; self.width = clamped_width;
self.height = clamped_height; self.height = clamped_height;
self.relayout(font_system); self.relayout(font_system);
self.shape_until_scroll(font_system); self.shape_until_scroll(font_system, false);
} }
} }
/// Get the current scroll location /// Get the current scroll location
pub fn scroll(&self) -> i32 { pub fn scroll(&self) -> Scroll {
self.scroll self.scroll
} }
/// Set the current scroll location /// Set the current scroll location
pub fn set_scroll(&mut self, scroll: i32) { pub fn set_scroll(&mut self, scroll: Scroll) {
if scroll != self.scroll { if scroll != self.scroll {
self.scroll = scroll; self.scroll = scroll;
self.redraw = true; self.redraw = true;
@ -651,9 +675,9 @@ impl Buffer {
} }
} }
self.scroll = 0; self.scroll = Scroll::default();
self.shape_until_scroll(font_system); self.shape_until_scroll(font_system, false);
} }
/// True if a redraw is needed /// True if a redraw is needed
@ -1098,19 +1122,15 @@ impl Buffer {
} }
impl<'a> BorrowedWithFontSystem<'a, Buffer> { impl<'a> BorrowedWithFontSystem<'a, Buffer> {
/// Pre-shape lines in the buffer, up to `lines`, return actual number of layout lines
pub fn shape_until(&mut self, lines: i32) -> i32 {
self.inner.shape_until(self.font_system, lines)
}
/// Shape lines until cursor, also scrolling to include cursor in view /// Shape lines until cursor, also scrolling to include cursor in view
pub fn shape_until_cursor(&mut self, cursor: Cursor) { pub fn shape_until_cursor(&mut self, cursor: Cursor, prune: bool) {
self.inner.shape_until_cursor(self.font_system, cursor); self.inner
.shape_until_cursor(self.font_system, cursor, prune);
} }
/// Shape lines until scroll /// Shape lines until scroll
pub fn shape_until_scroll(&mut self) { pub fn shape_until_scroll(&mut self, prune: bool) {
self.inner.shape_until_scroll(self.font_system); 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

View file

@ -13,6 +13,7 @@ pub struct BufferLine {
shape_opt: Option<ShapeLine>, shape_opt: Option<ShapeLine>,
layout_opt: Option<Vec<LayoutLine>>, layout_opt: Option<Vec<LayoutLine>>,
shaping: Shaping, shaping: Shaping,
metadata: Option<usize>,
} }
impl BufferLine { impl BufferLine {
@ -27,6 +28,7 @@ impl BufferLine {
shape_opt: None, shape_opt: None,
layout_opt: None, layout_opt: None,
shaping, shaping,
metadata: None,
} }
} }
@ -69,7 +71,7 @@ impl BufferLine {
pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool { pub fn set_attrs_list(&mut self, attrs_list: AttrsList) -> bool {
if attrs_list != self.attrs_list { if attrs_list != self.attrs_list {
self.attrs_list = attrs_list; self.attrs_list = attrs_list;
self.reset(); self.reset_shaping();
true true
} else { } else {
false false
@ -129,23 +131,23 @@ impl BufferLine {
new new
} }
/// Reset shaping and layout information /// Reset shaping, layout, and metadata caches
//TODO: make this private
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.shape_opt = None; self.metadata = None;
self.layout_opt = None; self.reset_shaping();
} }
/// Reset only layout information /// Reset shaping and layout caches
pub fn reset_shaping(&mut self) {
self.shape_opt = None;
self.reset_layout();
}
/// Reset only layout cache
pub fn reset_layout(&mut self) { pub fn reset_layout(&mut self) {
self.layout_opt = None; self.layout_opt = None;
} }
/// Check if shaping and layout information is cleared
pub fn is_reset(&self) -> bool {
self.shape_opt.is_none()
}
/// Shape line, will cache results /// Shape line, will cache results
pub fn shape(&mut self, font_system: &mut FontSystem) -> &ShapeLine { pub fn shape(&mut self, font_system: &mut FontSystem) -> &ShapeLine {
self.shape_in_buffer(&mut ShapeBuffer::default(), font_system) self.shape_in_buffer(&mut ShapeBuffer::default(), font_system)
@ -215,4 +217,15 @@ impl BufferLine {
pub fn layout_opt(&self) -> &Option<Vec<LayoutLine>> { pub fn layout_opt(&self) -> &Option<Vec<LayoutLine>> {
&self.layout_opt &self.layout_opt
} }
/// Get line metadata. This will be None if [`BufferLine::set_metadata`] has not been called
/// after the last reset of shaping and layout caches
pub fn metadata(&self) -> Option<usize> {
self.metadata
}
/// Set line metadata. This is stored until the next line reset
pub fn set_metadata(&mut self, metadata: usize) {
self.metadata = Some(metadata);
}
} }

View file

@ -3,7 +3,7 @@ use crate::Color;
/// Current cursor location /// Current cursor location
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Cursor { pub struct Cursor {
/// Text line the cursor is on /// Index of [`BufferLine`] in [`Buffer::lines`]
pub line: usize, pub line: usize,
/// First-byte-index of glyph at cursor (will insert behind this glyph) /// First-byte-index of glyph at cursor (will insert behind this glyph)
pub index: usize, pub index: usize,
@ -81,8 +81,11 @@ impl Affinity {
/// The position of a cursor within a [`Buffer`]. /// The position of a cursor within a [`Buffer`].
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LayoutCursor { pub struct LayoutCursor {
/// Index of [`BufferLine`] in [`Buffer::lines`]
pub line: usize, pub line: usize,
/// Index of [`LayoutLine`] in [`BufferLine::layout`]
pub layout: usize, pub layout: usize,
/// Index of [`LayoutGlyph`] in [`LayoutLine::glyphs`]
pub glyph: usize, pub glyph: usize,
} }
@ -145,3 +148,14 @@ pub enum Motion {
/// Move cursor to specific line /// Move cursor to specific line
GotoLine(usize), GotoLine(usize),
} }
/// Scroll position in [`Buffer`]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Scroll {
/// Index of [`BufferLine`] in [`Buffer::lines`]. This will be adjusted as needed if layout is
/// out of bounds
pub line: usize,
/// Index of [`LayoutLine`] in [`BufferLine::layout`]. This will be adjusted as needed
/// if it is negative or exceeds the number of layout lines
pub layout: i32,
}

View file

@ -94,12 +94,13 @@ impl Edit for Editor {
} }
} }
fn shape_as_needed(&mut self, font_system: &mut FontSystem) { fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
if self.cursor_moved { if self.cursor_moved {
self.buffer.shape_until_cursor(font_system, self.cursor); self.buffer
.shape_until_cursor(font_system, self.cursor, prune);
self.cursor_moved = false; self.cursor_moved = false;
} else { } else {
self.buffer.shape_until_scroll(font_system); self.buffer.shape_until_scroll(font_system, prune);
} }
} }
@ -627,7 +628,7 @@ impl Edit for Editor {
} }
Action::Scroll { lines } => { Action::Scroll { lines } => {
let mut scroll = self.buffer.scroll(); let mut scroll = self.buffer.scroll();
scroll += lines; scroll.layout += lines;
self.buffer.set_scroll(scroll); self.buffer.set_scroll(scroll);
} }
} }

View file

@ -231,7 +231,7 @@ pub trait Edit {
fn set_tab_width(&mut self, 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); fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool);
/// Delete text starting at start Cursor and ending at end Cursor /// Delete text starting at start Cursor and ending at end Cursor
fn delete_range(&mut self, start: Cursor, end: Cursor); fn delete_range(&mut self, start: Cursor, end: Cursor);
@ -288,8 +288,8 @@ impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> {
} }
/// Shape lines until scroll, after adjusting scroll if the cursor moved /// Shape lines until scroll, after adjusting scroll if the cursor moved
pub fn shape_as_needed(&mut self) { pub fn shape_as_needed(&mut self, prune: bool) {
self.inner.shape_as_needed(self.font_system); self.inner.shape_as_needed(self.font_system, prune);
} }
/// Perform an [Action] on the editor /// Perform an [Action] on the editor

View file

@ -206,14 +206,15 @@ impl<'a> Edit for SyntaxEditor<'a> {
self.editor.set_tab_width(tab_width); self.editor.set_tab_width(tab_width);
} }
fn shape_as_needed(&mut self, font_system: &mut FontSystem) { fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
#[cfg(feature = "std")] #[cfg(feature = "std")]
let now = std::time::Instant::now(); let now = std::time::Instant::now();
let cursor = self.cursor(); let cursor = self.cursor();
let buffer = self.editor.buffer_mut(); let buffer = self.editor.buffer_mut();
let lines = buffer.visible_lines(); let visible_lines = buffer.visible_lines();
let scroll_end = buffer.scroll() + lines; let scroll = buffer.scroll();
let scroll_end = scroll.layout + visible_lines;
let mut total_layout = 0; let mut total_layout = 0;
let mut highlighted = 0; let mut highlighted = 0;
for line_i in 0..buffer.lines.len() { for line_i in 0..buffer.lines.len() {
@ -223,15 +224,17 @@ impl<'a> Edit for SyntaxEditor<'a> {
} }
let line = &mut buffer.lines[line_i]; let line = &mut buffer.lines[line_i];
if !line.is_reset() && line_i < self.syntax_cache.len() { if line.metadata().is_some() && line_i < self.syntax_cache.len() {
//TODO: duplicated code! //TODO: duplicated code!
// Perform shaping and layout of this line in order to count if we have reached scroll if line_i >= scroll.line && total_layout < scroll_end {
match buffer.line_layout(font_system, line_i) { // Perform shaping and layout of this line in order to count if we have reached scroll
Some(layout_lines) => { match buffer.line_layout(font_system, line_i) {
total_layout += layout_lines.len() as i32; Some(layout_lines) => {
} total_layout += layout_lines.len() as i32;
None => { }
//TODO: should this be possible? None => {
//TODO: should this be possible?
}
} }
} }
continue; continue;
@ -285,12 +288,14 @@ impl<'a> Edit for SyntaxEditor<'a> {
line.set_attrs_list(attrs_list); line.set_attrs_list(attrs_list);
// Perform shaping and layout of this line in order to count if we have reached scroll // Perform shaping and layout of this line in order to count if we have reached scroll
match buffer.line_layout(font_system, line_i) { if line_i >= scroll.line && total_layout < scroll_end {
Some(layout_lines) => { match buffer.line_layout(font_system, line_i) {
total_layout += layout_lines.len() as i32; Some(layout_lines) => {
} total_layout += layout_lines.len() as i32;
None => { }
//TODO: should this be possible? None => {
//TODO: should this be possible?
}
} }
} }
@ -303,6 +308,7 @@ impl<'a> Edit for SyntaxEditor<'a> {
} }
} }
} else { } else {
buffer.lines[line_i].set_metadata(self.syntax_cache.len());
self.syntax_cache.push(cache_item); self.syntax_cache.push(cache_item);
} }
} }
@ -317,7 +323,7 @@ impl<'a> Edit for SyntaxEditor<'a> {
); );
} }
self.editor.shape_as_needed(font_system); self.editor.shape_as_needed(font_system, prune);
} }
fn delete_range(&mut self, start: Cursor, end: Cursor) { fn delete_range(&mut self, start: Cursor, end: Cursor) {

View file

@ -291,8 +291,8 @@ impl<'a> Edit for ViEditor<'a> {
self.editor.set_tab_width(tab_width); self.editor.set_tab_width(tab_width);
} }
fn shape_as_needed(&mut self, font_system: &mut FontSystem) { fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
self.editor.shape_as_needed(font_system); self.editor.shape_as_needed(font_system, prune);
} }
fn delete_range(&mut self, start: Cursor, end: Cursor) { fn delete_range(&mut self, start: Cursor, end: Cursor) {
@ -443,8 +443,8 @@ impl<'a> Edit for ViEditor<'a> {
editor.insert_at(cursor, "\n", None); editor.insert_at(cursor, "\n", None);
editor.insert_at(cursor, data, None); editor.insert_at(cursor, data, None);
// Hack to allow immediate up/down //TODO: Hack to allow immediate up/down
editor.shape_as_needed(font_system); editor.shape_as_needed(font_system, false);
// Move to inserted line, preserving cursor x position // Move to inserted line, preserving cursor x position
if after { if after {

View file

@ -177,7 +177,7 @@ impl FontSystem {
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
log::info!( log::debug!(
"Parsed {} font faces in {}ms.", "Parsed {} font faces in {}ms.",
db.len(), db.len(),
now.elapsed().as_millis() now.elapsed().as_millis()

View file

@ -39,7 +39,7 @@
//! buffer.set_text("Hello, Rust! 🦀\n", attrs, Shaping::Advanced); //! buffer.set_text("Hello, Rust! 🦀\n", attrs, Shaping::Advanced);
//! //!
//! // Perform shaping as desired //! // Perform shaping as desired
//! buffer.shape_until_scroll(); //! 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() {

View file

@ -93,7 +93,7 @@ impl DrawTestCfg {
(self.canvas_height - margins * 2) as f32, (self.canvas_height - margins * 2) as f32,
); );
buffer.set_text(&self.text, self.font.as_attrs(), Shaping::Advanced); buffer.set_text(&self.text, self.font.as_attrs(), Shaping::Advanced);
buffer.shape_until_scroll(); buffer.shape_until_scroll(true);
// Black // Black
let text_color = Color::rgb(0x00, 0x00, 0x00); let text_color = Color::rgb(0x00, 0x00, 0x00);