Menu bar for for text editor etc.

This commit is contained in:
Ritchie Cunningham 2025-10-03 23:56:30 +01:00
parent 30ae57d0ba
commit a6e159f0da
15 changed files with 242 additions and 67 deletions

View File

@ -4,6 +4,7 @@
#include <thread>
#include <chrono>
#include "gfx/types.h"
#include "network_manager.h"
#include "game_state.h"
#include "gfx/shape_renderer.h"
@ -135,7 +136,7 @@ void GameState::update(void) {
if(separator_pos != std::string::npos) {
std::string filepath = payload.substr(0, separator_pos);
std::string content = payload.substr(separator_pos+2);
auto editor = std::make_unique<Editor>();
auto editor = std::make_unique<Editor>(filepath);
editor->set_buffer_content(content);
auto editor_window = std::make_unique<UIWindow>(filepath.c_str(),
200, 200, 600, 400);
@ -192,22 +193,21 @@ void GameState::update(void) {
}
}
void GameState::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor) {
void GameState::render(const RenderContext& context) {
switch(_current_screen) {
case Screen::MAIN_MENU:
if(_main_menu) {
_main_menu->render(shape_renderer, txt_renderer);
_main_menu->render(context.shape_renderer, context.txt_renderer);
}
break;
case Screen::BOOTING:
if(_boot_sequence) {
_boot_sequence->render(txt_renderer, screen_height);
_boot_sequence->render(context.txt_renderer, context.screen_height);
}
break;
case Screen::DESKTOP:
if(_desktop) {
_desktop->render(shape_renderer, txt_renderer, screen_height, show_cursor);
_desktop->render(context);
}
break;
}

View File

@ -2,6 +2,8 @@
#include <memory>
#include "gfx/types.h"
class ClientNetwork;
class Desktop;
class BootSequence;
@ -25,8 +27,7 @@ public:
void start_single_player_now(int screen_width, int screen_height);
void handle_event(SDL_Event* event, int screen_width, int screen_height);
void update(void);
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor);
void render(const RenderContext& context);
private:
std::unique_ptr<ClientNetwork> _network;

View File

@ -1,5 +1,8 @@
#pragma once
class ShapeRenderer;
class TextRenderer;
struct Color {
float r, g, b;
};
@ -7,3 +10,10 @@ struct Color {
struct Rect {
int x, y, w, h;
};
struct RenderContext {
ShapeRenderer* shape_renderer;
TextRenderer* txt_renderer;
int screen_height;
bool show_cursor;
};

View File

@ -8,6 +8,7 @@
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "game_state.h"
#include "gfx/types.h"
#include "ui/cursor_manager.h"
const int SCREEN_WIDTH = 1280;
@ -109,7 +110,13 @@ int main(int argc, char** argv) {
glClearColor(0.04f, 0.05f, 0.06, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
game_state->render(shape_renderer_instance, txt_render_instance, SCREEN_HEIGHT, show_cursor);
RenderContext context = {
shape_renderer_instance,
txt_render_instance,
SCREEN_HEIGHT,
show_cursor
};
game_state->render(context);
/* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
SDL_GL_SwapWindow(window);

View File

@ -88,7 +88,7 @@ void Terminal::_on_ret_press(void) {
}
}
void Terminal::handle_input(SDL_Event* event) {
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(_input_view->handle_event(event)) { _on_ret_press(); }
}
@ -111,8 +111,7 @@ void Terminal::scroll(int amount, int win_content_height) {
}
}
void Terminal::render(TextRenderer* renderer, int x, int y, int width, int height,
bool show_cursor) {
void Terminal::render(const RenderContext& context, int x, int y, int width, int height) {
const Color white = { 1.0f, 1.0f, 1.0f };
const Color green = { 0.2f, 1.0f, 0.2f };
@ -126,7 +125,7 @@ void Terminal::render(TextRenderer* renderer, int x, int y, int width, int heigh
/* Draw History. */
for(size_t i = _scroll_offset; i < _history.size(); ++i) {
float y_pos = (y+height) - padding - line_height - ((i - _scroll_offset) * line_height);
renderer->render_text(_history[i].c_str(), x+padding, y_pos, 1.0f, white);
context.txt_renderer->render_text(_history[i].c_str(), x+padding, y_pos, 1.0f, white);
}
/* Draw current input line. */
@ -135,12 +134,13 @@ void Terminal::render(TextRenderer* renderer, int x, int y, int width, int heigh
/* Render prompt string. */
std::string prompt_str = _prompt + "> ";
renderer->render_text(prompt_str.c_str(), x + padding, prompt_y_pos, 1.0f, green);
context.txt_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 */
float input_width = width - (input_x_pos-x);
_input_view->render(renderer, input_x_pos, prompt_y_pos, input_width, line_height, show_cursor);
_input_view->render(context.txt_renderer, input_x_pos, prompt_y_pos, input_width,
line_height, context.show_cursor);
/* Disable scissor test. */
glDisable(GL_SCISSOR_TEST);
}

View File

@ -4,8 +4,8 @@
#include <memory>
#include <SDL3/SDL.h>
#include "gfx/txt_renderer.h"
#include "client_network.h"
#include "gfx/types.h"
#include "ui/text_buffer.h"
#include "ui/text_view.h"
#include "ui/i_window_content.h"
@ -17,8 +17,8 @@ public:
~Terminal(void);
void update(void) override;
void handle_input(SDL_Event* event) override;
void render(TextRenderer* renderer, int x, int y, int width, int height, bool show_cursor) override;
void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) override;
void render(const RenderContext& context, int x, int y, int width, int height) override;
void scroll(int amount, int content_height) override;
void add_history(const std::string& line);
void set_prompt(const std::string& prompt);

View File

@ -5,6 +5,7 @@
#include <functional>
#include <memory>
#include "gfx/types.h"
#include "ui/editor.h"
#include "desktop.h"
#include <SDL3/SDL_events.h>
@ -146,14 +147,6 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height
if(_focused_window) {
_focused_window->handle_event(event, screen_width, screen_height,
_taskbar->get_height());
IWindowContent* content = _focused_window->get_content();
if(content) {
/*
* Only terminal should receive direct text input this way.
* Editor gets it through the generic handle_event call.
*/
content->handle_input(event);
}
}
}
@ -202,22 +195,21 @@ UIWindow* Desktop::get_focused_window(void) {
return _focused_window;
}
void Desktop::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor) {
_render_wallpaper(txt_renderer);
void Desktop::render(const RenderContext& context) {
_render_wallpaper(context.txt_renderer);
if(_launcher_is_open) {
_launcher->render(shape_renderer, txt_renderer, screen_height);
_launcher->render(context.shape_renderer, context.txt_renderer, context.screen_height);
}
_taskbar->render(shape_renderer, txt_renderer, _windows, _focused_window);
_taskbar->render(context.shape_renderer, context.txt_renderer, _windows, _focused_window);
/* Render non-focused windows first. */
for(const auto& win : _windows) {
if(win.get() != _focused_window && !win->is_minimized()) {
win.get()->render(shape_renderer, txt_renderer, screen_height, show_cursor);
win.get()->render(context);
}
}
/* Render focused window last so it's on top. */
if(_focused_window && !_focused_window->is_minimized()) {
_focused_window->render(shape_renderer, txt_renderer, screen_height, show_cursor);
_focused_window->render(context);
}
}

View File

@ -5,8 +5,8 @@
#include <SDL3/SDL.h>
#include "client_network.h"
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
#include "ui/ui_window.h"
#include "ui/taskbar.h"
#include "ui/launcher.h"
@ -28,8 +28,7 @@ public:
void add_window(std::unique_ptr<UIWindow> window);
void handle_event(SDL_Event* event, int screen_width, int screen_height);
void update(int screen_width, int screen_height);
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height,
bool show_cursor);
void render(const RenderContext& context);
UIWindow* get_focused_window(void);

View File

@ -1,11 +1,28 @@
#include "editor.h"
#include <memory>
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
#include "ui/window_action.h"
Editor::Editor(void)
: _should_close(false), _pending_action({ActionType::NONE}) {
: _should_close(false), _pending_action({ActionType::NONE}),
_filename("untitled.txt") {
_view = std::make_unique<TextView>(&_buffer, true);
_menu_bar = std::make_unique<MenuBar>(25);
_menu_bar->add_menu("File");
_menu_bar->add_menu_item("File", "Save", [this]() {
_pending_action = {ActionType::WRITE_FILE, _filename, _buffer.get_text()};
});
}
Editor::Editor(const std::string& filename)
: _should_close(false), _pending_action({ActionType::NONE}),
_filename(filename) {
_view = std::make_unique<TextView>(&_buffer, true);
_menu_bar = std::make_unique<MenuBar>(25);
_menu_bar->add_menu("File");
_menu_bar->add_menu_item("File", "Save", [this]() {
_pending_action = {ActionType::WRITE_FILE, _filename, _buffer.get_text()};
});
}
Editor::~Editor(void) {}
@ -14,20 +31,28 @@ void Editor::update(void) {
/* Nothing to do yet. */
}
void Editor::handle_input(SDL_Event* event) {
void Editor::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) {
if(!event) return;
_menu_bar->handle_event(event, window_x, window_y, window_gl_y);
/* We don't care about the return val here. RET is just newline. */
if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_S &&
(event->key.mod & SDL_KMOD_CTRL)) {
/* C-S pressed, create a save action. */
_pending_action = { ActionType::WRITE_FILE, "test.txt", _buffer.get_text() };
_pending_action = { ActionType::WRITE_FILE, _filename, _buffer.get_text() };
} else {
_view->handle_event(event);
}
}
void Editor::render(TextRenderer* renderer, int x, int y, int width, int height,
bool show_cursor) {
_view->render(renderer, x, y, width, height, show_cursor);
void Editor::render(const RenderContext& context, int x, int y, int width, int height) {
int menu_bar_height = _menu_bar->get_height();
int menu_bar_y_gl = y + height - menu_bar_height;
_menu_bar->render(context.shape_renderer, context.txt_renderer, x, menu_bar_y_gl, width);
_view->render(context.txt_renderer, x, y, width, height - menu_bar_height,
context.show_cursor);
}
void Editor::scroll(int amount, int content_height) {
@ -47,3 +72,7 @@ WindowAction Editor::get_pending_action(void) {
_pending_action.type = ActionType::NONE; /* Clear action. */
return action;
}
const std::string& Editor::get_filename(void) const {
return _filename;
}

View File

@ -2,29 +2,33 @@
#include <memory>
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
#include "i_window_content.h"
#include "ui/text_buffer.h"
#include "text_view.h"
#include "ui/window_action.h"
#include "ui/menu_bar.h"
class Editor : public IWindowContent {
public:
Editor(void);
Editor(const std::string& filename);
~Editor(void) override;
void update(void) override;
void handle_input(SDL_Event* event) override;
void render(TextRenderer* renderer, int x, int y, int width, int height,
bool show_cursor) override;
void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) override;
void render(const RenderContext& context, int x, int y, int width, int height) override;
void scroll(int amount, int content_height) override;
bool should_close(void) override;
void set_buffer_content(const std::string& content);
WindowAction get_pending_action(void) override;
const std::string& get_filename(void) const;
private:
TextBuffer _buffer;
std::unique_ptr<TextView> _view;
std::unique_ptr<MenuBar> _menu_bar;
bool _should_close;
WindowAction _pending_action;
std::string _filename;
};

View File

@ -3,16 +3,14 @@
#include <SDL3/SDL_events.h>
#include "window_action.h"
class TextRenderer;
#include "gfx/types.h"
class IWindowContent {
public:
virtual ~IWindowContent(void) = default;
virtual void update(void) = 0;
virtual void handle_input(SDL_Event* event) = 0;
virtual void render(TextRenderer* renderer, int x, int y, int width, int height,
bool show_cursor = 0) = 0;
virtual void handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) = 0;
virtual void render(const RenderContext& context, int x, int y, int width, int height) = 0;
virtual void scroll(int amount, int content_height) = 0;
virtual bool should_close(void) = 0;
virtual WindowAction get_pending_action() = 0;

View File

@ -0,0 +1,95 @@
#include "menu_bar.h"
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
MenuBar::MenuBar(int height)
: _height(height) {}
MenuBar::~MenuBar(void) {}
void MenuBar::add_menu(const std::string& label) {
_menus.push_back({label, {}, false});
}
void MenuBar::add_menu_item(const std::string& menu_label, const std::string& item_label,
std::function<void()> action) {
for(auto& menu : _menus) {
if(menu.label == menu_label) {
menu.items.push_back({item_label, action});
return;
}
}
}
int MenuBar::get_height(void) const {
return _height;
}
void MenuBar::handle_event(SDL_Event* event, int window_x, int window_y, int window_gl_y) {
if(event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
int mouse_x = event->button.x;
int mouse_y = event->button.y;
int menu_x = window_x;
for(size_t i = 0; i < _menus.size(); ++i) {
int menu_width = 60; /* Just hardcode this for now. */
if(mouse_x >= menu_x && mouse_x <= menu_x + menu_width &&
mouse_y >= window_y && mouse_y <= window_y + _height) {
if(_open_menu_index == (int)i) {
_open_menu_index = -1; /* Close if open already. */
} else {
_open_menu_index = i;
}
return;
}
menu_x += menu_width;
}
if(_open_menu_index != -1) {
Menu& open_menu = _menus[_open_menu_index];
int item_y = window_gl_y + _height;
int item_height = 30;
int dropdown_width = 150;
for(const auto& item: open_menu.items) {
if(mouse_x >= window_x && mouse_x <= window_x + dropdown_width &&
(window_gl_y - mouse_y) >= item_y && (window_gl_y - mouse_y) <= item_y + item_height) {
item.action();
_open_menu_index = -1; /* Close menu. */
return;
}
item_y += item_height;
}
}
/* Close any open menu when clicked outside. */
_open_menu_index = -1;
}
}
void MenuBar::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int x, int y_gl, int width) {
const Color bg_color = {0.15f, 0.17f, 0.19f};
const Color text_color = {0.9f, 0.9f, 0.9f};
const Color hover_color = {0.3f, 0.32f, 0.34f};
shape_renderer->draw_rect(x, y_gl, width, _height, bg_color);
int menu_x = x;
for(size_t i = 0; i < _menus.size(); ++i) {
int menu_width = 60;
txt_renderer->render_text(_menus[i].label.c_str(), menu_x+10, y_gl+8, 1.0f, text_color);
if(_open_menu_index == (int)i) {
int item_gl_y = y_gl - 30; /* Draw items below the bar. */
int item_height = 30;
int dropdown_width = 150;
for(const auto& item : _menus[i].items) {
shape_renderer->draw_rect(menu_x, item_gl_y, dropdown_width, item_height, bg_color);
txt_renderer->render_text(item.label.c_str(), menu_x+10, item_gl_y+8, 1.0f, text_color);
item_gl_y -= item_height;
}
}
menu_x += menu_width;
}
}

40
client/src/ui/menu_bar.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <vector>
#include <functional>
#include <SDL3/SDL_events.h>
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
struct MenuItem {
std::string label;
std::function<void()> action;
};
struct Menu {
std::string label;
std::vector<MenuItem> items;
bool is_open = false;
};
class MenuBar {
public:
MenuBar(int height);
~MenuBar(void);
void add_menu(const std::string& label);
void add_menu_item(const std::string& menu_label, const std::string& item_label,
std::function<void()> action);
void handle_event(SDL_Event* event, int window_x, int window_y, int window_gl_y);
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int x,
int y_gl, int width);
int get_height(void) const;
private:
int _height;
std::vector<Menu> _menus;
int _open_menu_index = -1;
};

View File

@ -5,7 +5,6 @@
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
#include "ui/i_window_content.h"
#include "ui/window_action.h"
UIWindow::UIWindow(const char* title, int x, int y, int width, int height) :
_title(title),
@ -78,8 +77,7 @@ bool UIWindow::is_point_inside(int x, int y) {
y >= _y && y <= _y + _height);
}
void UIWindow::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor) {
void UIWindow::render(const RenderContext& context) {
int title_bar_height = 30;
/* Define colours. */
@ -90,22 +88,22 @@ void UIWindow::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
const Color resize_handle_color = { 0.9f, 0.9f, 0.9f };
/* Convert top-left coords to bottom-left for OpenGL. */
int y_gl = screen_height - _y - _height;
int y_gl = context.screen_height - _y - _height;
/* Draw main window frame/background. */
shape_renderer->draw_rect(_x, y_gl, _width, _height, frame_color);
context.shape_renderer->draw_rect(_x, y_gl, _width, _height, frame_color);
/* Draw title bar. */
if(_is_focused) {
shape_renderer->draw_rect(_x, y_gl+_height-title_bar_height, _width, title_bar_height,
context.shape_renderer->draw_rect(_x, y_gl+_height-title_bar_height, _width, title_bar_height,
focused_title_bar_color);
} else {
shape_renderer->draw_rect(_x, y_gl+_height-title_bar_height, _width, title_bar_height,
context.shape_renderer->draw_rect(_x, y_gl+_height-title_bar_height, _width, title_bar_height,
title_bar_color);
}
/* Draw title text. */
txt_renderer->render_text(_title.c_str(), _x+5, y_gl+_height-title_bar_height+8,
context.txt_renderer->render_text(_title.c_str(), _x+5, y_gl+_height-title_bar_height+8,
1.0f, title_text_color);
/* Draw title bar buttons. */
@ -113,7 +111,7 @@ void UIWindow::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int button_margin = 5;
int x_offset = _x + _width - button_size - button_margin;
for(const auto& button : _title_bar_buttons) {
shape_renderer->draw_rect(x_offset, y_gl + _height - title_bar_height + button_margin,
context.shape_renderer->draw_rect(x_offset, y_gl + _height - title_bar_height + button_margin,
button_size, button_size, button.color);
x_offset -= (button_size + button_margin);
}
@ -121,13 +119,13 @@ void UIWindow::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
/* Draw Resize handle. */
int corner_x = _x + _width;
int corner_y = y_gl;
shape_renderer->draw_triangle(corner_x, corner_y + 10, corner_x - 10,
context.shape_renderer->draw_triangle(corner_x, corner_y + 10, corner_x - 10,
corner_y, corner_x, corner_y, resize_handle_color);
if(_content) {
int content_y_gl = y_gl;
int content_height_gl = _height - title_bar_height;
_content->render(txt_renderer, _x, content_y_gl, _width, content_height_gl, show_cursor);
_content->render(context, _x, content_y_gl, _width, content_height_gl);
}
}
@ -209,5 +207,9 @@ void UIWindow::handle_event(SDL_Event* event, int screen_width,
_content->scroll(-event->wheel.y, content_height_gl);
}
}
if(_content) {
int y_gl = screen_height - _y - _height;
_content->handle_input(event, _x, _y, y_gl);
}
}

View File

@ -4,8 +4,7 @@
#include <string>
#include <vector>
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
#include "i_window_content.h"
enum class WindowState {
@ -31,8 +30,7 @@ public:
~UIWindow(void);
void update(void);
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height,
bool show_cursor);
void render(const RenderContext& context);
void handle_event(SDL_Event* event, int screen_width, int screen_height,
int taskbar_height);
void minimize(void);