[Refactor] Abstract text editing into reusable components.
This commit is contained in:
parent
43907509eb
commit
4a6d33292a
@ -1,4 +1,5 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <memory>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <SDL3/SDL_events.h>
|
#include <SDL3/SDL_events.h>
|
||||||
@ -8,11 +9,10 @@
|
|||||||
#include "gfx/txt_renderer.h"
|
#include "gfx/txt_renderer.h"
|
||||||
#include "gfx/types.h"
|
#include "gfx/types.h"
|
||||||
|
|
||||||
Terminal::Terminal(ClientNetwork* network) : _network(network) {
|
Terminal::Terminal(ClientNetwork* network)
|
||||||
/* Placeholder welcome message to history. */
|
: _network(network), _should_close(false), _scroll_offset(0),
|
||||||
_should_close = false;
|
_prompt("") {
|
||||||
_scroll_offset = 0;
|
_input_view = std::make_unique<TextView>(&_input_buffer);
|
||||||
_prompt = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Terminal::~Terminal(void) {}
|
Terminal::~Terminal(void) {}
|
||||||
@ -45,8 +45,8 @@ void Terminal::set_prompt(const std::string& prompt) {
|
|||||||
bool Terminal::close(void) { return _should_close; }
|
bool Terminal::close(void) { return _should_close; }
|
||||||
|
|
||||||
void Terminal::_on_ret_press(void) {
|
void Terminal::_on_ret_press(void) {
|
||||||
std::string command = _input_buffer;
|
std::string command = _input_buffer.get_line(0);
|
||||||
_input_buffer.clear(); /* Clear the input buffer immediately. */
|
_input_buffer.clear();
|
||||||
|
|
||||||
/* Add the command to history. */
|
/* Add the command to history. */
|
||||||
_history.push_back(_prompt + "> " + command);
|
_history.push_back(_prompt + "> " + command);
|
||||||
@ -70,18 +70,11 @@ void Terminal::_on_ret_press(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::handle_input(SDL_Event* event) {
|
void Terminal::handle_input(SDL_Event* event) {
|
||||||
if(event->type == SDL_EVENT_TEXT_INPUT) {
|
/* Pass input to TextView; if true, RET was pressed. */
|
||||||
/* Append chars to the input buffer. */
|
if(_input_view->handle_event(event)) {
|
||||||
_input_buffer += event->text.text;
|
|
||||||
} else if(event->type == SDL_EVENT_KEY_DOWN) {
|
|
||||||
/* Handle special keys. */
|
|
||||||
if(event->key.key == SDLK_BACKSPACE && _input_buffer.length() > 0) {
|
|
||||||
_input_buffer.pop_back();
|
|
||||||
} else if(event->key.key == SDLK_RETURN) {
|
|
||||||
_on_ret_press();
|
_on_ret_press();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Terminal::scroll(int amount, int win_content_height) {
|
void Terminal::scroll(int amount, int win_content_height) {
|
||||||
/* amount > 0 = scroll up. amount < 0 = scroll down. */
|
/* amount > 0 = scroll up. amount < 0 = scroll down. */
|
||||||
@ -120,14 +113,16 @@ void Terminal::render(TextRenderer* renderer, int x, int y, int width, int heigh
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Draw current input line. */
|
/* Draw current input line. */
|
||||||
std::string line_to_render = _prompt + "> " + _input_buffer;
|
|
||||||
if(show_cursor) {
|
|
||||||
line_to_render += "_";
|
|
||||||
}
|
|
||||||
float prompt_y_pos = (y+height) - padding - line_height
|
float prompt_y_pos = (y+height) - padding - line_height
|
||||||
- ((_history.size()-_scroll_offset)*line_height);
|
- ((_history.size()-_scroll_offset)*line_height);
|
||||||
renderer->render_text(line_to_render.c_str(), x+padding, prompt_y_pos, 1.0f, green);
|
|
||||||
|
|
||||||
|
/* Render prompt string. */
|
||||||
|
std::string prompt_str = _prompt + "> ";
|
||||||
|
renderer->render_text(prompt_str.c_str(), x + padding, prompt_y_pos, 1.0f, green);
|
||||||
|
|
||||||
|
/* Render text view for the input right after prompt. */
|
||||||
|
float input_x_pos = x + padding + (prompt_str.length() * 8.5f); /* Estimate width */
|
||||||
|
_input_view->render(renderer, input_x_pos, prompt_y_pos, show_cursor);
|
||||||
/* Disable scissor test. */
|
/* Disable scissor test. */
|
||||||
glDisable(GL_SCISSOR_TEST);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "gfx/txt_renderer.h"
|
#include "gfx/txt_renderer.h"
|
||||||
#include "client_network.h"
|
#include "client_network.h"
|
||||||
|
#include "ui/text_buffer.h"
|
||||||
|
#include "ui/text_view.h"
|
||||||
|
|
||||||
class Terminal {
|
class Terminal {
|
||||||
public:
|
public:
|
||||||
@ -22,10 +25,11 @@ public:
|
|||||||
private:
|
private:
|
||||||
void _on_ret_press(void);
|
void _on_ret_press(void);
|
||||||
|
|
||||||
std::string _input_buffer;
|
|
||||||
bool _should_close;
|
bool _should_close;
|
||||||
std::vector<std::string> _history;
|
std::vector<std::string> _history;
|
||||||
int _scroll_offset;
|
int _scroll_offset;
|
||||||
std::string _prompt;
|
std::string _prompt;
|
||||||
ClientNetwork* _network;
|
ClientNetwork* _network;
|
||||||
|
TextBuffer _input_buffer;
|
||||||
|
std::unique_ptr<TextView> _input_view;
|
||||||
};
|
};
|
||||||
|
|||||||
77
client/src/ui/text_view.cpp
Normal file
77
client/src/ui/text_view.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include <SDL3/SDL_events.h>
|
||||||
|
#include "text_view.h"
|
||||||
|
#include "gfx/txt_renderer.h"
|
||||||
|
#include "gfx/types.h"
|
||||||
|
#include "ui/text_buffer.h"
|
||||||
|
|
||||||
|
TextView::TextView(TextBuffer* buffer) : _buffer(buffer) {}
|
||||||
|
|
||||||
|
TextView::~TextView(void) {}
|
||||||
|
|
||||||
|
bool TextView::handle_event(SDL_Event* event) {
|
||||||
|
if(!_buffer) return false;
|
||||||
|
|
||||||
|
if(event->type == SDL_EVENT_TEXT_INPUT) {
|
||||||
|
_buffer->insert_char(event->text.text[0]);
|
||||||
|
} else if(event->type == SDL_EVENT_KEY_DOWN) {
|
||||||
|
switch(event->key.key) {
|
||||||
|
case SDLK_BACKSPACE:
|
||||||
|
_buffer->backspace();
|
||||||
|
break;
|
||||||
|
case SDLK_RETURN:
|
||||||
|
/* Instead of adding newline, signal that input was submitted. */
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case SDLK_LEFT:
|
||||||
|
_buffer->move_cursor(-1,0);
|
||||||
|
break;
|
||||||
|
case SDLK_RIGHT:
|
||||||
|
_buffer->move_cursor(1,0);
|
||||||
|
break;
|
||||||
|
case SDLK_UP:
|
||||||
|
_buffer->move_cursor(0,-1);
|
||||||
|
break;
|
||||||
|
case SDLK_DOWN:
|
||||||
|
_buffer->move_cursor(0,1);
|
||||||
|
break;
|
||||||
|
case SDLK_HOME:
|
||||||
|
_buffer->move_cursor_home();
|
||||||
|
break;
|
||||||
|
case SDLK_END:
|
||||||
|
_buffer->move_cursor_end();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextView::render(TextRenderer* renderer, int x, int y, bool show_cursor) {
|
||||||
|
if(!_buffer) return;
|
||||||
|
|
||||||
|
const Color text_color = { 1.0f, 1.0f, 1.0f };
|
||||||
|
float line_height = 20.0f; /* TODO: Get font metrics? */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Just render first line (for term input) for now.
|
||||||
|
* The code editor would need to iterate through _buffer->get_line_count()
|
||||||
|
*/
|
||||||
|
std::string line = _buffer->get_line(0);
|
||||||
|
Point cursor_pos = _buffer->get_cursor_pos();
|
||||||
|
|
||||||
|
if(show_cursor) {
|
||||||
|
/*
|
||||||
|
* 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, '_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer->render_text(line.c_str(), x, y, 1.0f, text_color);
|
||||||
|
}
|
||||||
19
client/src/ui/text_view.h
Normal file
19
client/src/ui/text_view.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL_events.h>
|
||||||
|
|
||||||
|
#include "ui/text_buffer.h"
|
||||||
|
|
||||||
|
class TextRenderer;
|
||||||
|
|
||||||
|
class TextView {
|
||||||
|
public:
|
||||||
|
TextView(TextBuffer* buffer);
|
||||||
|
~TextView(void);
|
||||||
|
|
||||||
|
bool handle_event(SDL_Event* event);
|
||||||
|
void render(TextRenderer* renderer, int x, int y, bool show_cursor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TextBuffer* _buffer;
|
||||||
|
};
|
||||||
86
common/src/ui/text_buffer.cpp
Normal file
86
common/src/ui/text_buffer.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "ui/text_buffer.h"
|
||||||
|
|
||||||
|
TextBuffer::TextBuffer(void) : _cursor_row(0), _cursor_col(0) {
|
||||||
|
_lines.emplace_back(""); /* Start with a single empty line. */
|
||||||
|
}
|
||||||
|
|
||||||
|
TextBuffer::~TextBuffer(void) {}
|
||||||
|
|
||||||
|
void TextBuffer::insert_char(char c) {
|
||||||
|
_lines[_cursor_row].insert(_cursor_col, 1, c);
|
||||||
|
_cursor_col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBuffer::backspace(void) {
|
||||||
|
if(_cursor_col > 0) {
|
||||||
|
_lines[_cursor_row].erase(_cursor_col - 1, 1);
|
||||||
|
_cursor_col--;
|
||||||
|
} else if(_cursor_row > 0) {
|
||||||
|
/* If start of line, merge with previous. */
|
||||||
|
_cursor_col = _lines[_cursor_row-1].length();
|
||||||
|
_lines[_cursor_row-1] += _lines[_cursor_row];
|
||||||
|
_lines.erase(_lines.begin() + _cursor_row);
|
||||||
|
_cursor_row--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBuffer::newline(void) {
|
||||||
|
std::string current_line = _lines[_cursor_row];
|
||||||
|
std::string new_line = current_line.substr(_cursor_col);
|
||||||
|
_lines[_cursor_row] = current_line.substr(0, _cursor_col);
|
||||||
|
|
||||||
|
_cursor_row++;
|
||||||
|
_lines.insert(_lines.begin() + _cursor_row, new_line);
|
||||||
|
_cursor_col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBuffer::move_cursor(int dx, int dy) {
|
||||||
|
_cursor_col += dx;
|
||||||
|
_cursor_row += dy;
|
||||||
|
|
||||||
|
/* Clamp cursor row. */
|
||||||
|
_cursor_row = std::max(0, std::min((int)_lines.size()-1, _cursor_row));
|
||||||
|
/* Clamp cursor col. */
|
||||||
|
_cursor_col = std::max(0, std::min((int)_lines[_cursor_row].length(), _cursor_col));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBuffer::move_cursor_home(void) {
|
||||||
|
_cursor_col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBuffer::move_cursor_end(void) {
|
||||||
|
_cursor_col = _lines[_cursor_row].length();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& TextBuffer::get_line(int row) const {
|
||||||
|
return _lines[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t TextBuffer::get_line_count(void) const {
|
||||||
|
return _lines.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Point TextBuffer::get_cursor_pos(void) const {
|
||||||
|
return { _cursor_row, _cursor_col };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TextBuffer::get_text(void) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
for(size_t i = 0; i < _lines.size(); ++i) {
|
||||||
|
ss << _lines[i];
|
||||||
|
if(i < _lines.size()-1) {
|
||||||
|
ss << "\n"; /* Or another separator? */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextBuffer::clear(void) {
|
||||||
|
_lines.clear();
|
||||||
|
_lines.emplace_back("");
|
||||||
|
_cursor_row = 0;
|
||||||
|
_cursor_col = 0;
|
||||||
|
}
|
||||||
33
common/src/ui/text_buffer.h
Normal file
33
common/src/ui/text_buffer.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
int row;
|
||||||
|
int col;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextBuffer {
|
||||||
|
public:
|
||||||
|
TextBuffer(void);
|
||||||
|
~TextBuffer(void);
|
||||||
|
|
||||||
|
void insert_char(char c);
|
||||||
|
void backspace(void);
|
||||||
|
void newline(void);
|
||||||
|
void move_cursor(int dx, int dy);
|
||||||
|
void move_cursor_home(void);
|
||||||
|
void move_cursor_end(void);
|
||||||
|
|
||||||
|
const std::string& get_line(int row) const;
|
||||||
|
size_t get_line_count(void) const;
|
||||||
|
Point get_cursor_pos(void) const;
|
||||||
|
std::string get_text(void) const;
|
||||||
|
void clear(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> _lines;
|
||||||
|
int _cursor_row;
|
||||||
|
int _cursor_col;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user