From 9fa0b4b097e83f9a0c0733e9c980373edd41f2f6 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Tue, 14 Oct 2025 19:39:15 +0100 Subject: [PATCH] [Add] Line numbers to the editor. --- client/src/terminal.cpp | 11 +++++-- client/src/ui/editor.cpp | 19 +++++++---- client/src/ui/text_view.cpp | 66 +++++++++++++++++++++++-------------- client/src/ui/text_view.h | 7 ++-- 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/client/src/terminal.cpp b/client/src/terminal.cpp index 20163d0..b9378ad 100644 --- a/client/src/terminal.cpp +++ b/client/src/terminal.cpp @@ -13,7 +13,7 @@ Terminal::Terminal(GameState* game_state) : _game_state(game_state), _should_close(false), _scroll_offset(0), _prompt(""), _pending_action({ActionType::NONE}) { - _input_view = std::make_unique(&_input_buffer, false); + _input_view = std::make_unique(&_input_buffer, false, false); } Terminal::~Terminal(void) {} @@ -141,11 +141,16 @@ void Terminal::render(const RenderContext& context, int x, int y_screen, int y_g /* Render text view for the input right after prompt. */ float input_x_pos = x + padding + (prompt_str.length() * 8.5f); /* Estimate width */ float input_width = width - (input_x_pos-x); - _input_view->render(context.ui_renderer, input_x_pos, prompt_line_y, input_width, - line_height, context.show_cursor); + _input_view->render_text_content(context.ui_renderer, input_x_pos, prompt_line_y, input_width, line_height); context.ui_renderer->flush_text(); /* Disable scissor test. */ glDisable(GL_SCISSOR_TEST); + + if(context.show_cursor) { + context.ui_renderer->begin_shapes(); + _input_view->render_cursor(context.ui_renderer, input_x_pos, prompt_line_y, input_width, line_height); + context.ui_renderer->flush_shapes(); + } } diff --git a/client/src/ui/editor.cpp b/client/src/ui/editor.cpp index b1bf9b7..702ab8e 100644 --- a/client/src/ui/editor.cpp +++ b/client/src/ui/editor.cpp @@ -6,7 +6,7 @@ Editor::Editor(void) : _should_close(false), _pending_action({ActionType::NONE}), _filename("untitled.txt") { - _view = std::make_unique(&_buffer, true); + _view = std::make_unique(&_buffer, true, true); _menu_bar = std::make_unique(25); _menu_bar->add_menu("File"); _menu_bar->add_menu_item("File", "Save", [this]() { @@ -17,7 +17,7 @@ Editor::Editor(void) Editor::Editor(const std::string& filename) : _should_close(false), _pending_action({ActionType::NONE}), _filename(filename) { - _view = std::make_unique(&_buffer, true); + _view = std::make_unique(&_buffer, true, true); _menu_bar = std::make_unique(25); _menu_bar->add_menu("File"); _menu_bar->add_menu_item("File", "Save", [this]() { @@ -58,15 +58,20 @@ void Editor::render(const RenderContext& context, int x, int y_screen, int y_gl, context.ui_renderer->flush_shapes(); /* Pass 2: Main text view. */ - _view->render(context.ui_renderer, x, content_y, width, content_height, context.show_cursor); - - /* Pass 3: Menu bar text and dropdown. */ context.ui_renderer->begin_text(); - _menu_bar->render_bar_text(context.ui_renderer, x, y_screen, width); + _view->render_text_content(context.ui_renderer, x, content_y, width, content_height); context.ui_renderer->flush_text(); - /* Pass 4: Dropdown menu. */ + /* Pass 3: Editor cursor. */ + if(context.show_cursor) { + context.ui_renderer->begin_shapes(); + _view->render_cursor(context.ui_renderer, x, content_y, width, content_height); + context.ui_renderer->flush_shapes(); + } + + /* Pass 4: Menu bar text and dropdown. */ context.ui_renderer->begin_text(); + _menu_bar->render_bar_text(context.ui_renderer, x, y_screen, width); _menu_bar->render_dropdown(context.ui_renderer, x, y_screen, width); context.ui_renderer->flush_text(); } diff --git a/client/src/ui/text_view.cpp b/client/src/ui/text_view.cpp index e302a75..0921ea4 100644 --- a/client/src/ui/text_view.cpp +++ b/client/src/ui/text_view.cpp @@ -1,11 +1,16 @@ #include +#include + #include "text_view.h" -#include "gfx/txt_renderer.h" #include "gfx/types.h" #include "ui/text_buffer.h" +#include "ui/ui_renderer.h" -TextView::TextView(TextBuffer* buffer, bool handle_ret) : _buffer(buffer), - _scroll_offset(0), _handle_ret(handle_ret) {} +TextView::TextView(TextBuffer* buffer, bool handle_ret, bool show_line_numbers) : + _buffer(buffer), + _scroll_offset(0), + _handle_ret(handle_ret), + _show_line_numbers(show_line_numbers) {} TextView::~TextView(void) {} @@ -70,15 +75,16 @@ void TextView::scroll(int amount, int content_height) { } } -void TextView::render(UIRenderer* ui_renderer, int x, int y, int width, int height, - bool show_cursor) { +void TextView::render_text_content(UIRenderer* ui_renderer, int x, int y, int width, int height) { if(!_buffer) return; - const Color text_color = { 1.0f, 1.0f, 1.0f }; - float line_height = 20.0f; /* TODO: Get font metrics? */ - float padding = 5.0f; + const Color text_color = { 1.0f, 1.0f, 1.0f }; + const Color line_num_color = { 0.5f, 0.6f, 0.7f }; + + const float line_height = 20.0f; /* TODO: Get font metrics? */ + const float padding = 5.0f; + const float gutter_width = _show_line_numbers ? 40.0f : 0.0f; - Point cursor_pos = _buffer->get_cursor_pos(); float current_y = y; for(size_t i = _scroll_offset; i < _buffer->get_line_count(); ++i) { @@ -90,22 +96,34 @@ void TextView::render(UIRenderer* ui_renderer, int x, int y, int width, int heig break; } - std::string line = _buffer->get_line(i); - - if(show_cursor && (int)i == cursor_pos.row) { - /* - * This hacky. we should calculate the text width - * up to the cursor pos to draw correctly. - * For now, just append an underscore. - */ - if(cursor_pos.col == line.length()) { - line += "_"; - } else { - line.insert(cursor_pos.col, 1, '_'); - } + if(_show_line_numbers) { + /* Render line number. */ + std::string line_num_str = std::to_string(i+1); + float line_num_text_width = + ui_renderer->get_text_renderer()->get_text_width(line_num_str.c_str(), 1.0f); + ui_renderer->render_text(line_num_str.c_str(), x+padding+(gutter_width-line_num_text_width-10), + current_y+18, line_num_color); } - /* Add 18 to get baseline from top of the line. */ - ui_renderer->render_text(line.c_str(), x+padding, current_y + 18, text_color); + + /* Render line content. */ + std::string line = _buffer->get_line(i); + ui_renderer->render_text(line.c_str(), x+padding+gutter_width, current_y+18, text_color); current_y += line_height; } } + +void TextView::render_cursor(UIRenderer* ui_renderer, int x, int y, int width, int height) { + const Color text_color = { 1.0f, 1.0f, 1.0f }; + const float line_height = 20.0f; /* TODO: Get font metrics? */ + const float padding = 5.0f; + const float gutter_width = _show_line_numbers ? 40.0f : 0.0f; + Point cursor_pos = _buffer->get_cursor_pos(); + std::string line_before_cursor = _buffer->get_line(cursor_pos.row).substr(0, cursor_pos.col); + float text_width = + ui_renderer->get_text_renderer()->get_text_width(line_before_cursor.c_str(), 1.0f); + float cursor_x = x + padding + gutter_width + text_width; + float cursor_y = y + (cursor_pos.row - _scroll_offset) * line_height; + if(cursor_y >= y && cursor_y < y + height) { + ui_renderer->draw_rect(cursor_x, cursor_y, 2, line_height, text_color); + } +} diff --git a/client/src/ui/text_view.h b/client/src/ui/text_view.h index a19d498..419b62a 100644 --- a/client/src/ui/text_view.h +++ b/client/src/ui/text_view.h @@ -9,16 +9,17 @@ class TextRenderer; class TextView { public: - TextView(TextBuffer* buffer, bool handle_ret); + TextView(TextBuffer* buffer, bool handle_ret, bool show_line_numbers); ~TextView(void); bool handle_event(SDL_Event* event); - void render(UIRenderer* ui_renderer, int x, int y, int width, int height, - bool show_cursor); void scroll(int amount, int content_height); + void render_text_content(UIRenderer* ui_renderer, int x, int y, int width, int height); + void render_cursor(UIRenderer* ui_renderer, int x, int y, int width, int height); private: TextBuffer* _buffer; int _scroll_offset; bool _handle_ret; + bool _show_line_numbers; };