#include #include #include #include #include #include #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(&_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(); } }