mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 14:42:44 +00:00 
			
		
		
		
	GTextEditor: Add basic selection support.
This commit is contained in:
		
							parent
							
								
									c9c40e1da6
								
							
						
					
					
						commit
						b5521e1b0d
					
				
					 3 changed files with 65 additions and 12 deletions
				
			
		|  | @ -27,7 +27,12 @@ int main(int argc, char** argv) | ||||||
|     auto* statusbar = new GStatusBar(widget); |     auto* statusbar = new GStatusBar(widget); | ||||||
| 
 | 
 | ||||||
|     text_editor->on_cursor_change = [statusbar] (GTextEditor& editor) { |     text_editor->on_cursor_change = [statusbar] (GTextEditor& editor) { | ||||||
|         statusbar->set_text(String::format("Line: %d, Column: %d", editor.cursor().line(), editor.cursor().column())); |         StringBuilder builder; | ||||||
|  |         builder.appendf("Line: %d, Column: %d", editor.cursor().line(), editor.cursor().column()); | ||||||
|  |         if (editor.selection_start().is_valid()) { | ||||||
|  |             builder.appendf("      Selection: [%d,%d]-[%d,%d]", editor.selection_start().line(), editor.selection_start().column(), editor.cursor().line(), editor.cursor().column()); | ||||||
|  |         } | ||||||
|  |         statusbar->set_text(builder.to_string()); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     String path = "/tmp/TextEditor.save.txt"; |     String path = "/tmp/TextEditor.save.txt"; | ||||||
|  |  | ||||||
|  | @ -106,6 +106,11 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const | ||||||
| void GTextEditor::mousedown_event(GMouseEvent& event) | void GTextEditor::mousedown_event(GMouseEvent& event) | ||||||
| { | { | ||||||
|     set_cursor(text_position_at(event.position())); |     set_cursor(text_position_at(event.position())); | ||||||
|  |     // FIXME: Allow mouse selection!
 | ||||||
|  |     if (m_selection_start.is_valid()) { | ||||||
|  |         m_selection_start = { }; | ||||||
|  |         update(); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int GTextEditor::ruler_width() const | int GTextEditor::ruler_width() const | ||||||
|  | @ -141,11 +146,16 @@ void GTextEditor::paint_event(GPaintEvent& event) | ||||||
|     int first_visible_line = text_position_at(event.rect().top_left()).line(); |     int first_visible_line = text_position_at(event.rect().top_left()).line(); | ||||||
|     int last_visible_line = text_position_at(event.rect().bottom_right()).line(); |     int last_visible_line = text_position_at(event.rect().bottom_right()).line(); | ||||||
| 
 | 
 | ||||||
|  |     auto normalized_selection_start = m_selection_start; | ||||||
|  |     auto normalized_selection_end = m_cursor; | ||||||
|  |     if (m_cursor < m_selection_start) | ||||||
|  |         swap(normalized_selection_start, normalized_selection_end); | ||||||
|  |     bool has_selection = m_selection_start.is_valid(); | ||||||
|  | 
 | ||||||
|     painter.set_font(Font::default_font()); |     painter.set_font(Font::default_font()); | ||||||
|     for (int i = first_visible_line; i <= last_visible_line; ++i) { |     for (int i = first_visible_line; i <= last_visible_line; ++i) { | ||||||
|         bool is_current_line = i == m_cursor.line(); |         bool is_current_line = i == m_cursor.line(); | ||||||
|         auto ruler_line_rect = ruler_content_rect(i); |         auto ruler_line_rect = ruler_content_rect(i); | ||||||
|         //painter.fill_rect(ruler_line_rect, Color::LightGray);
 |  | ||||||
|         Color text_color = Color::MidGray; |         Color text_color = Color::MidGray; | ||||||
|         if (is_current_line) { |         if (is_current_line) { | ||||||
|             painter.set_font(Font::default_bold_font()); |             painter.set_font(Font::default_bold_font()); | ||||||
|  | @ -166,6 +176,16 @@ void GTextEditor::paint_event(GPaintEvent& event) | ||||||
|         if (i == m_cursor.line()) |         if (i == m_cursor.line()) | ||||||
|             painter.fill_rect(line_rect, Color(230, 230, 230)); |             painter.fill_rect(line_rect, Color(230, 230, 230)); | ||||||
|         painter.draw_text(line_rect, line.characters(), line.length(), TextAlignment::CenterLeft, Color::Black); |         painter.draw_text(line_rect, line.characters(), line.length(), TextAlignment::CenterLeft, Color::Black); | ||||||
|  |         bool line_has_selection = has_selection && i >= normalized_selection_start.line() && i <= normalized_selection_end.line(); | ||||||
|  |         if (line_has_selection) { | ||||||
|  |             int selection_start_column_on_line = normalized_selection_start.line() == i ? normalized_selection_start.column() : 0; | ||||||
|  |             int selection_end_column_on_line = normalized_selection_end.line() == i ? normalized_selection_end.column() : line.length(); | ||||||
|  |             int selection_left = selection_start_column_on_line * font().glyph_width('x'); | ||||||
|  |             int selection_right = line_rect.left() + selection_end_column_on_line * font().glyph_width('x'); | ||||||
|  |             Rect selection_rect { selection_left, line_rect.y(), selection_right - selection_left, line_rect.height() }; | ||||||
|  |             painter.fill_rect(selection_rect, Color::from_rgb(0x955233)); | ||||||
|  |             painter.draw_text(selection_rect, line.characters() + selection_start_column_on_line, line.length() - selection_start_column_on_line - (line.length() - selection_end_column_on_line), TextAlignment::CenterLeft, Color::White); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (is_focused() && m_cursor_state) |     if (is_focused() && m_cursor_state) | ||||||
|  | @ -184,67 +204,91 @@ void GTextEditor::paint_event(GPaintEvent& event) | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GTextEditor::toggle_selection_if_needed_for_event(const GKeyEvent& event) | ||||||
|  | { | ||||||
|  |     if (event.shift() && !m_selection_start.is_valid()) { | ||||||
|  |         m_selection_start = m_cursor; | ||||||
|  |         update(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     if (!event.shift() && m_selection_start.is_valid()) { | ||||||
|  |         m_selection_start = { }; | ||||||
|  |         update(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void GTextEditor::keydown_event(GKeyEvent& event) | void GTextEditor::keydown_event(GKeyEvent& event) | ||||||
| { | { | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_Up) { |     if (event.key() == KeyCode::Key_Up) { | ||||||
|         if (m_cursor.line() > 0) { |         if (m_cursor.line() > 0) { | ||||||
|             int new_line = m_cursor.line() - 1; |             int new_line = m_cursor.line() - 1; | ||||||
|             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); |             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); | ||||||
|  |             toggle_selection_if_needed_for_event(event); | ||||||
|             set_cursor(new_line, new_column); |             set_cursor(new_line, new_column); | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_Down) { |     if (event.key() == KeyCode::Key_Down) { | ||||||
|         if (m_cursor.line() < (m_lines.size() - 1)) { |         if (m_cursor.line() < (m_lines.size() - 1)) { | ||||||
|             int new_line = m_cursor.line() + 1; |             int new_line = m_cursor.line() + 1; | ||||||
|             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); |             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); | ||||||
|  |             toggle_selection_if_needed_for_event(event); | ||||||
|             set_cursor(new_line, new_column); |             set_cursor(new_line, new_column); | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_PageUp) { |     if (event.key() == KeyCode::Key_PageUp) { | ||||||
|         if (m_cursor.line() > 0) { |         if (m_cursor.line() > 0) { | ||||||
|             int new_line = max(0, m_cursor.line() - visible_content_rect().height() / line_height()); |             int new_line = max(0, m_cursor.line() - visible_content_rect().height() / line_height()); | ||||||
|             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); |             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); | ||||||
|  |             toggle_selection_if_needed_for_event(event); | ||||||
|             set_cursor(new_line, new_column); |             set_cursor(new_line, new_column); | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_PageDown) { |     if (event.key() == KeyCode::Key_PageDown) { | ||||||
|         if (m_cursor.line() < (m_lines.size() - 1)) { |         if (m_cursor.line() < (m_lines.size() - 1)) { | ||||||
|             int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height()); |             int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height()); | ||||||
|             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); |             int new_column = min(m_cursor.column(), m_lines[new_line]->length()); | ||||||
|  |             toggle_selection_if_needed_for_event(event); | ||||||
|             set_cursor(new_line, new_column); |             set_cursor(new_line, new_column); | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_Left) { |     if (event.key() == KeyCode::Key_Left) { | ||||||
|         if (m_cursor.column() > 0) { |         if (m_cursor.column() > 0) { | ||||||
|             int new_column = m_cursor.column() - 1; |             int new_column = m_cursor.column() - 1; | ||||||
|  |             toggle_selection_if_needed_for_event(event); | ||||||
|             set_cursor(m_cursor.line(), new_column); |             set_cursor(m_cursor.line(), new_column); | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_Right) { |     if (event.key() == KeyCode::Key_Right) { | ||||||
|         if (m_cursor.column() < current_line().length()) { |         if (m_cursor.column() < current_line().length()) { | ||||||
|             int new_column = m_cursor.column() + 1; |             int new_column = m_cursor.column() + 1; | ||||||
|  |             toggle_selection_if_needed_for_event(event); | ||||||
|             set_cursor(m_cursor.line(), new_column); |             set_cursor(m_cursor.line(), new_column); | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_Home) { |     if (event.key() == KeyCode::Key_Home) { | ||||||
|  |         toggle_selection_if_needed_for_event(event); | ||||||
|         set_cursor(m_cursor.line(), 0); |         set_cursor(m_cursor.line(), 0); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (!event.modifiers() && event.key() == KeyCode::Key_End) { |     if (event.key() == KeyCode::Key_End) { | ||||||
|  |         toggle_selection_if_needed_for_event(event); | ||||||
|         set_cursor(m_cursor.line(), current_line().length()); |         set_cursor(m_cursor.line(), current_line().length()); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (event.ctrl() && event.key() == KeyCode::Key_Home) { |     if (event.key() == KeyCode::Key_Home) { | ||||||
|  |         toggle_selection_if_needed_for_event(event); | ||||||
|         set_cursor(0, 0); |         set_cursor(0, 0); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     if (event.ctrl() && event.key() == KeyCode::Key_End) { |     if (event.key() == KeyCode::Key_End) { | ||||||
|  |         toggle_selection_if_needed_for_event(event); | ||||||
|         set_cursor(line_count() - 1, m_lines[line_count() - 1]->length()); |         set_cursor(line_count() - 1, m_lines[line_count() - 1]->length()); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ public: | ||||||
|     void set_column(int column) { m_column = column; } |     void set_column(int column) { m_column = column; } | ||||||
| 
 | 
 | ||||||
|     bool operator==(const GTextPosition& other) const { return m_line == other.m_line && m_column == other.m_column; } |     bool operator==(const GTextPosition& other) const { return m_line == other.m_line && m_column == other.m_column; } | ||||||
|  |     bool operator<(const GTextPosition& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     int m_line { -1 }; |     int m_line { -1 }; | ||||||
|  | @ -47,6 +48,7 @@ public: | ||||||
|     int line_height() const { return font().glyph_height() + m_line_spacing; } |     int line_height() const { return font().glyph_height() + m_line_spacing; } | ||||||
|     int padding() const { return 3; } |     int padding() const { return 3; } | ||||||
|     GTextPosition cursor() const { return m_cursor; } |     GTextPosition cursor() const { return m_cursor; } | ||||||
|  |     GTextPosition selection_start() const { return m_selection_start; } | ||||||
|     int glyph_width() const { return font().glyph_width('x'); } |     int glyph_width() const { return font().glyph_width('x'); } | ||||||
| 
 | 
 | ||||||
|     bool write_to_file(const String& path); |     bool write_to_file(const String& path); | ||||||
|  | @ -95,6 +97,7 @@ private: | ||||||
|     void insert_at_cursor(char); |     void insert_at_cursor(char); | ||||||
|     int ruler_width() const; |     int ruler_width() const; | ||||||
|     Rect ruler_content_rect(int line) const; |     Rect ruler_content_rect(int line) const; | ||||||
|  |     void toggle_selection_if_needed_for_event(const GKeyEvent&); | ||||||
| 
 | 
 | ||||||
|     GScrollBar* m_vertical_scrollbar { nullptr }; |     GScrollBar* m_vertical_scrollbar { nullptr }; | ||||||
|     GScrollBar* m_horizontal_scrollbar { nullptr }; |     GScrollBar* m_horizontal_scrollbar { nullptr }; | ||||||
|  | @ -103,4 +106,5 @@ private: | ||||||
|     GTextPosition m_cursor; |     GTextPosition m_cursor; | ||||||
|     bool m_cursor_state { true }; |     bool m_cursor_state { true }; | ||||||
|     int m_line_spacing { 2 }; |     int m_line_spacing { 2 }; | ||||||
|  |     GTextPosition m_selection_start; | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling