mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 04:38:11 +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