Original commit: 87c1628448
This commit is contained in:
Danilo Guanabara 2020-04-15 10:10:10 -03:00 committed by GitHub
parent ad6fd9321e
commit 8ebb17d538
5 changed files with 139 additions and 67 deletions

View File

@ -26,13 +26,13 @@ use crate::display::shape::text::glyph::font::FontRegistry;
use crate::display::shape::text::text_field::render::TextFieldSprites;
use crate::display::shape::text::text_field::render::assignment::GlyphLinesAssignmentUpdate;
use crate::display::world::World;
use crate::system::web::text_input::KeyboardBinding;
use data::text::TextChange;
use data::text::TextLocation;
use nalgebra::Vector2;
use nalgebra::Vector3;
use nalgebra::Vector4;
use crate::system::web::text_input::KeyboardBinding;
@ -148,28 +148,28 @@ shared! { TextField
self.properties.size
}
/// Scroll one page down.
pub fn page_down(&mut self) {
self.scroll(Vector2::new(0.0, -self.size().y))
}
/// Scroll one page up.
pub fn page_up(&mut self) {
self.scroll(Vector2::new(0.0, self.size().y))
}
/// Scroll text by given offset in pixels.
pub fn scroll(&mut self, offset:Vector2<f32>) {
let position_change = -Vector3::new(offset.x,offset.y,0.0);
self.rendered.display_object.mod_position(|pos| *pos += position_change);
let mut update = self.assignment_update();
if offset.x != 0.0 {
update.update_after_x_scroll(offset.x);
let scroll_position = self.scroll_position();
let padding_lines = 2;
let lines = self.content.lines().len() + padding_lines;
let text_height = self.content.line_height * lines as f32;
let view_height = self.size().y;
let height = (text_height - view_height).max(0.0);
let offset_y = offset.y.min(scroll_position.y).max(scroll_position.y - height);
let offset = Vector2::new(offset.x, offset_y);
if offset.x != 0.0 || offset.y != 0.0 {
let position_change = -Vector3::new(offset.x,offset.y,0.0);
self.rendered.display_object.mod_position(|pos| *pos += position_change);
let mut update = self.assignment_update();
if offset.x != 0.0 {
update.update_after_x_scroll(offset.x);
}
if offset.y != 0.0 {
update.update_line_assignment();
}
self.rendered.update_glyphs(&mut self.content);
}
if offset.y != 0.0 {
update.update_line_assignment();
}
self.rendered.update_glyphs(&mut self.content);
}
/// Get current scroll position.
@ -204,21 +204,51 @@ shared! { TextField
/// Jump active cursor to point on the screen.
pub fn jump_cursor(&mut self, point:Vector2<f32>, selecting:bool) {
let point_on_text = self.relative_position(point);
let content = &mut self.content;
let mut navigation = CursorNavigation {selecting, ..CursorNavigation::default(content)};
let point_on_text = self.relative_position(point);
let text_field_size = self.size();
let content = &mut self.content;
let mut navigation = CursorNavigation{selecting,content,text_field_size};
self.cursors.jump_cursor(&mut navigation,point_on_text);
self.rendered.update_cursor_sprites(&self.cursors, &mut self.content,self.focused);
}
/// Processes PageUp and PageDown, scrolling the page accordingly.
fn scroll_page(&mut self, step:Step) {
let page_height = self.size().y;
let scrolling = match step {
Step::PageUp => page_height,
Step::PageDown => -page_height,
_ => 0.0
};
self.scroll(Vector2::new(0.0,scrolling));
}
/// Adjust the view to make the last cursor visible.
fn adjust_view(&mut self) {
let last_cursor = self.cursors.last_cursor();
let scroll_y = self.scroll_position().y;
let view_size = self.size();
let current_line = last_cursor.current_line(&mut self.content);
let current_line_pos = current_line.y_position();
let next_line_pos = current_line_pos + current_line.height;
let y_scrolling = (scroll_y - next_line_pos + view_size.y).min(0.0);
let y_scrolling = (scroll_y - current_line_pos).max(y_scrolling);
let scrolling = Vector2::new(0.0,y_scrolling);
self.scroll(scrolling);
}
/// Move all cursors by given step.
pub fn navigate_cursors(&mut self, step:Step, selecting:bool) {
if !selecting {
self.clear_word_occurrences()
}
let content = &mut self.content;
let mut navigation = CursorNavigation {content,selecting};
let text_field_size = self.size();
let content = &mut self.content;
let mut navigation = CursorNavigation{content,selecting,text_field_size};
self.cursors.navigate_all_cursors(&mut navigation,step);
self.scroll_page(step);
self.adjust_view();
self.rendered.update_cursor_sprites(&self.cursors, &mut self.content,self.focused);
}
@ -370,6 +400,7 @@ impl TextField {
// TODO[ao] updates should be done only in one place and only once per frame
// see https://github.com/luna/ide/issues/178
this.assignment_update().update_after_text_edit();
this.adjust_view();
this.rendered.update_glyphs(&mut this.content);
this.rendered.update_cursor_sprites(&this.cursors, &mut this.content, this.focused);
});
@ -385,11 +416,11 @@ impl TextField {
/// For cursors with selection it will just remove the selected text. For the rest, it will
/// remove all content covered by `step`.
pub fn do_delete_operation(&self, step:Step) {
let text_field_size = self.size();
self.with_borrowed(|this| {
let content = &mut this.content;
let selecting = true;
let mut navigation = CursorNavigation
{selecting,..CursorNavigation::default(content)};
let mut navigation = CursorNavigation{selecting,content,text_field_size};
let without_selection = |c:&Cursor| !c.has_selection();
this.cursors.navigate_cursors(&mut navigation,step,without_selection);
});

View File

@ -8,7 +8,6 @@ use nalgebra::Vector2;
use std::ops::Range;
// ============
// === Line ===
// ============
@ -99,11 +98,16 @@ pub struct LineFullInfo<'a> {
}
impl<'a> LineFullInfo<'a> {
/// Get the y position of the top of the line.
pub fn y_position(&self) -> f32 {
self.height * self.line_id as f32
}
/// Get the point where a _baseline_ of current line begins (The _baseline_ is a font specific
/// term, for details see [freetype documentation]
/// (https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html#section-1)).
pub fn baseline_start(&self) -> Vector2<f32> {
Vector2::new(0.0, (-(self.line_id as f32) - 0.85) * self.height)
Vector2::new(0.0, -self.y_position() - self.height * 0.85)
}
/// Get x position of character with given index. The position is in _text space_.

View File

@ -133,11 +133,14 @@ impl Cursor {
/// Home, End, Ctrl+Home, etc.)
#[derive(Copy,Clone,Debug,Eq,Hash,PartialEq)]
#[allow(missing_docs)]
pub enum Step {Left,LeftWord,Right,RightWord,Up,Down,LineBegin,LineEnd,DocBegin,DocEnd}
pub enum Step
{Left,LeftWord,Right,RightWord,PageUp,Up,PageDown,Down,LineBegin,LineEnd,DocBegin,DocEnd}
/// A struct for cursor navigation process.
#[derive(Debug)]
pub struct CursorNavigation<'a> {
/// A snapshot of TextField's size.
pub text_field_size: Vector2<f32>,
/// A reference to text content. This is required to obtain the x positions of chars for proper
/// moving cursors up and down.
pub content: &'a mut TextFieldContent,
@ -146,12 +149,6 @@ pub struct CursorNavigation<'a> {
}
impl<'a> CursorNavigation<'a> {
/// Creates a new CursorNavigation with defaults.
pub fn default(content:&'a mut TextFieldContent) -> Self {
let selecting = default();
Self {content,selecting}
}
/// Jump cursor directly to given position.
pub fn move_cursor_to_position(&self, cursor:&mut Cursor, to:TextLocation) {
cursor.position = to;
@ -220,16 +217,18 @@ impl<'a> CursorNavigation<'a> {
/// Get cursor position one line above the given position, such the new x coordinate of
/// displayed cursor on the screen will be nearest the current value.
pub fn line_up_position(&mut self, position:&TextLocation) -> Option<TextLocation> {
let prev_line = position.line.checked_sub(1);
prev_line.map(|line| self.near_same_x_in_another_line(position,line))
pub fn line_up_position(&mut self, position:&TextLocation, lines:usize) -> TextLocation {
let prev_line = position.line.checked_sub(lines);
let prev_line = prev_line.map(|line| self.near_same_x_in_another_line(position,line));
prev_line.unwrap_or_else(TextLocation::at_document_begin)
}
/// Get cursor position one line behind the given position, such the new x coordinate of
/// displayed cursor on the screen will be nearest the current value.
pub fn line_down_position(&mut self, position:&TextLocation) -> Option<TextLocation> {
let next_line = Some(position.line + 1).filter(|l| *l < self.content.lines().len());
next_line.map(|line| self.near_same_x_in_another_line(position,line))
pub fn line_down_position(&mut self, position:&TextLocation, lines:usize) -> TextLocation {
let next_line = Some(position.line + lines).filter(|l| *l < self.content.lines().len());
let next_line = next_line.map(|line| self.near_same_x_in_another_line(position,line));
next_line.unwrap_or_else(|| self.content_end_position())
}
/// Returns the next column if it exists. If it doesn't exist it attempts to return the
@ -277,6 +276,10 @@ impl<'a> CursorNavigation<'a> {
Self::next_valid_text_location(line, previous_line, previous_column, line_end)
}
fn get_lines_from_height(&self) -> usize {
(self.text_field_size.y / self.content.line_height) as usize
}
/// New position of cursor at `position` after applying `step`.
fn new_position(&mut self, position: TextLocation, step:Step) -> TextLocation {
match step {
@ -284,8 +287,10 @@ impl<'a> CursorNavigation<'a> {
Step::RightWord => self.next_word_position(&position).unwrap_or(position),
Step::Left => self.prev_char_position(&position).unwrap_or(position),
Step::Right => self.next_char_position(&position).unwrap_or(position),
Step::Up => self.line_up_position(&position).unwrap_or(position),
Step::Down => self.line_down_position(&position).unwrap_or(position),
Step::PageUp => self.line_up_position(&position,self.get_lines_from_height()),
Step::PageDown => self.line_down_position(&position,self.get_lines_from_height()),
Step::Up => self.line_up_position(&position,1),
Step::Down => self.line_down_position(&position,1),
Step::LineBegin => TextLocation::at_line_begin(position.line),
Step::LineEnd => self.line_end_position(position.line),
Step::DocBegin => TextLocation::at_document_begin(),
@ -587,13 +592,19 @@ mod test {
expected_positions.insert(LineEnd, vec![(0,10),(1,10),(2,9)]);
expected_positions.insert(DocBegin, vec![(0,0)]);
expected_positions.insert(DocEnd, vec![(2,9)]);
expected_positions.insert(PageUp, vec![(0,0),(0,9)]);
expected_positions.insert(PageDown, vec![(2,0),(2,9)]);
let mut fonts = FontRegistry::new();
let properties = TextFieldProperties::default(&mut fonts);
let mut content = TextFieldContent::new(text,&properties);
let mut navigation = CursorNavigation::default(&mut content);
let mut fonts = FontRegistry::new();
let mut properties = TextFieldProperties::default(&mut fonts);
let two_lines_high = properties.text_size * 2.0;
properties.size = Vector2::new(10.0, two_lines_high);
let content = &mut TextFieldContent::new(text,&properties);
let text_field_size = properties.size;
let selecting = false;
let mut navigation = CursorNavigation{selecting,content,text_field_size};
for step in &[/*Left,Right,Up,*/Down,/*LineBegin,LineEnd,DocBegin,DocEnd*/] {
for step in &[Left,Right,Up,Down,LineBegin,LineEnd,DocBegin,DocEnd,PageUp,PageDown] {
let mut cursors = Cursors::mock(initial_cursors.clone());
cursors.navigate_all_cursors(&mut navigation,*step);
let expected = expected_positions.get(step).unwrap();
@ -613,16 +624,39 @@ mod test {
let initial_cursors = vec![initial_cursor];
let new_position = TextLocation {line:1,column:10};
let mut fonts = FontRegistry::new();
let properties = TextFieldProperties::default(&mut fonts);
let mut content = TextFieldContent::new(text,&properties);
let mut navigation = CursorNavigation::default(&mut content);
let mut fonts = FontRegistry::new();
let properties = TextFieldProperties::default(&mut fonts);
let content = &mut TextFieldContent::new(text,&properties);
let selecting = false;
let text_field_size = properties.size;
let mut navigation = CursorNavigation{content,text_field_size,selecting};
let mut cursors = Cursors::mock(initial_cursors.clone());
cursors.navigate_all_cursors(&mut navigation,LineEnd);
assert_eq!(new_position, cursors.first_cursor().position);
assert_eq!(new_position, cursors.first_cursor().selected_to);
}
#[wasm_bindgen_test(async)]
async fn page_scrolling() {
ensogl_core_msdf_sys::initialized().await;
let text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19";
let initial_cursor = Cursor::new(TextLocation::at_document_begin());
let initial_cursors = vec![initial_cursor];
let expected_position = TextLocation {line:6,column:0};
let mut fonts = FontRegistry::new();
let mut properties = TextFieldProperties::default(&mut fonts);
properties.size = Vector2::new(100.0,100.0);
let content = &mut TextFieldContent::new(text,&properties);
let selecting = false;
let text_field_size = properties.size;
let mut navigation = CursorNavigation{selecting,content,text_field_size};
let mut cursors = Cursors::mock(initial_cursors.clone());
cursors.navigate_all_cursors(&mut navigation,PageDown);
assert_eq!(expected_position, cursors.first_cursor().position);
assert_eq!(expected_position, cursors.first_cursor().selected_to);
}
#[wasm_bindgen_test(async)]
async fn moving_with_select() {
ensogl_core_msdf_sys::initialized().await;
@ -631,11 +665,12 @@ mod test {
let initial_cursors = vec![Cursor::new(initial_loc)];
let new_loc = TextLocation {line:0,column:9};
let mut fonts = FontRegistry::new();
let properties = TextFieldProperties::default(&mut fonts);
let mut content = TextFieldContent::new(text,&properties);
let mut navigation = CursorNavigation
{selecting:true, ..CursorNavigation::default(&mut content)};
let mut fonts = FontRegistry::new();
let properties = TextFieldProperties::default(&mut fonts);
let content = &mut TextFieldContent::new(text,&properties);
let selecting = true;
let text_field_size = properties.size;
let mut navigation = CursorNavigation{selecting,content,text_field_size};
let mut cursors = Cursors::mock(initial_cursors.clone());
cursors.navigate_all_cursors(&mut navigation,LineEnd);
assert_eq!(new_loc , cursors.first_cursor().position);
@ -697,12 +732,14 @@ mod test {
#[wasm_bindgen_test(async)]
async fn step_into_word() {
msdf_sys::initialized().await;
let content = "first sentence\r\nthis is a second sentence\r\nlast sentence\n";
let content = &mut TextFieldContent::new(content,&mock_properties());
let selecting = false;
let mut navigation = CursorNavigation{content,selecting};
let mut location = TextLocation::at_document_begin();
location = navigation.next_word_position(&location).unwrap();
let content = "first sentence\r\nthis is a second sentence\r\nlast sentence\n";
let properties = mock_properties();
let content = &mut TextFieldContent::new(content,&properties);
let selecting = false;
let text_field_size = properties.size;
let mut navigation = CursorNavigation{content,selecting,text_field_size};
let mut location = TextLocation::at_document_begin();
location = navigation.next_word_position(&location).unwrap();
assert_eq!(location, TextLocation{line:0, column:5});
location = navigation.next_word_position(&location).unwrap();
assert_eq!(location, TextLocation{line:0, column:14});

View File

@ -150,6 +150,8 @@ impl TextFieldKeyboardFrp {
setter.set_navigation_action(&[ArrowRight], Step::Right);
setter.set_navigation_action(&[ArrowUp], Step::Up);
setter.set_navigation_action(&[ArrowDown], Step::Down);
setter.set_navigation_action(&[PageDown], Step::PageDown);
setter.set_navigation_action(&[PageUp], Step::PageUp);
setter.set_navigation_action(&[Home], Step::LineBegin);
setter.set_navigation_action(&[End], Step::LineEnd);
setter.set_navigation_action(&[Control,Home], Step::DocBegin);
@ -161,8 +163,6 @@ impl TextFieldKeyboardFrp {
setter.set_action(&[Delete], |t| t.do_delete_operation(Step::Right));
setter.set_action(&[Backspace], |t| t.do_delete_operation(Step::Left));
setter.set_action(&[Escape], |t| t.finish_multicursor_mode());
setter.set_action(&[PageDown], |t| t.page_down());
setter.set_action(&[PageUp], |t| t.page_up());
}
}

View File

@ -77,7 +77,7 @@ impl TextEditor {
let font = fonts.get_or_load_embedded_font("DejaVuSansMono").unwrap();
let padding = default();
let position = zero();
let size = Vector2::new(screen.width, screen.height);
let size = Vector2::new(screen.width, screen.height / 2.0);
let black = Vector4::new(0.0,0.0,0.0,1.0);
let base_color = black;
let text_size = 16.0;