mirror of
https://github.com/enso-org/enso.git
synced 2024-12-27 23:15:01 +03:00
Text editing (https://github.com/enso-org/ide/pull/88)
Added operation for editing text in text component, which does the minimal
required buffer refresh.
Original commit: 2ece0ca13b
This commit is contained in:
parent
99e08bbe75
commit
74ed8b36c8
@ -1,13 +1,14 @@
|
||||
pub mod font;
|
||||
pub mod buffer;
|
||||
pub mod content;
|
||||
pub mod msdf;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::Color;
|
||||
use crate::display::world::Workspace;
|
||||
use crate::text::buffer::ContentRef;
|
||||
use crate::text::buffer::TextComponentBuffers;
|
||||
use crate::text::content::TextComponentContent;
|
||||
use crate::text::font::FontId;
|
||||
use crate::text::font::FontRenderInfo;
|
||||
use crate::text::font::Fonts;
|
||||
@ -37,15 +38,15 @@ use web_sys::WebGlTexture;
|
||||
/// commits
|
||||
#[derive(Debug)]
|
||||
pub struct TextComponent {
|
||||
pub lines : Vec<String>,
|
||||
pub font : FontId,
|
||||
pub position : Point2<f64>,
|
||||
pub size : Vector2<f64>,
|
||||
pub text_size : f64,
|
||||
gl_context : WebGl2RenderingContext,
|
||||
gl_program : Program,
|
||||
gl_msdf_texture : WebGlTexture,
|
||||
buffers : TextComponentBuffers,
|
||||
pub content : TextComponentContent,
|
||||
pub position : Point2<f64>,
|
||||
pub size : Vector2<f64>,
|
||||
pub text_size : f64,
|
||||
gl_context : WebGl2RenderingContext,
|
||||
gl_program : Program,
|
||||
gl_msdf_texture : WebGlTexture,
|
||||
msdf_texture_rows : usize,
|
||||
buffers : TextComponentBuffers,
|
||||
}
|
||||
|
||||
impl TextComponent {
|
||||
@ -75,15 +76,17 @@ impl TextComponent {
|
||||
|
||||
/// Render text
|
||||
pub fn display(&mut self, fonts:&mut Fonts) {
|
||||
self.buffers.refresh(&self.gl_context,self.content.refresh_info(fonts));
|
||||
|
||||
if self.msdf_texture_rows != fonts.get_render_info(self.content.font).msdf_texture.rows() {
|
||||
self.update_msdf_texture(fonts);
|
||||
}
|
||||
|
||||
let gl_context = &self.gl_context;
|
||||
let vertices_count = self.buffers.vertices_count() as i32;
|
||||
let lines = &mut self.lines;
|
||||
let font = fonts.get_render_info(self.font);
|
||||
let content_ref = ContentRef{lines,font};
|
||||
|
||||
self.buffers.refresh(gl_context,content_ref);
|
||||
gl_context.use_program(Some(&self.gl_program));
|
||||
self.update_uniforms();
|
||||
self.update_uniforms(fonts);
|
||||
self.bind_buffer_to_attribute("position",&self.buffers.vertex_position);
|
||||
self.bind_buffer_to_attribute("tex_coord",&self.buffers.texture_coords);
|
||||
self.setup_blending();
|
||||
@ -91,13 +94,39 @@ impl TextComponent {
|
||||
gl_context.draw_arrays(WebGl2RenderingContext::TRIANGLES,0,vertices_count);
|
||||
}
|
||||
|
||||
fn update_uniforms(&self) {
|
||||
let gl_context = &self.gl_context;
|
||||
let to_scene = self.to_scene_matrix();
|
||||
let to_scene_ref = to_scene.as_ref();
|
||||
let to_scene_loc = gl_context.get_uniform_location(&self.gl_program,"to_scene");
|
||||
let transpose = false;
|
||||
fn update_uniforms(&self, fonts:&mut Fonts) {
|
||||
let gl_context = &self.gl_context;
|
||||
let to_scene = self.to_scene_matrix();
|
||||
let to_scene_ref = to_scene.as_ref();
|
||||
let msdf_width = MsdfTexture::WIDTH as f32;
|
||||
let msdf_height = fonts.get_render_info(self.content.font).msdf_texture.rows() as f32;
|
||||
let to_scene_loc = gl_context.get_uniform_location(&self.gl_program,"to_scene");
|
||||
let msdf_size_loc = gl_context.get_uniform_location(&self.gl_program,"msdf_size");
|
||||
let transpose = false;
|
||||
gl_context.uniform_matrix3fv_with_f32_array(to_scene_loc.as_ref(),transpose,to_scene_ref);
|
||||
gl_context.uniform2f(msdf_size_loc.as_ref(),msdf_width,msdf_height);
|
||||
|
||||
}
|
||||
|
||||
fn update_msdf_texture(&mut self, fonts:&mut Fonts) {
|
||||
let gl_context = &self.gl_context;
|
||||
let font_msdf = &fonts.get_render_info(self.content.font).msdf_texture;
|
||||
let target = Context::TEXTURE_2D;
|
||||
let width = MsdfTexture::WIDTH as i32;
|
||||
let height = font_msdf.rows() as i32;
|
||||
let border = 0;
|
||||
let tex_level = 0;
|
||||
let format = Context::RGB;
|
||||
let internal_fmt = Context::RGB as i32;
|
||||
let tex_type = Context::UNSIGNED_BYTE;
|
||||
let data = Some(font_msdf.data.as_slice());
|
||||
|
||||
gl_context.bind_texture(target,Some(&self.gl_msdf_texture));
|
||||
let tex_image_result =
|
||||
gl_context.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array
|
||||
(target,tex_level,internal_fmt,width,height,border,format,tex_type,data);
|
||||
tex_image_result.unwrap();
|
||||
self.msdf_texture_rows = font_msdf.rows();
|
||||
}
|
||||
|
||||
fn to_scene_matrix(&self) -> SmallVec<[f32;9]> {
|
||||
@ -172,25 +201,19 @@ impl<'a,'b,Str:AsRef<str>> TextComponentBuilder<'a,'b,Str> {
|
||||
/// Build a new text component rendering on given workspace
|
||||
pub fn build(mut self) -> TextComponent {
|
||||
self.load_all_chars();
|
||||
let gl_context = self.workspace.context.clone();
|
||||
let gl_program = self.create_program(&gl_context);
|
||||
let gl_msdf_texture = self.create_msdf_texture(&gl_context);
|
||||
let lines = self.split_lines();
|
||||
let font = self.fonts.get_render_info(self.font_id);
|
||||
let display_size = self.size / self.text_size;
|
||||
let content_ref = ContentRef{lines:lines.as_ref(),font};
|
||||
let buffers = TextComponentBuffers::new(&gl_context,display_size,content_ref);
|
||||
let gl_context = self.workspace.context.clone();
|
||||
let gl_program = self.create_program(&gl_context);
|
||||
let gl_msdf_texture = self.create_msdf_texture(&gl_context);
|
||||
let display_size = self.size / self.text_size;
|
||||
let mut content = TextComponentContent::new(self.font_id,self.text.as_ref());
|
||||
let initial_refresh = content.refresh_info(self.fonts);
|
||||
let buffers = TextComponentBuffers::new(&gl_context,display_size,initial_refresh);
|
||||
self.setup_constant_uniforms(&gl_context,&gl_program);
|
||||
TextComponent {
|
||||
lines,
|
||||
font: self.font_id,
|
||||
position: self.position,
|
||||
size: self.size,
|
||||
text_size: self.text_size,
|
||||
gl_context,
|
||||
gl_program,
|
||||
gl_msdf_texture,
|
||||
buffers,
|
||||
TextComponent {content,gl_context,gl_program,gl_msdf_texture,buffers,
|
||||
position : self.position,
|
||||
size : self.size,
|
||||
text_size : self.text_size,
|
||||
msdf_texture_rows : 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,12 +223,6 @@ impl<'a,'b,Str:AsRef<str>> TextComponentBuilder<'a,'b,Str> {
|
||||
}
|
||||
}
|
||||
|
||||
fn split_lines(&self) -> Vec<String> {
|
||||
let lines_text = self.text.as_ref().split('\n');
|
||||
let lines_iter = lines_text.map(|line| line.to_string());
|
||||
lines_iter.collect()
|
||||
}
|
||||
|
||||
fn create_program(&self, gl_context:&Context) -> Program {
|
||||
let vert_shader = self.create_vertex_shader(gl_context);
|
||||
let frag_shader = self.create_fragment_shader(gl_context);
|
||||
@ -229,36 +246,14 @@ impl<'a,'b,Str:AsRef<str>> TextComponentBuilder<'a,'b,Str> {
|
||||
fn create_msdf_texture(&mut self, gl_ctx:&Context)
|
||||
-> WebGlTexture {
|
||||
let msdf_texture = gl_ctx.create_texture().unwrap();
|
||||
let font_msdf = &self.fonts.get_render_info(self.font_id).msdf_texture;
|
||||
let target = Context::TEXTURE_2D;
|
||||
let wrap = Context::CLAMP_TO_EDGE as i32;
|
||||
let min_filter = Context::LINEAR as i32;
|
||||
let width = MsdfTexture::WIDTH as i32;
|
||||
let height = font_msdf.rows() as i32;
|
||||
let border = 0;
|
||||
let tex_level = 0;
|
||||
let format = Context::RGB;
|
||||
let internal_fmt = Context::RGB as i32;
|
||||
let tex_type = Context::UNSIGNED_BYTE;
|
||||
let data = Some(font_msdf.data.as_slice());
|
||||
|
||||
gl_ctx.bind_texture(target,Some(&msdf_texture));
|
||||
gl_ctx.tex_parameteri(target,Context::TEXTURE_WRAP_S,wrap);
|
||||
gl_ctx.tex_parameteri(target,Context::TEXTURE_WRAP_T,wrap);
|
||||
gl_ctx.tex_parameteri(target,Context::TEXTURE_MIN_FILTER,min_filter);
|
||||
let tex_image_result =
|
||||
gl_ctx.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array
|
||||
( target
|
||||
, tex_level
|
||||
, internal_fmt
|
||||
, width
|
||||
, height
|
||||
, border
|
||||
, format
|
||||
, tex_type
|
||||
, data
|
||||
);
|
||||
tex_image_result.unwrap();
|
||||
msdf_texture
|
||||
}
|
||||
|
||||
@ -269,14 +264,11 @@ impl<'a,'b,Str:AsRef<str>> TextComponentBuilder<'a,'b,Str> {
|
||||
let bottom = self.position.y as f32;
|
||||
let color = &self.color;
|
||||
let range = FontRenderInfo::MSDF_PARAMS.range as f32;
|
||||
let msdf_width = MsdfTexture::WIDTH as f32;
|
||||
let msdf_height = self.fonts.get_render_info(self.font_id).msdf_texture.rows() as f32;
|
||||
let clip_lower_loc = gl_context.get_uniform_location(gl_program,"clip_lower");
|
||||
let clip_upper_loc = gl_context.get_uniform_location(gl_program,"clip_upper");
|
||||
let color_loc = gl_context.get_uniform_location(gl_program,"color");
|
||||
let range_loc = gl_context.get_uniform_location(gl_program,"range");
|
||||
let msdf_loc = gl_context.get_uniform_location(gl_program,"msdf");
|
||||
let msdf_size_loc = gl_context.get_uniform_location(gl_program,"msdf_size");
|
||||
|
||||
gl_context.use_program(Some(gl_program));
|
||||
gl_context.uniform2f(clip_lower_loc.as_ref(),left,bottom);
|
||||
@ -284,6 +276,5 @@ impl<'a,'b,Str:AsRef<str>> TextComponentBuilder<'a,'b,Str> {
|
||||
gl_context.uniform4f(color_loc.as_ref(),color.r,color.g,color.b,color.a);
|
||||
gl_context.uniform1f(range_loc.as_ref(),range);
|
||||
gl_context.uniform1i(msdf_loc.as_ref(),0);
|
||||
gl_context.uniform2f(msdf_size_loc.as_ref(),msdf_width,msdf_height);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use crate::text::buffer::glyph_square::GlyphVertexPositionBuilder;
|
||||
use crate::text::buffer::glyph_square::GlyphTextureCoordsBuilder;
|
||||
use crate::text::buffer::fragment::BufferFragments;
|
||||
use crate::text::buffer::fragment::FragmentsDataBuilder;
|
||||
use crate::text::content::RefreshInfo;
|
||||
use crate::text::font::FontRenderInfo;
|
||||
|
||||
use basegl_backend_webgl::Context;
|
||||
@ -38,19 +39,13 @@ pub struct TextComponentBuffers {
|
||||
scroll_since_last_frame : Vector2<f64>
|
||||
}
|
||||
|
||||
/// References to all needed stuff for generating buffer's data.
|
||||
pub struct ContentRef<'a, 'b> {
|
||||
pub lines : &'a[String],
|
||||
pub font : &'b mut FontRenderInfo,
|
||||
}
|
||||
|
||||
impl TextComponentBuffers {
|
||||
/// Create and initialize buffers.
|
||||
pub fn new(gl_context:&Context, display_size:Vector2<f64>, content:ContentRef)
|
||||
pub fn new(gl_context:&Context, display_size:Vector2<f64>, refresh:RefreshInfo)
|
||||
-> TextComponentBuffers {
|
||||
let mut content_mut = content;
|
||||
let mut buffers = Self::create_uninitialized(gl_context,display_size,&mut content_mut);
|
||||
buffers.setup_buffers(gl_context,content_mut);
|
||||
let mut ref_mut = refresh;
|
||||
let mut buffers = Self::create_uninitialized(gl_context,display_size,&mut ref_mut);
|
||||
buffers.setup_buffers(gl_context,ref_mut);
|
||||
buffers
|
||||
}
|
||||
|
||||
@ -71,23 +66,24 @@ impl TextComponentBuffers {
|
||||
}
|
||||
|
||||
/// Refresh the whole buffers data.
|
||||
pub fn refresh(&mut self, gl_context:&Context, content:ContentRef) {
|
||||
pub fn refresh(&mut self, gl_context:&Context, info:RefreshInfo) {
|
||||
let scrolled_x = self.scroll_since_last_frame.x != 0.0;
|
||||
let scrolled_y = self.scroll_since_last_frame.y != 0.0;
|
||||
if scrolled_y {
|
||||
let displayed_lines = self.displayed_lines(content.lines.len());
|
||||
let displayed_lines = self.displayed_lines(info.lines.len());
|
||||
self.fragments.reassign_fragments(displayed_lines);
|
||||
}
|
||||
if scrolled_x {
|
||||
let displayed_x = self.displayed_x_range();
|
||||
let x_scroll = self.scroll_since_last_frame.x;
|
||||
let lines = content.lines;
|
||||
let lines = info.lines;
|
||||
self.fragments.mark_dirty_after_x_scrolling(x_scroll,displayed_x,lines);
|
||||
}
|
||||
if scrolled_x || scrolled_y {
|
||||
if scrolled_x || scrolled_y || info.dirty_lines.any_dirty() {
|
||||
self.fragments.mark_lines_dirty(&info.dirty_lines);
|
||||
let opt_dirty_range = self.fragments.minimum_fragments_range_with_all_dirties();
|
||||
if let Some(dirty_range) = opt_dirty_range {
|
||||
self.refresh_fragments(gl_context,dirty_range,content); // Note[refreshing buffers]
|
||||
self.refresh_fragments(gl_context,dirty_range,info); // Note[refreshing buffers]
|
||||
}
|
||||
self.scroll_since_last_frame = Vector2::new(0.0,0.0);
|
||||
}
|
||||
@ -100,14 +96,15 @@ impl TextComponentBuffers {
|
||||
* not-dirty fragments.
|
||||
*/
|
||||
|
||||
fn create_uninitialized(gl_context:&Context, display_size:Vector2<f64>, content:&mut ContentRef)
|
||||
fn create_uninitialized
|
||||
(gl_context:&Context, display_size:Vector2<f64>, refresh:&mut RefreshInfo)
|
||||
-> TextComponentBuffers {
|
||||
// Display_size.(x/y).floor() makes space for all lines/glyphs that fit in space in
|
||||
// their full size. But we have 2 more lines/glyphs: one clipped from top or left, and one
|
||||
// from bottom or right.
|
||||
const ADDITIONAL: usize = 2;
|
||||
let displayed_lines = display_size.y.floor() as usize + ADDITIONAL;
|
||||
let space_width = content.font.get_glyph_info(' ').advance;
|
||||
let space_width = refresh.font.get_glyph_info(' ').advance;
|
||||
let displayed_chars = (display_size.x/space_width).floor();
|
||||
// This margin is to ensure, that after x scrolling we won't need to refresh all the lines
|
||||
// at once.
|
||||
@ -144,13 +141,14 @@ impl TextComponentBuffers {
|
||||
is_valid.and_option_from(|| Some(index as usize))
|
||||
}
|
||||
|
||||
fn setup_buffers(&mut self, gl_context:&Context, content:ContentRef) {
|
||||
let displayed_lines = self.displayed_lines(content.lines.len());
|
||||
let all_fragments = 0..self.fragments.fragments.len();
|
||||
let mut builder = self.create_fragments_data_builder(content.font);
|
||||
fn setup_buffers(&mut self, gl_context:&Context, refresh:RefreshInfo) {
|
||||
let displayed_lines = self.displayed_lines(refresh.lines.len());
|
||||
let lines = refresh.lines;
|
||||
let all_fragments = 0..self.fragments.fragments.len();
|
||||
let mut builder = self.create_fragments_data_builder(refresh.font);
|
||||
|
||||
self.fragments.reassign_fragments(displayed_lines);
|
||||
self.fragments.build_buffer_data_for_fragments(all_fragments,&mut builder,content.lines);
|
||||
self.fragments.build_buffer_data_for_fragments(all_fragments,&mut builder,lines);
|
||||
let vertex_position_data = builder.vertex_position_data.as_ref();
|
||||
let texture_coords_data = builder.texture_coords_data.as_ref();
|
||||
self.set_buffer_data(gl_context,&self.vertex_position, vertex_position_data);
|
||||
@ -158,13 +156,13 @@ impl TextComponentBuffers {
|
||||
}
|
||||
|
||||
fn refresh_fragments
|
||||
(&mut self, gl_context:&Context, indexes:RangeInclusive<usize>, content:ContentRef) {
|
||||
let ofsset = *indexes.start();
|
||||
let mut builder = self.create_fragments_data_builder(content.font);
|
||||
(&mut self, gl_context:&Context, indexes:RangeInclusive<usize>, refresh:RefreshInfo) {
|
||||
let offset = *indexes.start();
|
||||
let mut builder = self.create_fragments_data_builder(refresh.font);
|
||||
|
||||
self.fragments.build_buffer_data_for_fragments(indexes,&mut builder,content.lines);
|
||||
self.set_vertex_position_buffer_subdata(gl_context,ofsset,&builder);
|
||||
self.set_texture_coords_buffer_subdata (gl_context,ofsset,&builder);
|
||||
self.fragments.build_buffer_data_for_fragments(indexes,&mut builder,refresh.lines.as_ref());
|
||||
self.set_vertex_position_buffer_subdata(gl_context,offset,&builder);
|
||||
self.set_texture_coords_buffer_subdata (gl_context,offset,&builder);
|
||||
}
|
||||
|
||||
fn create_fragments_data_builder<'a>(&self, font:&'a mut FontRenderInfo)
|
||||
|
@ -4,6 +4,7 @@ use crate::text::buffer::glyph_square::Pen;
|
||||
use crate::text::buffer::glyph_square::GlyphVertexPositionBuilder;
|
||||
use crate::text::buffer::glyph_square::GlyphTextureCoordsBuilder;
|
||||
use crate::text::buffer::line::LineAttributeBuilder;
|
||||
use crate::text::content::DirtyLines;
|
||||
use crate::text::font::FontRenderInfo;
|
||||
|
||||
use nalgebra::geometry::Point2;
|
||||
@ -121,6 +122,7 @@ impl<'a> FragmentsDataBuilder<'a> {
|
||||
let first_char_ref = first_char.as_ref();
|
||||
let rendered_text = first_char_ref.map_or(line, |rch| &line[rch.byte_offset..]);
|
||||
let last_char = first_char_ref.map(|fc| self.last_rendered_char(&fc,rendered_text));
|
||||
|
||||
self.build_vertex_positions(&pen,rendered_text);
|
||||
self.build_texture_coords(&rendered_text);
|
||||
match (first_char,last_char.flatten()) {
|
||||
@ -167,14 +169,16 @@ impl<'a> FragmentsDataBuilder<'a> {
|
||||
pub fn build_vertex_positions(&mut self, pen:&Pen, text:&str) {
|
||||
let rendering_pen = Pen::new(pen.position);
|
||||
let glyph_builder = GlyphVertexPositionBuilder::new(self.font,rendering_pen);
|
||||
let builder = LineAttributeBuilder::new(text,glyph_builder,self.max_chars_in_fragment);
|
||||
let max_line_size = self.max_chars_in_fragment;
|
||||
let builder = LineAttributeBuilder::new(text,glyph_builder,max_line_size);
|
||||
self.vertex_position_data.extend(builder.flatten().map(|f| f as f32));
|
||||
}
|
||||
|
||||
/// Extend texture coordinates data with a new line's.
|
||||
pub fn build_texture_coords(&mut self, text:&str) {
|
||||
let glyph_builder = GlyphTextureCoordsBuilder::new(self.font);
|
||||
let builder = LineAttributeBuilder::new(text,glyph_builder,self.max_chars_in_fragment);
|
||||
let max_line_size = self.max_chars_in_fragment;
|
||||
let builder = LineAttributeBuilder::new(text,glyph_builder,max_line_size);
|
||||
self.texture_coords_data.extend(builder.flatten().map(|f| f as f32));
|
||||
}
|
||||
}
|
||||
@ -292,6 +296,14 @@ impl BufferFragments {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark as dirty all fragments with dirty assigned line.
|
||||
pub fn mark_lines_dirty(&mut self, lines:&DirtyLines) {
|
||||
let not_yet_dirty = self.fragments.iter_mut().filter(|f| !f.dirty);
|
||||
for fragment in not_yet_dirty {
|
||||
fragment.dirty = fragment.assigned_line.map_or(false, |l| lines.is_dirty(l));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the minimum fragment id range covering all dirties.
|
||||
pub fn minimum_fragments_range_with_all_dirties(&self) -> Option<RangeInclusive<usize>> {
|
||||
let fragments = self.fragments.iter().enumerate();
|
||||
@ -318,6 +330,7 @@ impl BufferFragments {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -138,6 +138,9 @@ impl<'a> GlyphAttributeBuilder for GlyphVertexPositionBuilder<'a> {
|
||||
type Output = SmallVec<[f64;12]>; // Note[Output size]
|
||||
|
||||
/// Compute vertices for the next glyph.
|
||||
///
|
||||
/// The vertices position are the final vertices passed to webgl buffer. It takes the previous
|
||||
/// built glyph into consideration for proper spacing.
|
||||
fn build_for_next_glyph(&mut self, ch:char) -> Self::Output {
|
||||
self.pen.next_char(ch, self.font);
|
||||
let to_pen_position = self.translation_by_pen_position();
|
||||
@ -170,50 +173,52 @@ impl<'a> GlyphTextureCoordsBuilder<'a> {
|
||||
GlyphTextureCoordsBuilder {font}
|
||||
}
|
||||
|
||||
/// Convert base layout to msdf space.
|
||||
///
|
||||
/// The base layout contains vertices within (0.0, 0.0) - (1.0, 1.0) range. In msdf
|
||||
/// space we use distances expressed in msdf cells.
|
||||
pub fn base_layout_to_msdf_space() -> Affine2<f64> {
|
||||
let scale_x = MsdfTexture::WIDTH as f64;
|
||||
let scale_y = MsdfTexture::ONE_GLYPH_HEIGHT as f64;
|
||||
let matrix = Matrix3::new
|
||||
( scale_x, 0.0 , 0.0
|
||||
, 0.0 , scale_y, 0.0
|
||||
, 0.0 , 0.0 , 1.0
|
||||
);
|
||||
Affine2::from_matrix_unchecked(matrix)
|
||||
}
|
||||
|
||||
/// Transformation aligning borders to MSDF cell center
|
||||
///
|
||||
/// Each cell in MSFD contains a distance measured from its center, therefore the borders of
|
||||
/// glyph's square should be matched with center of MSDF cells to read distance properly.
|
||||
///
|
||||
/// The transformation's input should be a point in _single MSDF space_, where (0.0, 0.0) is
|
||||
/// the bottom-left corner of MSDF, and (1.0, 1.0) is the top-right corner.
|
||||
pub fn align_borders_to_msdf_cell_center_transform(&self) -> Affine2<f64> {
|
||||
/// the bottom-left corner of MSDF, and each cell have size of 1.0.
|
||||
pub fn align_borders_to_msdf_cell_center_transform() -> Affine2<f64> {
|
||||
let columns = MsdfTexture::WIDTH as f64;
|
||||
let rows = MsdfTexture::ONE_GLYPH_HEIGHT as f64;
|
||||
let column_size = 1.0 / columns;
|
||||
let row_size = 1.0 / rows;
|
||||
|
||||
let translation_x = column_size / 2.0;
|
||||
let translation_y = row_size / 2.0;
|
||||
let scale_x = 1.0 - column_size;
|
||||
let scale_y = 1.0 - row_size;
|
||||
let translation_x = 0.5;
|
||||
let translation_y = 0.5;
|
||||
let scale_x = (columns - 1.0) / columns;
|
||||
let scale_y = (rows - 1.0) / rows;
|
||||
let matrix = Matrix3::new
|
||||
( scale_x, 0.0 , translation_x
|
||||
, 0.0 , scale_y, translation_y
|
||||
, 0.0 , 0.0 , 1.0
|
||||
);
|
||||
( scale_x, 0.0 , translation_x
|
||||
, 0.0 , scale_y, translation_y
|
||||
, 0.0 , 0.0 , 1.0
|
||||
);
|
||||
Affine2::from_matrix_unchecked(matrix)
|
||||
}
|
||||
|
||||
/// Transformation MSDF texture fragment associated with given glyph
|
||||
///
|
||||
/// The MSDF texture contains MSDFs for many glyphs. The returned transform maps the point in
|
||||
/// a _single MSDF space_ to actual texture space. In other words, a (0.0, 0.0) point will be
|
||||
/// mapped to bottom-left corner of `ch` texture fragment, and a (1.0, 1.0) will be mapped to
|
||||
/// upper-right corner.
|
||||
pub fn glyph_texture_fragment_transform(&mut self, ch:char) -> Affine2<f64> {
|
||||
let one_glyph_rows = MsdfTexture::ONE_GLYPH_HEIGHT as f64;
|
||||
let all_rows = self.font.msdf_texture.rows() as f64;
|
||||
|
||||
let fraction = one_glyph_rows / all_rows;
|
||||
/// The MSDF texture contains MSDFs for many glyphs, so this translation moves points expressed
|
||||
/// in "single" msdf space to actual texture coordinates.
|
||||
pub fn glyph_texture_fragment_transform(&mut self, ch:char) -> Translation2<f64> {
|
||||
let glyph_info = self.font.get_glyph_info(ch);
|
||||
let offset = glyph_info.msdf_texture_rows.start as f64 / all_rows;
|
||||
let matrix = nalgebra::Matrix3::new
|
||||
( 1.0, 0.0 , 0.0
|
||||
, 0.0, fraction, offset
|
||||
, 0.0, 0.0 , 1.0
|
||||
);
|
||||
Affine2::from_matrix_unchecked(matrix)
|
||||
let offset_y = glyph_info.msdf_texture_rows.start as f64;
|
||||
Translation2::new(0.0, offset_y)
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,13 +228,14 @@ impl<'a> GlyphAttributeBuilder for GlyphTextureCoordsBuilder<'a> {
|
||||
|
||||
type Output = SmallVec<[f64; 12]>; // Note[Output size]
|
||||
|
||||
/// Compute texture coordinates for `ch`
|
||||
/// Compute texture coordinates for `ch`.
|
||||
fn build_for_next_glyph(&mut self, ch:char) -> Self::Output {
|
||||
let border_align = self.align_borders_to_msdf_cell_center_transform();
|
||||
let to_msdf = Self::base_layout_to_msdf_space();
|
||||
let border_align = Self::align_borders_to_msdf_cell_center_transform();
|
||||
let to_proper_fragment = self.glyph_texture_fragment_transform(ch);
|
||||
|
||||
let base = GLYPH_SQUARE_VERTICES_BASE_LAYOUT.iter();
|
||||
let aligned_to_border = base .map(|p| border_align * p);
|
||||
let aligned_to_border = base .map(|p| border_align * to_msdf * p);
|
||||
let transformed = aligned_to_border.map(|p| to_proper_fragment * p);
|
||||
transformed.map(point_to_iterable).flatten().collect()
|
||||
}
|
||||
@ -330,20 +336,20 @@ mod tests {
|
||||
let w_texture_coords = builder.build_for_next_glyph('W');
|
||||
|
||||
let expected_a_coords = &
|
||||
[ 1./64. , 1./128.
|
||||
, 1./64. , 63./128.
|
||||
, 63./64. , 1./128.
|
||||
, 63./64. , 1./128.
|
||||
, 1./64. , 63./128.
|
||||
, 63./64. , 63./128.
|
||||
[ 0.5 , 0.5
|
||||
, 0.5 , 31.5
|
||||
, 31.5 , 0.5
|
||||
, 31.5 , 0.5
|
||||
, 0.5 , 31.5
|
||||
, 31.5 , 31.5
|
||||
];
|
||||
let expected_w_coords = &
|
||||
[ 1./64. , 65./128.
|
||||
, 1./64. , 127./128.
|
||||
, 63./64. , 65./128.
|
||||
, 63./64. , 65./128.
|
||||
, 1./64. , 127./128.
|
||||
, 63./64. , 127./128.
|
||||
[ 0.5 , 32.5
|
||||
, 0.5 , 63.5
|
||||
, 31.5 , 32.5
|
||||
, 31.5 , 32.5
|
||||
, 0.5 , 63.5
|
||||
, 31.5 , 63.5
|
||||
];
|
||||
|
||||
assert_eq!(expected_a_coords, a_texture_coords.as_ref());
|
||||
|
379
gui/lib/core/src/text/content.rs
Normal file
379
gui/lib/core/src/text/content.rs
Normal file
@ -0,0 +1,379 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::text::font::FontId;
|
||||
use crate::text::font::FontRenderInfo;
|
||||
use crate::text::font::Fonts;
|
||||
|
||||
use std::ops::Range;
|
||||
use std::ops::RangeFrom;
|
||||
use failure::_core::ops::RangeInclusive;
|
||||
|
||||
|
||||
// ==================
|
||||
// === DirtyLines ===
|
||||
// ==================
|
||||
|
||||
/// Set of dirty lines' indices
|
||||
#[derive(Debug)]
|
||||
pub struct DirtyLines {
|
||||
pub single_lines : HashSet<usize>,
|
||||
pub range : Option<RangeFrom<usize>>
|
||||
}
|
||||
|
||||
impl Default for DirtyLines {
|
||||
/// Default `DirtyLines` where no line is dirty.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
single_lines : HashSet::new(),
|
||||
range : None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirtyLines {
|
||||
/// Mark single line as dirty.
|
||||
pub fn add_single_line(&mut self, index:usize) {
|
||||
self.single_lines.insert(index);
|
||||
}
|
||||
|
||||
/// Mark an open range of lines as dirty.
|
||||
pub fn add_lines_range_from(&mut self, range:RangeFrom<usize>) {
|
||||
let current_is_wider = self.range.as_ref().map_or(false, |cr| cr.start <= range.start);
|
||||
if !current_is_wider {
|
||||
self.range = Some(range);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark an open range of lines as dirty.
|
||||
pub fn add_lines_range(&mut self, range:RangeInclusive<usize>) {
|
||||
for i in range {
|
||||
self.add_single_line(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if line is marked as dirty.
|
||||
pub fn is_dirty(&self, index:usize) -> bool {
|
||||
let range_contains = self.range.as_ref().map_or(false, |r| r.contains(&index));
|
||||
range_contains || self.single_lines.contains(&index)
|
||||
}
|
||||
|
||||
/// Check if there is any dirty line
|
||||
pub fn any_dirty(&self) -> bool {
|
||||
self.range.is_some() || !self.single_lines.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// ==============
|
||||
// === Change ===
|
||||
// ==============
|
||||
|
||||
/// A change type
|
||||
///
|
||||
/// A change is simple if it's replace a fragment of one line with text without new lines. Otherwise
|
||||
/// its a multiline change.
|
||||
pub enum ChangeType {
|
||||
Simple, Multiline
|
||||
}
|
||||
|
||||
/// A structure describing a text operation in one place.
|
||||
pub struct TextChange {
|
||||
replaced : Range<CharPosition>,
|
||||
lines : Vec<String>,
|
||||
}
|
||||
|
||||
impl TextChange {
|
||||
/// Creates operation which inserts text at given position.
|
||||
pub fn insert(position:CharPosition, text:&str) -> Self {
|
||||
TextChange {
|
||||
replaced : position.clone()..position,
|
||||
lines : TextComponentContent::split_to_lines(text)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates operation which deletes text at given range.
|
||||
pub fn delete(range:Range<CharPosition>) -> Self {
|
||||
TextChange {
|
||||
replaced : range,
|
||||
lines : vec!["".to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates operation which replaces text at given range with given string.
|
||||
pub fn replace(replaced:Range<CharPosition>, text:&str) -> Self {
|
||||
TextChange {replaced,
|
||||
lines : TextComponentContent::split_to_lines(text)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type of this change. See `ChangeType` doc for details.
|
||||
pub fn change_type(&self) -> ChangeType {
|
||||
if self.lines.is_empty() {
|
||||
panic!("Invalid change");
|
||||
}
|
||||
let is_one_line_modified = self.replaced.start.line == self.replaced.end.line;
|
||||
let is_one_line_inserted = self.lines.len() == 1;
|
||||
if is_one_line_modified && is_one_line_inserted {
|
||||
ChangeType::Simple
|
||||
} else {
|
||||
ChangeType::Multiline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// === TextComponentContent ===
|
||||
// ============================
|
||||
|
||||
/// The content of text component - namely lines of text.
|
||||
#[derive(Debug)]
|
||||
pub struct TextComponentContent {
|
||||
pub lines : Vec<String>,
|
||||
pub dirty_lines : DirtyLines,
|
||||
pub font : FontId,
|
||||
}
|
||||
|
||||
/// A position of character in multiline text.
|
||||
#[derive(Clone,PartialEq,Eq,PartialOrd,Ord)]
|
||||
pub struct CharPosition {
|
||||
pub line : usize,
|
||||
pub byte_offset : usize,
|
||||
}
|
||||
|
||||
/// References to all needed stuff for generating buffer's data.
|
||||
pub struct RefreshInfo<'a, 'b> {
|
||||
pub lines : &'a [String],
|
||||
pub dirty_lines : DirtyLines,
|
||||
pub font : &'b mut FontRenderInfo,
|
||||
}
|
||||
|
||||
impl TextComponentContent {
|
||||
/// Create a text component containing `text`
|
||||
///
|
||||
/// The text will be split to lines by `'\n'` characters.
|
||||
pub fn new(font_id:FontId, text:&str) -> Self {
|
||||
TextComponentContent {
|
||||
lines : Self::split_to_lines(text),
|
||||
dirty_lines : DirtyLines::default(),
|
||||
font : font_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn split_to_lines(text:&str) -> Vec<String> {
|
||||
let split = text.split('\n');
|
||||
let without_cr = split.map(Self::cut_cr_at_end_of_line);
|
||||
without_cr.map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
/// Cuts carriage return (also known as CR or `'\r'`) from line's end
|
||||
fn cut_cr_at_end_of_line(from:&str) -> &str {
|
||||
if from.ends_with('\r') {
|
||||
&from[..from.len()-1]
|
||||
} else {
|
||||
from
|
||||
}
|
||||
}
|
||||
|
||||
/// Get RefreshInfo for this content.
|
||||
///
|
||||
/// The dirty flags for lines are moved to returned content, so the `self` dirty flags will be
|
||||
/// cleared after this call.
|
||||
pub fn refresh_info<'a,'b>(&'a mut self, fonts:&'b mut Fonts) -> RefreshInfo<'a,'b> {
|
||||
RefreshInfo {
|
||||
lines : &mut self.lines,
|
||||
dirty_lines : std::mem::take(&mut self.dirty_lines),
|
||||
font : fonts.get_render_info(self.font)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply change to content.
|
||||
pub fn make_change(&mut self, change:TextChange) {
|
||||
match change.change_type() {
|
||||
ChangeType::Simple => self.make_simple_change(change),
|
||||
ChangeType::Multiline => self.make_multiline_change(change),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_simple_change(&mut self, change:TextChange) {
|
||||
let line_index = change.replaced.start.line;
|
||||
let new_content = change.lines.first().unwrap();
|
||||
let range = change.replaced.start.byte_offset..change.replaced.end.byte_offset;
|
||||
self.lines[line_index].replace_range(range, new_content);
|
||||
self.dirty_lines.add_single_line(line_index);
|
||||
}
|
||||
|
||||
fn make_multiline_change(&mut self, mut change:TextChange) {
|
||||
let start_line = change.replaced.start.line;
|
||||
let end_line = change.replaced.end.line;
|
||||
let replaced_lines_count = end_line - start_line + 1;
|
||||
let inserted_lines_count = change.lines.len();
|
||||
|
||||
self.mix_content_into_change(&mut change);
|
||||
self.lines.splice(start_line..=end_line, change.lines);
|
||||
if replaced_lines_count != inserted_lines_count {
|
||||
self.dirty_lines.add_lines_range_from(start_line..);
|
||||
} else {
|
||||
self.dirty_lines.add_lines_range(start_line..=end_line);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mix the unchanged parts of modified lines into change.
|
||||
///
|
||||
/// This is for convenience of making multiline content changes. After mixing existing content
|
||||
/// into change we can just operate on whole lines (replace the whole lines of current content
|
||||
/// with the whole lines-to-insert in change description).
|
||||
fn mix_content_into_change(&mut self, change:&mut TextChange) {
|
||||
self.mix_first_edited_line_into_change(change);
|
||||
self.mix_last_edited_line_into_change(change);
|
||||
}
|
||||
|
||||
fn mix_first_edited_line_into_change(&self, change:&mut TextChange) {
|
||||
let first_line = change.replaced.start.line;
|
||||
let replace_from = change.replaced.start.byte_offset;
|
||||
let first_edited = &self.lines[first_line];
|
||||
let prefix = &first_edited[..replace_from];
|
||||
change.lines.first_mut().unwrap().insert_str(0,prefix);
|
||||
}
|
||||
|
||||
fn mix_last_edited_line_into_change(&mut self, change:&mut TextChange) {
|
||||
let last_line = change.replaced.end.line;
|
||||
let replace_to = change.replaced.end.byte_offset;
|
||||
let last_edited = &self.lines[last_line];
|
||||
let suffix = &last_edited[replace_to..];
|
||||
change.lines.last_mut().unwrap().push_str(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mark_single_line_as_dirty() {
|
||||
let mut dirty_lines = DirtyLines::default();
|
||||
dirty_lines.add_single_line(3);
|
||||
dirty_lines.add_single_line(5);
|
||||
assert!( dirty_lines.is_dirty(3));
|
||||
assert!(!dirty_lines.is_dirty(4));
|
||||
assert!( dirty_lines.is_dirty(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_line_range_as_dirty() {
|
||||
let mut dirty_lines = DirtyLines::default();
|
||||
dirty_lines.add_lines_range(3..=5);
|
||||
assert!(!dirty_lines.is_dirty(2));
|
||||
assert!( dirty_lines.is_dirty(3));
|
||||
assert!( dirty_lines.is_dirty(4));
|
||||
assert!( dirty_lines.is_dirty(5));
|
||||
assert!(!dirty_lines.is_dirty(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_line_range_from_as_dirty() {
|
||||
let mut dirty_lines = DirtyLines::default();
|
||||
dirty_lines.add_lines_range_from(3..);
|
||||
dirty_lines.add_lines_range_from(5..);
|
||||
assert!(!dirty_lines.is_dirty(2));
|
||||
assert!( dirty_lines.is_dirty(3));
|
||||
assert!( dirty_lines.is_dirty(4));
|
||||
assert!( dirty_lines.is_dirty(70000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_content() {
|
||||
let font_id = 0;
|
||||
let single_line = "Single line";
|
||||
let mutliple_lines = "Multiple\nlines\n";
|
||||
|
||||
let single_line_content = TextComponentContent::new(font_id,single_line);
|
||||
let multiline_content = TextComponentContent::new(font_id,mutliple_lines);
|
||||
assert_eq!(1, single_line_content.lines.len());
|
||||
assert_eq!(3, multiline_content .lines.len());
|
||||
assert_eq!(single_line, single_line_content.lines[0]);
|
||||
assert_eq!("Multiple" , multiline_content .lines[0]);
|
||||
assert_eq!("lines" , multiline_content .lines[1]);
|
||||
assert_eq!("" , multiline_content .lines[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_single_line() {
|
||||
let text = "Line a\nLine b\nLine c";
|
||||
let delete_from = CharPosition{line:1, byte_offset:0};
|
||||
let delete_to = CharPosition{line:1, byte_offset:4};
|
||||
let deleted_range = delete_from..delete_to;
|
||||
let insert = TextChange::insert(CharPosition{line:1, byte_offset:1}, "ab");
|
||||
let delete = TextChange::delete(deleted_range.clone());
|
||||
let replace = TextChange::replace(deleted_range, "text");
|
||||
|
||||
let mut content = TextComponentContent::new(0, text);
|
||||
|
||||
content.make_change(insert);
|
||||
let expected = vec!["Line a", "Labine b", "Line c"];
|
||||
assert_eq!(expected, content.lines);
|
||||
|
||||
content.make_change(delete);
|
||||
let expected = vec!["Line a", "ne b", "Line c"];
|
||||
assert_eq!(expected, content.lines);
|
||||
|
||||
content.make_change(replace);
|
||||
let expected = vec!["Line a", "text", "Line c"];
|
||||
assert_eq!(expected, content.lines);
|
||||
|
||||
assert!(!content.dirty_lines.is_dirty(0));
|
||||
assert!( content.dirty_lines.is_dirty(1));
|
||||
assert!(!content.dirty_lines.is_dirty(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_multiple_lines() {
|
||||
let text = "Line a\nLine b\nLine c";
|
||||
let inserted = "Ins a\nIns b";
|
||||
let begin_position = CharPosition{line:0, byte_offset:0};
|
||||
let middle_position = CharPosition{line:1, byte_offset:2};
|
||||
let end_position = CharPosition{line:2, byte_offset:6};
|
||||
let insert_at_begin = TextChange::insert(begin_position , inserted);
|
||||
let insert_in_middle = TextChange::insert(middle_position, inserted);
|
||||
let insert_at_end = TextChange::insert(end_position , inserted);
|
||||
|
||||
let mut content = TextComponentContent::new(0,text);
|
||||
|
||||
content.make_change(insert_at_end);
|
||||
let expected = vec!["Line a", "Line b", "Line cIns a", "Ins b"];
|
||||
assert_eq!(expected, content.lines);
|
||||
assert!(!content.dirty_lines.is_dirty(0));
|
||||
assert!(!content.dirty_lines.is_dirty(1));
|
||||
assert!( content.dirty_lines.is_dirty(2));
|
||||
content.dirty_lines = default();
|
||||
|
||||
content.make_change(insert_in_middle);
|
||||
let expected = vec!["Line a", "LiIns a", "Ins bne b", "Line cIns a", "Ins b"];
|
||||
assert_eq!(expected, content.lines);
|
||||
assert!(!content.dirty_lines.is_dirty(0));
|
||||
assert!( content.dirty_lines.is_dirty(1));
|
||||
assert!( content.dirty_lines.is_dirty(2));
|
||||
content.dirty_lines = default();
|
||||
|
||||
content.make_change(insert_at_begin);
|
||||
let expected = vec!["Ins a", "Ins bLine a", "LiIns a", "Ins bne b", "Line cIns a", "Ins b"];
|
||||
assert_eq!(expected, content.lines);
|
||||
assert!( content.dirty_lines.is_dirty(0));
|
||||
assert!( content.dirty_lines.is_dirty(1));
|
||||
assert!( content.dirty_lines.is_dirty(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_multiple_lines() {
|
||||
let text = "Line a\nLine b\nLine c";
|
||||
let delete_from = CharPosition{line:0, byte_offset:2};
|
||||
let delete_to = CharPosition{line:2, byte_offset:3};
|
||||
let deleted_range = delete_from..delete_to;
|
||||
let delete = TextChange::delete(deleted_range);
|
||||
|
||||
let mut content = TextComponentContent::new(0,text);
|
||||
content.make_change(delete);
|
||||
|
||||
let expected = vec!["Lie c"];
|
||||
assert_eq!(expected, content.lines);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
in vec2 position;
|
||||
in vec2 tex_coord;
|
||||
|
||||
uniform highp vec2 msdf_size;
|
||||
uniform highp mat3 to_scene;
|
||||
uniform highp vec2 clip_lower;
|
||||
uniform highp vec2 clip_upper;
|
||||
@ -17,6 +18,6 @@ void main() {
|
||||
v_clip_distance.z = clip_upper.x - position_on_scene.x;
|
||||
v_clip_distance.w = clip_upper.y - position_on_scene.y;
|
||||
|
||||
v_tex_coord = tex_coord;
|
||||
v_tex_coord = tex_coord / msdf_size;
|
||||
gl_Position = vec4(position_on_scene.xy, 0.0, position_on_scene.z);
|
||||
}
|
||||
|
@ -82,6 +82,8 @@ mod tests {
|
||||
use super::WorldTest;
|
||||
use basegl::Color;
|
||||
use basegl::display::world::World;
|
||||
use basegl::text::content::TextChange;
|
||||
use basegl::text::content::CharPosition;
|
||||
use basegl::text::TextComponentBuilder;
|
||||
|
||||
use basegl_core_msdf_sys::run_once_initialized;
|
||||
@ -138,8 +140,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn scrolling_vertical(bencher:&mut Bencher) {
|
||||
if let Some(world_test) = WorldTest::new("scrolling_vertical") {
|
||||
fn scrolling_vertical_30(bencher:&mut Bencher) {
|
||||
if let Some(world_test) = WorldTest::new("scrolling_vertical_30") {
|
||||
let mut bencher_clone = bencher.clone();
|
||||
run_once_initialized(move || {
|
||||
create_full_sized_text_component(&world_test,LONG_TEXT.to_string());
|
||||
@ -158,8 +160,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn scrolling_horizontal(bencher:&mut Bencher) {
|
||||
if let Some(world_test) = WorldTest::new("scrolling_horizontal") {
|
||||
fn scrolling_horizontal_10(bencher:&mut Bencher) {
|
||||
if let Some(world_test) = WorldTest::new("scrolling_horizontal_10") {
|
||||
let mut bencher_clone = bencher.clone();
|
||||
run_once_initialized(move || {
|
||||
create_full_sized_text_component(&world_test,WIDE_TEXT.to_string());
|
||||
@ -177,6 +179,50 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn editing_single_long_line_20(bencher:&mut Bencher) {
|
||||
if let Some(world_test) = WorldTest::new("editing_single_long_line_20") {
|
||||
let mut bencher_clone = bencher.clone();
|
||||
run_once_initialized(move || {
|
||||
create_full_sized_text_component(&world_test,WIDE_TEXT.to_string());
|
||||
bencher_clone.iter(move || {
|
||||
let world : &mut World = &mut world_test.world_ptr.borrow_mut();
|
||||
for _ in 0..20 {
|
||||
let workspace = &mut world.workspaces[world_test.workspace_id];
|
||||
let text_component = &mut workspace.text_components[0];
|
||||
let replace_from = CharPosition{line:1, byte_offset:2};
|
||||
let replace_to = CharPosition{line:1, byte_offset:3};
|
||||
let replaced_range = replace_from..replace_to;
|
||||
let change = TextChange::replace(replaced_range, "abc");
|
||||
text_component.content.make_change(change);
|
||||
world.workspace_dirty.set(world_test.workspace_id);
|
||||
world.update();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn inserting_many_lines_in_long_file(bencher:&mut Bencher) {
|
||||
if let Some(world_test) = WorldTest::new("inserting_many_lines_in_long_file") {
|
||||
let mut bencher_clone = bencher.clone();
|
||||
run_once_initialized(move || {
|
||||
create_full_sized_text_component(&world_test,LONG_TEXT.to_string());
|
||||
bencher_clone.iter(move || {
|
||||
let world : &mut World = &mut world_test.world_ptr.borrow_mut();
|
||||
let workspace = &mut world.workspaces[world_test.workspace_id];
|
||||
let text_component = &mut workspace.text_components[0];
|
||||
let position = CharPosition{line:1, byte_offset:0};
|
||||
let change = TextChange::insert(position, TEST_TEXT);
|
||||
text_component.content.make_change(change);
|
||||
world.workspace_dirty.set(world_test.workspace_id);
|
||||
world.update();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn create_full_sized_text_component(world_test:&WorldTest, text:String) {
|
||||
let workspace_id = world_test.workspace_id;
|
||||
let world : &mut World = &mut world_test.world_ptr.borrow_mut();
|
||||
|
Loading…
Reference in New Issue
Block a user