From bb8591a80331887705b7146c93783e1c5d91a8f7 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sat, 25 Oct 2025 18:48:58 +0100 Subject: [PATCH] [Fix] Regression bugs with scrolling and auto-scrolling --- client/src/terminal.cpp | 9 ++++---- client/src/terminal.h | 5 +++-- client/src/ui/editor.cpp | 11 ++++++--- client/src/ui/editor.h | 5 +++-- client/src/ui/i_window_content.h | 5 +++-- client/src/ui/text_view.cpp | 38 ++++++++++++++++++++++++++++---- client/src/ui/text_view.h | 3 ++- client/src/ui/ui_renderer.cpp | 11 +++++++++ client/src/ui/ui_renderer.h | 3 +++ client/src/ui/ui_window.cpp | 7 ++++-- 10 files changed, 77 insertions(+), 20 deletions(-) diff --git a/client/src/terminal.cpp b/client/src/terminal.cpp index b2a8a4c..a02f12f 100644 --- a/client/src/terminal.cpp +++ b/client/src/terminal.cpp @@ -20,7 +20,7 @@ Terminal::Terminal(GameState* game_state) Terminal::~Terminal(void) {} -void Terminal::update(void) { +void Terminal::update(int content_width, int content_height) { } @@ -92,7 +92,8 @@ void Terminal::_on_ret_press(void) { _game_state->send_network_command(_session_id, command); } -void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) { +void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y, + int content_width, int content_height) { /* Pass input to TextView; if true, RET was pressed. */ if(event->type == SDL_EVENT_KEY_DOWN) { switch(event->key.key) { @@ -114,10 +115,10 @@ void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int wi } break; default: - if(_input_view->handle_event(event)) { _on_ret_press(); } + if(_input_view->handle_event(event, content_height)) { _on_ret_press(); } } } else { - if(_input_view->handle_event(event)) { _on_ret_press(); } + if(_input_view->handle_event(event, content_height)) { _on_ret_press(); } } } diff --git a/client/src/terminal.h b/client/src/terminal.h index b113008..f841b8a 100644 --- a/client/src/terminal.h +++ b/client/src/terminal.h @@ -17,8 +17,9 @@ public: Terminal(GameState* game_state); ~Terminal(void); - void update(void) override; - void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) override; + void update(int content_width, int content_height) override; + void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y, + int content_width, int content_height) override; void render(const RenderContext& context, int x, int y_screen, int y_gl, int width, int height) override; void scroll(int amount, int content_height) override; diff --git a/client/src/ui/editor.cpp b/client/src/ui/editor.cpp index b05d614..45e5b8a 100644 --- a/client/src/ui/editor.cpp +++ b/client/src/ui/editor.cpp @@ -29,11 +29,12 @@ Editor::Editor(const std::string& filename) Editor::~Editor(void) {} -void Editor::update(void) { +void Editor::update(int content_width, int content_height) { /* Nothing to do yet. */ } -void Editor::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) { +void Editor::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y, + int content_width, int content_height) { if(!event) return; _menu_bar->handle_event(event, window_x, window_y); @@ -44,7 +45,7 @@ void Editor::handle_input(SDL_Event* event, int window_x, int window_y, int wind /* C-S pressed, create a save action. */ _pending_action = { ActionType::WRITE_FILE, _filename, _buffer.get_text() }; } else { - _view->handle_event(event); + _view->handle_event(event, content_height); } } @@ -59,6 +60,8 @@ void Editor::render(const RenderContext& context, int x, int y_screen, int y_gl, _menu_bar->render_bar_bg(context.ui_renderer, x, y_screen, width); context.ui_renderer->flush_shapes(); + context.ui_renderer->enable_scissor(x, y_gl, width, content_height); + /* Pass 2: Main text view. */ context.ui_renderer->begin_text(); _view->render_text_content(context.ui_renderer, _theme, x, content_y, width, content_height); @@ -71,6 +74,8 @@ void Editor::render(const RenderContext& context, int x, int y_screen, int y_gl, context.ui_renderer->flush_shapes(); } + context.ui_renderer->disable_scissor(); + /* Pass 4: Menu bar text and dropdown. */ context.ui_renderer->begin_text(); _menu_bar->render_bar_text(context.ui_renderer, x, y_screen, width); diff --git a/client/src/ui/editor.h b/client/src/ui/editor.h index a7efd15..c1128a2 100644 --- a/client/src/ui/editor.h +++ b/client/src/ui/editor.h @@ -25,8 +25,9 @@ public: Editor(const std::string& filename); ~Editor(void) override; - void update(void) override; - void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) override; + void update(int content_width, int content_height) override; + void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y, + int content_widht, int content_heigth) override; void render(const RenderContext& context, int x, int y_screen, int y_gl, int width, int height) override; void scroll(int amount, int content_height) override; diff --git a/client/src/ui/i_window_content.h b/client/src/ui/i_window_content.h index 1cb78d1..de4ca9b 100644 --- a/client/src/ui/i_window_content.h +++ b/client/src/ui/i_window_content.h @@ -8,8 +8,9 @@ class IWindowContent { public: virtual ~IWindowContent(void) = default; - virtual void update(void) = 0; - virtual void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) = 0; + virtual void update(int content_width, int content_height) = 0; + virtual void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y, + int content_width, int content_height) = 0; virtual void render(const RenderContext& context, int x, int y_screen, int y_gl, int width, int height) = 0; virtual void scroll(int amount, int content_height) = 0; diff --git a/client/src/ui/text_view.cpp b/client/src/ui/text_view.cpp index 1d150a4..5958de1 100644 --- a/client/src/ui/text_view.cpp +++ b/client/src/ui/text_view.cpp @@ -22,17 +22,19 @@ TextView::TextView(TextBuffer* buffer, bool handle_ret, bool show_line_numbers, }; } -TextView::~TextView(void) {} +TextView::~TextView(void) = default; -bool TextView::handle_event(SDL_Event* event) { +bool TextView::handle_event(SDL_Event* event, int content_height) { if(!_buffer) return false; if(event->type == SDL_EVENT_TEXT_INPUT) { _buffer->insert_char(event->text.text[0]); + _ensure_cursor_visible(content_height); } else if(event->type == SDL_EVENT_KEY_DOWN) { switch(event->key.key) { case SDLK_BACKSPACE: _buffer->backspace(); + _ensure_cursor_visible(content_height); break; case SDLK_RETURN: /* @@ -43,22 +45,28 @@ bool TextView::handle_event(SDL_Event* event) { if(_handle_ret) { _buffer->newline(); } + _ensure_cursor_visible(content_height); return true; break; case SDLK_LEFT: _buffer->move_cursor(-1,0); + _ensure_cursor_visible(content_height); break; case SDLK_RIGHT: _buffer->move_cursor(1,0); + _ensure_cursor_visible(content_height); break; case SDLK_UP: _buffer->move_cursor(0,-1); + _ensure_cursor_visible(content_height); break; case SDLK_DOWN: _buffer->move_cursor(0,1); + _ensure_cursor_visible(content_height); break; case SDLK_HOME: _buffer->move_cursor_home(); + _ensure_cursor_visible(content_height); break; case SDLK_END: _buffer->move_cursor_end(); @@ -77,8 +85,13 @@ void TextView::scroll(int amount, int content_height) { _scroll_offset = 0; } float line_height = 20.0f; - int visible_lines = content_height / line_height; + int visible_lines = static_cast(content_height / line_height)-1; int max_scroll = _buffer->get_line_count() - visible_lines; + /* Allow one extra scroll step if content is larger than visible area. */ + if(_buffer->get_line_count() > visible_lines) max_scroll += 1; + if(max_scroll < 0) { + max_scroll = 0; + } if(max_scroll < 0) max_scroll = 0; if(_scroll_offset > max_scroll) { _scroll_offset = max_scroll; @@ -168,7 +181,7 @@ void TextView::render_text_content(UIRenderer* ui_renderer, const SyntaxTheme& t * Culling: If the top of the current line is already below the bottom of the * view, we can stop rendering completely. */ - if(current_y > y + height) { + if(current_y >= y + height) { break; } @@ -218,3 +231,20 @@ void TextView::render_cursor(UIRenderer* ui_renderer, const SyntaxTheme& theme, ui_renderer->draw_rect(cursor_x, cursor_y, 2, line_height, theme.normal); } } + +void TextView::_ensure_cursor_visible(int content_height) { + Point cursor_pos = _buffer->get_cursor_pos(); + float line_height = 20.0f; + int visible_lines = static_cast(content_height / line_height)-1; + + /* If cursor above current scroll view, scroll up. */ + if(cursor_pos.row < _scroll_offset) { + _scroll_offset = cursor_pos.row; + } else if(cursor_pos.row >= _scroll_offset + visible_lines) { + /* Below, scroll down. */ + _scroll_offset = cursor_pos.row - visible_lines+1; + } + + /* Re-clamp scroll offset to ensure it's within valid bounds. */ + scroll(0, content_height); +} diff --git a/client/src/ui/text_view.h b/client/src/ui/text_view.h index 1c3f7b2..c50679c 100644 --- a/client/src/ui/text_view.h +++ b/client/src/ui/text_view.h @@ -29,7 +29,7 @@ public: TextView(TextBuffer* buffer, bool handle_ret, bool show_line_numbers, bool syntax_highlighting); ~TextView(void); - bool handle_event(SDL_Event* event); + bool handle_event(SDL_Event* event, int content_height); void scroll(int amount, int content_height); void render_text_content(UIRenderer* ui_renderer, const SyntaxTheme& theme, int x, int y, int width, int height); @@ -37,6 +37,7 @@ public: int x, int y, int width, int height); private: + void _ensure_cursor_visible(int content_height); std::vector _tokenize_line(const std::string& line); TextBuffer* _buffer; diff --git a/client/src/ui/ui_renderer.cpp b/client/src/ui/ui_renderer.cpp index a605f49..bfd3b67 100644 --- a/client/src/ui/ui_renderer.cpp +++ b/client/src/ui/ui_renderer.cpp @@ -1,3 +1,5 @@ +#include + #include "ui_renderer.h" #include "gfx/shape_renderer.h" #include "gfx/txt_renderer.h" @@ -51,3 +53,12 @@ void UIRenderer::flush_shapes(void) { TextRenderer* UIRenderer::get_text_renderer(void) { return _txt_renderer; } + +void UIRenderer::enable_scissor(int x, int y, int width, int height) { + glEnable(GL_SCISSOR_TEST); + glScissor(x, y, width, height); +} + +void UIRenderer::disable_scissor(void) { + glDisable(GL_SCISSOR_TEST); +} diff --git a/client/src/ui/ui_renderer.h b/client/src/ui/ui_renderer.h index a51860a..e2f3247 100644 --- a/client/src/ui/ui_renderer.h +++ b/client/src/ui/ui_renderer.h @@ -26,6 +26,9 @@ public: /* Expose underlying text renderer for things like width calculation. */ TextRenderer* get_text_renderer(void); + void enable_scissor(int x, int y, int width, int height); + void disable_scissor(void); + private: ShapeRenderer* _shape_renderer; TextRenderer* _txt_renderer; diff --git a/client/src/ui/ui_window.cpp b/client/src/ui/ui_window.cpp index d9fa69c..cbbeb70 100644 --- a/client/src/ui/ui_window.cpp +++ b/client/src/ui/ui_window.cpp @@ -129,9 +129,10 @@ void UIWindow::render(const RenderContext& context) { if(_content) { int content_screen_y = _y + title_bar_height; int content_height = _height - title_bar_height; + int content_width = _width; /* Got to pass GL y-coord for scissor box to work correctly. */ int content_gl_y = context.screen_height - content_screen_y - content_height; - _content->render(context, _x, content_screen_y, content_gl_y, _width, content_height); + _content->render(context, _x, content_screen_y, content_gl_y, content_width, content_height); } } @@ -215,7 +216,9 @@ void UIWindow::handle_event(SDL_Event* event, int screen_width, } if(_content) { int y_gl = screen_height - _y - _height; - _content->handle_input(event, _x, _y, y_gl); + int content_height = _height - title_bar_height; + int content_width = _width; + _content->handle_input(event, _x, _y, y_gl, content_width, content_height); } }