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:
parent
e7261fc06e
commit
d0b4b4635e
16 changed files with 213 additions and 159 deletions
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
216
src/buffer.rs
216
src/buffer.rs
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue