[Refactor] Abstract text editing into reusable components.
This commit is contained in:
		
							parent
							
								
									43907509eb
								
							
						
					
					
						commit
						4a6d33292a
					
				@ -1,4 +1,5 @@
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
#include <SDL3/SDL_events.h>
 | 
			
		||||
@ -8,11 +9,10 @@
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "gfx/types.h"
 | 
			
		||||
 | 
			
		||||
Terminal::Terminal(ClientNetwork* network) : _network(network) {
 | 
			
		||||
  /* Placeholder welcome message to history. */
 | 
			
		||||
  _should_close   = false;
 | 
			
		||||
  _scroll_offset  = 0;
 | 
			
		||||
  _prompt         = "";
 | 
			
		||||
Terminal::Terminal(ClientNetwork* network)
 | 
			
		||||
    : _network(network), _should_close(false), _scroll_offset(0),
 | 
			
		||||
      _prompt("") {
 | 
			
		||||
  _input_view = std::make_unique<TextView>(&_input_buffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Terminal::~Terminal(void) {}
 | 
			
		||||
@ -45,8 +45,8 @@ void Terminal::set_prompt(const std::string& prompt) {
 | 
			
		||||
bool Terminal::close(void) { return _should_close; }
 | 
			
		||||
 | 
			
		||||
void Terminal::_on_ret_press(void) {
 | 
			
		||||
  std::string command = _input_buffer;
 | 
			
		||||
  _input_buffer.clear(); /* Clear the input buffer immediately. */
 | 
			
		||||
  std::string command = _input_buffer.get_line(0);
 | 
			
		||||
  _input_buffer.clear();
 | 
			
		||||
 | 
			
		||||
  /* Add the command to history. */
 | 
			
		||||
  _history.push_back(_prompt + "> " + command);
 | 
			
		||||
@ -70,16 +70,9 @@ void Terminal::_on_ret_press(void) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Terminal::handle_input(SDL_Event* event) {
 | 
			
		||||
  if(event->type == SDL_EVENT_TEXT_INPUT) {
 | 
			
		||||
    /* Append chars to the input buffer. */
 | 
			
		||||
    _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();
 | 
			
		||||
    }
 | 
			
		||||
  /* Pass input to TextView; if true, RET was pressed. */
 | 
			
		||||
  if(_input_view->handle_event(event)) {
 | 
			
		||||
    _on_ret_press();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -120,14 +113,16 @@ void Terminal::render(TextRenderer* renderer, int x, int y, int width, int heigh
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 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
 | 
			
		||||
    - ((_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. */
 | 
			
		||||
  glDisable(GL_SCISSOR_TEST);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,13 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "client_network.h"
 | 
			
		||||
#include "ui/text_buffer.h"
 | 
			
		||||
#include "ui/text_view.h"
 | 
			
		||||
 | 
			
		||||
class Terminal {
 | 
			
		||||
public:
 | 
			
		||||
@ -22,10 +25,11 @@ public:
 | 
			
		||||
private:
 | 
			
		||||
  void _on_ret_press(void);
 | 
			
		||||
 | 
			
		||||
  std::string _input_buffer;
 | 
			
		||||
  bool _should_close;
 | 
			
		||||
  std::vector<std::string> _history;
 | 
			
		||||
  int _scroll_offset;
 | 
			
		||||
  std::string _prompt;
 | 
			
		||||
  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