From 190b41f38709299ba5901e11604de1590de12d3f Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 19 Oct 2022 10:12:52 -0600 Subject: [PATCH] Add selection --- examples/editor-libcosmic/src/text_box.rs | 158 +++++++++++++--------- examples/editor-orbclient/src/main.rs | 2 +- src/buffer.rs | 62 ++++++++- 3 files changed, 154 insertions(+), 68 deletions(-) diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index 913fdef..49fc090 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -9,7 +9,7 @@ use cosmic::iced_native::{ layout::{self, Layout}, mouse::{self, Button, Event as MouseEvent, ScrollDelta}, renderer, - widget::{self, Widget}, + widget::{self, tree, Widget}, }; use cosmic_text::{ TextAction, @@ -77,6 +77,14 @@ where Renderer: renderer::Renderer, Renderer::Theme: StyleSheet, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::new()) + } + fn width(&self) -> Length { Length::Fill } @@ -194,7 +202,7 @@ where fn on_event( &mut self, - _state: &mut widget::Tree, + tree: &mut widget::Tree, event: Event, layout: Layout<'_>, cursor_position: Point, @@ -202,79 +210,85 @@ where _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, ) -> Status { + let state = tree.state.downcast_mut::(); let mut buffer = self.buffer.lock().unwrap(); match event { - Event::Keyboard(key_event) => match key_event { - KeyEvent::KeyPressed { key_code, modifiers } => { - match key_code { - KeyCode::Left => { - buffer.action(TextAction::Left); - Status::Captured - }, - KeyCode::Right => { - buffer.action(TextAction::Right); - Status::Captured - }, - KeyCode::Up => { - buffer.action(TextAction::Up); - Status::Captured - }, - KeyCode::Down => { - buffer.action(TextAction::Down); - Status::Captured - }, - KeyCode::Backspace => { - buffer.action(TextAction::Backspace); - Status::Captured - }, - KeyCode::Delete => { - buffer.action(TextAction::Delete); - Status::Captured - }, - KeyCode::PageUp => { - buffer.action(TextAction::PageUp); - Status::Captured - }, - KeyCode::PageDown => { - buffer.action(TextAction::PageDown); - Status::Captured - }, - _ => Status::Ignored, - } + Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code { + KeyCode::Left => { + buffer.action(TextAction::Left); + return Status::Captured; }, - KeyEvent::CharacterReceived(character) => { - buffer.action(TextAction::Insert(character)); - Status::Captured + KeyCode::Right => { + buffer.action(TextAction::Right); + return Status::Captured; }, - _ => Status::Ignored, + KeyCode::Up => { + buffer.action(TextAction::Up); + return Status::Captured; + }, + KeyCode::Down => { + buffer.action(TextAction::Down); + return Status::Captured; + }, + KeyCode::Backspace => { + buffer.action(TextAction::Backspace); + return Status::Captured; + }, + KeyCode::Delete => { + buffer.action(TextAction::Delete); + return Status::Captured; + }, + KeyCode::PageUp => { + buffer.action(TextAction::PageUp); + return Status::Captured; + }, + KeyCode::PageDown => { + buffer.action(TextAction::PageDown); + return Status::Captured; + }, + _ => () }, - Event::Mouse(mouse_event) => match mouse_event { - MouseEvent::ButtonPressed(button) => match button { - Button::Left => if layout.bounds().contains(cursor_position) { - buffer.action(TextAction::Click { - x: (cursor_position.x - layout.bounds().x) as i32, - y: (cursor_position.y - layout.bounds().y) as i32, - }); - Status::Captured - } else { - Status::Ignored - }, - _ => Status::Ignored, - }, - MouseEvent::WheelScrolled { delta } => match delta { - ScrollDelta::Lines { x, y } => { - buffer.action(TextAction::Scroll { - lines: (-y * 6.0) as i32, - }); - Status::Captured - }, - _ => Status::Ignored, + Event::Keyboard(KeyEvent::CharacterReceived(character)) => { + buffer.action(TextAction::Insert(character)); + return Status::Captured; + }, + Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => { + if layout.bounds().contains(cursor_position) { + buffer.action(TextAction::Click { + x: (cursor_position.x - layout.bounds().x) as i32, + y: (cursor_position.y - layout.bounds().y) as i32, + }); + state.is_dragging = true; + return Status::Captured; } - _ => Status::Ignored, }, - _ => Status::Ignored, + Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => { + state.is_dragging = false; + return Status::Captured; + }, + Event::Mouse(MouseEvent::CursorMoved { .. }) => { + if state.is_dragging { + buffer.action(TextAction::Drag { + x: (cursor_position.x - layout.bounds().x) as i32, + y: (cursor_position.y - layout.bounds().y) as i32, + }); + return Status::Captured; + } + }, + Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta { + ScrollDelta::Lines { x, y } => { + buffer.action(TextAction::Scroll { + lines: (-y * 6.0) as i32, + }); + return Status::Captured; + }, + _ => (), + }, + _ => () } + + Status::Ignored } } @@ -287,3 +301,15 @@ where Self::new(text_box) } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State { + is_dragging: bool, +} + +impl State { + /// Creates a new [`State`]. + pub fn new() -> State { + State::default() + } +} diff --git a/examples/editor-orbclient/src/main.rs b/examples/editor-orbclient/src/main.rs index 6b3bf96..bdf58a5 100644 --- a/examples/editor-orbclient/src/main.rs +++ b/examples/editor-orbclient/src/main.rs @@ -203,7 +203,7 @@ fn main() { mouse_x = event.x; mouse_y = event.y; if mouse_left { - buffer.action(TextAction::Click { + buffer.action(TextAction::Drag { x: mouse_x - line_x, y: mouse_y }); diff --git a/src/buffer.rs b/src/buffer.rs index c562dab..9f8edb5 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -29,6 +29,8 @@ pub enum TextAction { Insert(char), /// Mouse click at specified position Click { x: i32, y: i32 }, + /// Mouse drag to specified position + Drag { x: i32, y: i32 }, /// Scroll specified number of lines Scroll { lines: i32 }, } @@ -99,6 +101,7 @@ pub struct TextBuffer<'a> { height: i32, scroll: i32, cursor: TextCursor, + select_opt: Option, pub redraw: bool, } @@ -122,6 +125,7 @@ impl<'a> TextBuffer<'a> { height: 0, scroll: 0, cursor: TextCursor::default(), + select_opt: None, redraw: false, } } @@ -401,6 +405,13 @@ impl<'a> TextBuffer<'a> { } }, TextAction::Click { x, y } => { + self.select_opt = None; + self.click(x, y); + }, + TextAction::Drag { x, y } => { + if self.select_opt.is_none() { + self.select_opt = Some(self.cursor); + } self.click(x, y); }, TextAction::Scroll { lines } => { @@ -472,7 +483,56 @@ impl<'a> TextBuffer<'a> { .take(cmp::max(0, self.lines()) as usize) .enumerate() { - if self.cursor.line == line_i + self.scroll() as usize { + let line_i_scrolled = line_i + cmp::max(0, self.scroll()) as usize; + + // Highlight selection (TODO: HIGHLIGHT COLOR!) + if let Some(select) = self.select_opt { + let (start, end) = if select.line < self.cursor.line { + (select, self.cursor) + } else if select.line > self.cursor.line { + (self.cursor, select) + } else { + /* select.line == self.cursor.line */ + if select.glyph < self.cursor.glyph { + (select, self.cursor) + } else { + /* select.glyph >= self.cursor.glyph */ + (self.cursor, select) + } + }; + + if line_i_scrolled >= start.line && line_i_scrolled <= end.line { + let start_glyph = if start.line == line_i_scrolled { + start.glyph + } else { + 0 + }; + + let end_glyph = if end.line == line_i_scrolled { + end.glyph + } else { + line.glyphs.len() + }; + + let start_x = line.glyphs.get(start_glyph).map_or(0, |glyph| { + glyph.x as i32 + }); + let end_x = line.glyphs.get(end_glyph).map_or(self.width, |glyph| { + (glyph.x + glyph.w) as i32 + }); + + f( + start_x, + line_y - font_size, + cmp::max(0, end_x - start_x) as u32, + line_height as u32, + 0x33_00_00_00 | (color & 0xFF_FF_FF) + ); + } + } + + // Draw cursor + if self.cursor.line == line_i_scrolled { if self.cursor.glyph >= line.glyphs.len() { let x = match line.glyphs.last() { Some(glyph) => glyph.x + glyph.w,