bettola/client/src/terminal.cpp

197 lines
5.9 KiB
C++

#include <cstdio>
#include <memory>
#include <sstream>
#include <SDL3/SDL.h>
#include <GL/glew.h>
#include <SDL3/SDL_events.h>
#include "terminal.h"
#include "game_state.h"
#include "gfx/types.h"
#include "ui/editor.h"
#include "ui/window_action.h"
Terminal::Terminal(GameState* game_state)
: _game_state(game_state), _should_close(false), _command_history_index(0),
_scroll_offset(0), _prompt(""), _pending_action({ActionType::NONE}),
_session_id(0) {
_input_view = std::make_unique<TextView>(&_input_buffer, false, false, false);
}
Terminal::~Terminal(void) {}
void Terminal::update(void) {
}
void Terminal::add_history(const std::string& line) {
std::string line_with_spaces;
for(char ch : line) {
if(ch == '\t') {
line_with_spaces += " ";
} else {
line_with_spaces += ch;
}
}
_history.push_back(line_with_spaces);
if(line == "__CLOSE_CONNECTION__") {
_should_close = true;
}
}
void Terminal::set_prompt(const std::string& prompt) {
_prompt = prompt;
}
bool Terminal::should_close(void) {
return _should_close;
}
WindowAction Terminal::get_pending_action(void) {
WindowAction action = _pending_action;
_pending_action.type = ActionType::NONE;
return action;
}
void Terminal::set_session_id(uint32_t id) {
_session_id = id;
}
uint32_t Terminal::get_session_id(void) const {
return _session_id;
}
void Terminal::_on_ret_press(void) {
std::string command = _input_buffer.get_line(0);
if(!command.empty()) {
_command_history.push_back(command);
}
_command_history_index = _command_history.size();
_input_buffer.clear();
if(command == "clear") {
_history.push_back(_prompt + "> " + command);
_history.clear();
return;
}
/* Client-side command handling. */
std::stringstream ss(command);
std::string cmd_name;
ss >> cmd_name;
if(cmd_name == "edit") {
_history.push_back(_prompt + "> " + command);
ss >> _pending_action.payload1; /* filename. */
_pending_action.type = ActionType::READ_FILE;
return;
}
_history.push_back(_prompt + "> " + command);
_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) {
/* Pass input to TextView; if true, RET was pressed. */
if(event->type == SDL_EVENT_KEY_DOWN) {
switch(event->key.key) {
case SDLK_UP:
if(!_command_history.empty()) {
_command_history_index = std::max(0, _command_history_index-1);
_input_buffer.set_text(_command_history[_command_history_index]);
}
break;
case SDLK_DOWN:
if(!_command_history.empty()) {
_command_history_index =
std::min((int)_command_history.size(), _command_history_index+1);
if(_command_history_index < (int)_command_history.size()) {
_input_buffer.set_text(_command_history[_command_history_index]);
} else {
_input_buffer.clear();
}
}
break;
default:
if(_input_view->handle_event(event)) { _on_ret_press(); }
}
} else {
if(_input_view->handle_event(event)) { _on_ret_press(); }
}
}
void Terminal::scroll(int amount, int win_content_height) {
/* amount > 0 = scroll up. amount < 0 = scroll down. */
_scroll_offset += amount;
/* Lower bound: Don't scroll below the top of the history. */
if(_scroll_offset < 0) {
_scroll_offset = 0;
}
/* Upper bound: Don't scroll past the last command. */
float line_height = 20.0f;
int visible_lines = win_content_height / line_height;
int max_scroll = (_history.size()+1) - visible_lines;
if(max_scroll < 0) max_scroll = 0;
if(_scroll_offset > max_scroll) {
_scroll_offset = max_scroll;
}
}
void Terminal::render(const RenderContext& context, int x, int y_screen, int y_gl,
int width, int height) {
const Color white = { 1.0f, 1.0f, 1.0f };
const Color green = { 0.2f, 1.0f, 0.2f };
float line_height = 20.0f;
float padding = 5.0f;
/*
* Auto-scroll to bottom if the user has not manually scrolled up.
* Ensures input line is always visible after submitting a command.
*/
int visible_lines = (height-padding) / line_height;
int max_scroll_offset = (_history.size() + 1) - visible_lines;
if(max_scroll_offset < 0) max_scroll_offset = 0;
if(_scroll_offset >= max_scroll_offset - 1) _scroll_offset = max_scroll_offset;
context.ui_renderer->begin_text();
/* Enable scissor test to clip rendering to the window content area. */
glEnable(GL_SCISSOR_TEST);
glScissor(x, y_gl, width, height);
/* Draw History. */
for(size_t i = _scroll_offset; i < _history.size(); ++i) {
float line_y_pos = y_screen + padding + ((i - _scroll_offset) * line_height);
/* Culling: If line is already below the view, stop. */
if(line_y_pos > y_screen + height) { break; }
context.ui_renderer->render_text(_history[i].c_str(), x+padding, line_y_pos+18, white);
}
/* Draw current input line. */
float prompt_line_y = (y_screen + padding) + ((_history.size() - _scroll_offset) * line_height);
float prompt_baseline_y = prompt_line_y + 18;
/* Render prompt string. */
std::string prompt_str = _prompt + "> ";
context.ui_renderer->render_text(prompt_str.c_str(), x+padding, prompt_baseline_y, green);
/* 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);
SyntaxTheme theme; /* Terminal doesn't need highlighting, just a default theme. */
_input_view->render_text_content(context.ui_renderer, theme, 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, theme, input_x_pos, prompt_line_y, input_width, line_height);
context.ui_renderer->flush_shapes();
}
}