[Refactor] Implement batched shape rendering.

This mirrors the previous refactor of the TextRenderer. All calls to
draw_rect and draw_triangle now buffer vertex data (position and color).
A begin()/flush() system is introduced to manage batching.
This commit is contained in:
Ritchie Cunningham 2025-10-05 00:44:23 +01:00
parent d992cb54bf
commit d37f632344
17 changed files with 217 additions and 91 deletions

View File

@ -1,8 +1,8 @@
#version 330 core #version 330 core
out vec4 FragColor; out vec4 FragColor;
uniform vec3 objectColor; in vec3 ourColor;
void main() { void main() {
FragColor = vec4(objectColor, 1.0); FragColor = vec4(ourColor, 1.0);
} }

View File

@ -1,8 +1,12 @@
#version 330 core #version 330 core
layout (location = 0) in vec2 aPos; layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
uniform mat4 projection; uniform mat4 projection;
out vec3 ourColor;
void main() { void main() {
gl_Position = projection * vec4(aPos.x, aPos.y, 0.0, 1.0); gl_Position = projection * vec4(aPos.x, aPos.y, 0.0, 1.0);
ourColor = aColor;
} }

View File

@ -13,8 +13,10 @@
#include "ui/i_window_content.h" #include "ui/i_window_content.h"
#include "ui/ui_window.h" #include "ui/ui_window.h"
#include "ui/editor.h" #include "ui/editor.h"
#include <SDL3/SDL_events.h>
#include <ui/main_menu.h> #include <ui/main_menu.h>
#include <ui/boot_sequence.h> #include <ui/boot_sequence.h>
#include "debug/debug_overlay.h"
void GameState::_init_desktop(void) { void GameState::_init_desktop(void) {
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, _network.get()); _desktop = std::make_unique<Desktop>(_screen_width, _screen_height, _network.get());
@ -46,7 +48,8 @@ GameState::GameState(void) :
_current_screen(Screen::MAIN_MENU), _current_screen(Screen::MAIN_MENU),
_screen_width(0), _screen_width(0),
_screen_height(0), _screen_height(0),
_is_single_player(false) {} _is_single_player(false),
_show_debug_overlay(false) {_debug_overlay = std::make_unique<DebugOverlay>();}
GameState::~GameState(void) = default; GameState::~GameState(void) = default;
@ -77,6 +80,11 @@ void GameState::start_single_player_now(int screen_width, int screen_height) {
} }
void GameState::handle_event(SDL_Event* event, int screen_width, int screen_height) { void GameState::handle_event(SDL_Event* event, int screen_width, int screen_height) {
if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_D &&
(event->key.mod & SDL_KMOD_CTRL)) {
_show_debug_overlay = !_show_debug_overlay;
return; /* Consume the event. */
}
switch(_current_screen) { switch(_current_screen) {
case Screen::MAIN_MENU: case Screen::MAIN_MENU:
if(_main_menu) { if(_main_menu) {
@ -94,7 +102,8 @@ void GameState::handle_event(SDL_Event* event, int screen_width, int screen_heig
} }
} }
void GameState::update(float dt) { void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts) {
_debug_overlay->update(dt, draw_calls, shape_verts, text_verts);
switch(_current_screen) { switch(_current_screen) {
case Screen::MAIN_MENU: { case Screen::MAIN_MENU: {
if(!_main_menu) break; if(!_main_menu) break;
@ -221,4 +230,8 @@ void GameState::render(const RenderContext& context) {
} }
break; break;
} }
if(_show_debug_overlay) {
_debug_overlay->render(context.ui_renderer);
}
} }

View File

@ -4,6 +4,7 @@
#include "gfx/types.h" #include "gfx/types.h"
class DebugOverlay;
class ClientNetwork; class ClientNetwork;
class Desktop; class Desktop;
class BootSequence; class BootSequence;
@ -26,18 +27,20 @@ public:
void init(int screen_width, int screen_height); void init(int screen_width, int screen_height);
void start_single_player_now(int screen_width, int screen_height); void start_single_player_now(int screen_width, int screen_height);
void handle_event(SDL_Event* event, int screen_width, int screen_height); void handle_event(SDL_Event* event, int screen_width, int screen_height);
void update(float dt); void update(float dt, int draw_calls, int shape_verts, int text_verts);
void render(const RenderContext& context); void render(const RenderContext& context);
private: private:
std::unique_ptr<ClientNetwork> _network; std::unique_ptr<ClientNetwork> _network;
std::unique_ptr<Desktop> _desktop; std::unique_ptr<Desktop> _desktop;
std::unique_ptr<BootSequence> _boot_sequence; std::unique_ptr<BootSequence> _boot_sequence;
std::unique_ptr<MainMenu> _main_menu; std::unique_ptr<MainMenu> _main_menu;
Screen _current_screen; std::unique_ptr<DebugOverlay> _debug_overlay;
int _screen_width; bool _show_debug_overlay;
int _screen_height; Screen _current_screen;
bool _is_single_player; int _screen_width;
int _screen_height;
bool _is_single_player;
void _init_desktop(void); void _init_desktop(void);
void _run_server(void); void _run_server(void);

View File

@ -1,6 +1,7 @@
#include <GL/glew.h> #include <GL/glew.h>
#include "shape_renderer.h" #include "shape_renderer.h"
#include "debug/debug_stats.h"
#include "math/math.h" #include "math/math.h"
ShapeRenderer::ShapeRenderer(unsigned int screen_width, unsigned int screen_height) { ShapeRenderer::ShapeRenderer(unsigned int screen_width, unsigned int screen_height) {
@ -12,67 +13,70 @@ ShapeRenderer::ShapeRenderer(unsigned int screen_width, unsigned int screen_heig
_shape_shader->use(); _shape_shader->use();
_shape_shader->set_mat4("projection", _projection); _shape_shader->set_mat4("projection", _projection);
/* Configure VAO/VBO. */ /* Configure VAO/VBO for batch rendering. */
glGenVertexArrays(1, &_vao); glGenVertexArrays(1, &_vao);
glGenBuffers(1, &_vbo); glGenBuffers(1, &_vbo);
glBindVertexArray(_vao); glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 2, NULL, GL_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(ShapeVertex) * MAX_SHAPE_VERTICES, nullptr, GL_DYNAMIC_DRAW);
/* Position attribute. */
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex), (void*)0);
/* Colour attribute. */
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(ShapeVertex), (void*)offsetof(ShapeVertex, r));
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0); glBindVertexArray(0);
_vertices.reserve(MAX_SHAPE_VERTICES);
} }
ShapeRenderer::~ShapeRenderer(void) { ShapeRenderer::~ShapeRenderer(void) {
delete _shape_shader; delete _shape_shader;
} }
void ShapeRenderer::draw_rect(int x, int y, int width, int height, const Color& color) { void ShapeRenderer::begin(void) {
_shape_shader->use(); _vertices.clear();
_shape_shader->set_vec3("objectColor", color.r, color.g, color.b); }
void ShapeRenderer::flush(void) {
if(_vertices.empty()) return;
_shape_shader->use();
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, _vertices.size() * sizeof(ShapeVertex), _vertices.data());
DebugStats::draw_calls++;
DebugStats::shape_vertices += _vertices.size();
glDrawArrays(GL_TRIANGLES, 0, _vertices.size());
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void ShapeRenderer::draw_rect(int x, int y, int width, int height, const Color& color) {
float x_f = (float)x; float x_f = (float)x;
float y_f = (float)y; float y_f = (float)y;
float w_f = (float)width; float w_f = (float)width;
float h_f = (float)height; float h_f = (float)height;
/* This is ugly :) */ _vertices.push_back({x_f, y_f+h_f, color.r, color.g, color.b});
float vertices[6][2] = { _vertices.push_back({x_f, y_f, color.r, color.g, color.b});
{ x_f, y_f + h_f }, _vertices.push_back({x_f+w_f, y_f, color.r, color.g, color.b});
{ x_f, y_f },
{ x_f + w_f, y_f },
{ x_f, y_f + h_f }, _vertices.push_back({x_f, y_f+h_f, color.r, color.g, color.b});
{ x_f + w_f, y_f }, _vertices.push_back({x_f+w_f, y_f, color.r, color.g, color.b});
{x_f + w_f, y_f + h_f } _vertices.push_back({x_f+w_f, y_f+h_f, color.r, color.g, color.b});
};
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
} }
void ShapeRenderer::draw_triangle(int x1, int y1, int x2, int y2, int x3, void ShapeRenderer::draw_triangle(int x1, int y1, int x2, int y2, int x3,
int y3, const Color& color) { int y3, const Color& color) {
_shape_shader->use(); _vertices.push_back({(float)x1, (float)y1, color.r, color.g, color.b});
_shape_shader->set_vec3("objectColor", color.r, color.g, color.b); _vertices.push_back({(float)x2, (float)y2, color.r, color.g, color.b});
_vertices.push_back({(float)x3, (float)y3, color.r, color.g, color.b});
float vertices[3][2] = {
{(float)x1, (float)y1},
{(float)x2, (float)y2},
{(float)x3, (float)y3},
};
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
/*
* We're overwriting the buffer content here, this is fine for just one triangle,
* but don't do it for everything ;)
*/
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
} }

View File

@ -1,13 +1,26 @@
#pragma once #pragma once
#include <vector>
#include "shader.h" #include "shader.h"
#include "types.h" #include "types.h"
struct ShapeVertex {
float x, y, r, g, b;
};
const int MAX_SHAPES_PER_BATCH = 10000;
const int VERTICES_PER_RECT = 6;
const int MAX_SHAPE_VERTICES = MAX_SHAPES_PER_BATCH * VERTICES_PER_RECT;
class ShapeRenderer { class ShapeRenderer {
public: public:
ShapeRenderer(unsigned int screen_width, unsigned int screen_height); ShapeRenderer(unsigned int screen_width, unsigned int screen_height);
~ShapeRenderer(void); ~ShapeRenderer(void);
void begin(void);
void flush(void);
void draw_rect(int x, int y, int width, int height, const Color& color); void draw_rect(int x, int y, int width, int height, const Color& color);
void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3,
const Color& color); const Color& color);
@ -15,5 +28,6 @@ public:
private: private:
Shader* _shape_shader; Shader* _shape_shader;
unsigned int _vao, _vbo; unsigned int _vao, _vbo;
std::vector<ShapeVertex> _vertices;
float _projection[16]; float _projection[16];
}; };

View File

@ -5,6 +5,7 @@
#include <freetype/freetype.h> #include <freetype/freetype.h>
#include "txt_renderer.h" #include "txt_renderer.h"
#include "debug/debug_stats.h"
#include "math/math.h" #include "math/math.h"
TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height) { TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height) {
@ -141,6 +142,9 @@ void TextRenderer::flush(void) {
glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, _vertices.size() * sizeof(TextVertex), _vertices.data()); glBufferSubData(GL_ARRAY_BUFFER, 0, _vertices.size() * sizeof(TextVertex), _vertices.data());
DebugStats::draw_calls++;
DebugStats::text_vertices += _vertices.size();
glDrawArrays(GL_TRIANGLES, 0, _vertices.size()); glDrawArrays(GL_TRIANGLES, 0, _vertices.size());
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);

View File

@ -12,6 +12,7 @@
#include "ui/ui_renderer.h" #include "ui/ui_renderer.h"
#include "gfx/types.h" #include "gfx/types.h"
#include "ui/cursor_manager.h" #include "ui/cursor_manager.h"
#include "debug/debug_stats.h"
const int SCREEN_WIDTH = 1280; const int SCREEN_WIDTH = 1280;
const int SCREEN_HEIGHT = 720; const int SCREEN_HEIGHT = 720;
@ -101,6 +102,9 @@ int main(int argc, char** argv) {
bool running = true; bool running = true;
while(running) { while(running) {
/* Reset per-frame stats. */
DebugStats::reset();
/* Event handling. */ /* Event handling. */
SDL_Event event; SDL_Event event;
while(SDL_PollEvent(&event)) { while(SDL_PollEvent(&event)) {
@ -116,8 +120,6 @@ int main(int argc, char** argv) {
/* Clamp dt to avoid large jumps. */ /* Clamp dt to avoid large jumps. */
if(dt > 0.1f) dt = 0.1f; if(dt > 0.1f) dt = 0.1f;
game_state->update(dt);
Uint32 current_time = SDL_GetTicks(); Uint32 current_time = SDL_GetTicks();
if(current_time - last_blink_time > 500) { /* Every 500ms. */ if(current_time - last_blink_time > 500) { /* Every 500ms. */
show_cursor = !show_cursor; show_cursor = !show_cursor;
@ -135,6 +137,13 @@ int main(int argc, char** argv) {
}; };
game_state->render(context); game_state->render(context);
/* Update game state after rendering so stats are for the frame just rendered.
* I know this is unusual, but given the text based nature of the game
* we won't see a negative impact, and this is better than a large refactor ;)
*/
game_state->update(dt, DebugStats::draw_calls, DebugStats::shape_vertices,
DebugStats::text_vertices);
/* It's really odd to call it SwapWindow now, rather than SwapBuffer. */ /* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
SDL_GL_SwapWindow(window); SDL_GL_SwapWindow(window);
} }

View File

@ -60,9 +60,10 @@ Desktop::Desktop(int screen_width, int screen_height, ClientNetwork* network) {
Desktop::~Desktop(void) {} Desktop::~Desktop(void) {}
void Desktop::add_window(std::unique_ptr<UIWindow> window) { void Desktop::add_window(std::unique_ptr<UIWindow> window) {
UIWindow* window_ptr = window.get();
_windows.push_back(std::move(window)); _windows.push_back(std::move(window));
/* Focus new window. */ _taskbar->add_window(window_ptr);
_set_focused_window(_windows.back().get()); _set_focused_window(window_ptr);
} }
void Desktop::_set_focused_window(UIWindow* window) { void Desktop::_set_focused_window(UIWindow* window) {
@ -92,31 +93,37 @@ void Desktop::_set_focused_window(UIWindow* window) {
} }
void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height) { void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height) {
/* Let focused window handle all events first. */
if(_focused_window) {
_focused_window->handle_event(event, screen_width, screen_height, _taskbar->get_height());
}
if(event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { if(event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
int mouse_x = event->button.x; int mouse_x = event->button.x;
int mouse_y = event->button.y; int mouse_y = event->button.y;
/* If click is on taskbar, ignore it on the down-event. Let the up-event handle it. */
if(_taskbar->is_point_inside(mouse_x, mouse_y)) {
return;
}
/* When launcher open, check for outside click to close it. */ /* When launcher open, check for outside click to close it. */
if(_launcher_is_open && !_launcher->is_point_inside(mouse_x, mouse_y, screen_height)) { if(_launcher_is_open && !_launcher->is_point_inside(mouse_x, mouse_y, screen_height)) {
_launcher_is_open = false; _launcher_is_open = false;
return; return;
} }
/* If click no on focused window, find which window should receive focus. */ /* If click not on focused window, find which window should receive focus. */
bool click_on_window = false; bool click_on_window = false;
for(int i = _windows.size()-1; i >= 0; --i) { for(int i = _windows.size()-1; i >= 0; --i) {
if(_windows[i].get()->is_point_inside(mouse_x, mouse_y)) { if(_windows[i].get()->is_point_inside(mouse_x, mouse_y)) {
if(!_windows[i]->is_minimized()) { /* Top most window. Focus it. */
_set_focused_window(_windows[i].get()); UIWindow* target = _windows[i].get();
if(!target->is_minimized()) {
_set_focused_window(target);
/* Pass event down to newly focused window to handle dragging, etc. */
target->handle_event(event, screen_width, screen_height, _taskbar->get_height());
} }
click_on_window = true; click_on_window = true;
break; break;
} }
} }
/* Click wasn't on a window. Unfocus. */
if(!click_on_window) { if(!click_on_window) {
_set_focused_window(nullptr); _set_focused_window(nullptr);
} }
@ -139,7 +146,7 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height
_launcher_is_open = false; _launcher_is_open = false;
} }
} else { } else {
UIWindow* clicked_window = _taskbar->handle_event(event, screen_height, _windows); UIWindow* clicked_window = _taskbar->handle_event(event, screen_height);
if(clicked_window) { if(clicked_window) {
if(clicked_window == _focused_window && !clicked_window->is_minimized()) { if(clicked_window == _focused_window && !clicked_window->is_minimized()) {
clicked_window->minimize(); clicked_window->minimize();
@ -160,6 +167,11 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height
} }
CursorManager::set_cursor(CursorType::ARROW); CursorManager::set_cursor(CursorType::ARROW);
} }
/* Pass all other non-down-click events to focused window. */
if(_focused_window && event->type != SDL_EVENT_MOUSE_BUTTON_DOWN) {
_focused_window->handle_event(event, screen_width, screen_height, _taskbar->get_height());
}
} }
void Desktop::update(float dt, int screen_width, int screen_height) { void Desktop::update(float dt, int screen_width, int screen_height) {
@ -193,6 +205,7 @@ void Desktop::update(float dt, int screen_width, int screen_height) {
_windows.erase(std::remove_if(_windows.begin(), _windows.end(), _windows.erase(std::remove_if(_windows.begin(), _windows.end(),
[this](const std::unique_ptr<UIWindow>& w) { [this](const std::unique_ptr<UIWindow>& w) {
if (w->should_close()) { if (w->should_close()) {
_taskbar->remove_window(w.get());
if (w.get() == _focused_window) { if (w.get() == _focused_window) {
_focused_window = nullptr; _focused_window = nullptr;
} }
@ -243,7 +256,7 @@ void Desktop::render(const RenderContext& context) {
if(_launcher_is_open) { if(_launcher_is_open) {
_launcher->render(context.ui_renderer); _launcher->render(context.ui_renderer);
} }
_taskbar->render(context.ui_renderer, _windows, _focused_window); _taskbar->render(context.ui_renderer, _focused_window);
context.ui_renderer->flush_text(); context.ui_renderer->flush_text();
} }

View File

@ -22,6 +22,9 @@ void Launcher::render(UIRenderer* ui_renderer) {
const Color text_color = { 0.9f, 0.9f, 0.9f }; const Color text_color = { 0.9f, 0.9f, 0.9f };
const Color hover_color = { 0.3f, 0.32f, 0.34f }; const Color hover_color = { 0.3f, 0.32f, 0.34f };
ui_renderer->begin_shapes();
ui_renderer->begin_text();
/* Note: y-coord is TOP of launcher menu. */ /* Note: y-coord is TOP of launcher menu. */
ui_renderer->draw_rect(_x, _y, _width, _height, bg_color); ui_renderer->draw_rect(_x, _y, _width, _height, bg_color);
@ -41,6 +44,9 @@ void Launcher::render(UIRenderer* ui_renderer) {
ui_renderer->render_text(app_name.c_str(), _x+10, item_y+20, text_color); ui_renderer->render_text(app_name.c_str(), _x+10, item_y+20, text_color);
item_y += item_height; item_y += item_height;
} }
ui_renderer->flush_shapes();
ui_renderer->flush_text();
} }
std::string Launcher::handle_event(SDL_Event* event, int screen_height) { std::string Launcher::handle_event(SDL_Event* event, int screen_height) {

View File

@ -101,6 +101,7 @@ void MainMenu::render(UIRenderer* ui_renderer) {
ui_renderer->flush_text(); ui_renderer->flush_text();
/* Pass 2: Buttons. */ /* Pass 2: Buttons. */
ui_renderer->begin_shapes();
ui_renderer->begin_text(); ui_renderer->begin_text();
/* Button colours. */ /* Button colours. */
@ -124,6 +125,7 @@ void MainMenu::render(UIRenderer* ui_renderer) {
ui_renderer->render_text(button.label.c_str(), text_x, button.rect.y + 32, text_color); ui_renderer->render_text(button.label.c_str(), text_x, button.rect.y + 32, text_color);
} }
ui_renderer->flush_shapes();
ui_renderer->flush_text(); ui_renderer->flush_text();
} }

View File

@ -73,8 +73,11 @@ void MenuBar::handle_event(SDL_Event* event, int window_x, int window_y) {
} }
void MenuBar::render_bar(UIRenderer* ui_renderer, int x, int y, int width) { void MenuBar::render_bar(UIRenderer* ui_renderer, int x, int y, int width) {
const Color bg_color = {0.15f, 0.17f, 0.19f}; const Color bg_color = { 0.15f, 0.17f, 0.19f };
const Color text_color = {0.9f, 0.9f, 0.9f}; const Color text_color = { 0.9f, 0.9f, 0.9f };
ui_renderer->begin_shapes();
ui_renderer->begin_text();
ui_renderer->draw_rect(x, y, width, _height, bg_color); ui_renderer->draw_rect(x, y, width, _height, bg_color);
@ -85,6 +88,9 @@ void MenuBar::render_bar(UIRenderer* ui_renderer, int x, int y, int width) {
menu_x += menu_width; menu_x += menu_width;
} }
ui_renderer->flush_shapes();
ui_renderer->flush_text();
} }
void MenuBar::render_dropdown(UIRenderer* ui_renderer, int x, int y, int width ) { void MenuBar::render_dropdown(UIRenderer* ui_renderer, int x, int y, int width ) {
@ -93,6 +99,9 @@ void MenuBar::render_dropdown(UIRenderer* ui_renderer, int x, int y, int width )
const Color bg_color = { 0.15f, 0.17f, 0.19f }; const Color bg_color = { 0.15f, 0.17f, 0.19f };
const Color text_color = { 0.9f, 0.9f, 0.9f }; const Color text_color = { 0.9f, 0.9f, 0.9f };
ui_renderer->begin_shapes();
ui_renderer->begin_text();
int menu_x = x + (_open_menu_index*60); int menu_x = x + (_open_menu_index*60);
int item_y = y + _height; int item_y = y + _height;
int item_height = 30; int item_height = 30;
@ -102,4 +111,7 @@ void MenuBar::render_dropdown(UIRenderer* ui_renderer, int x, int y, int width )
ui_renderer->render_text(item.label.c_str(), menu_x+10, item_y+20, text_color); ui_renderer->render_text(item.label.c_str(), menu_x+10, item_y+20, text_color);
item_y += item_height; item_y += item_height;
} }
ui_renderer->flush_shapes();
ui_renderer->flush_text();
} }

View File

@ -1,10 +1,11 @@
#include "taskbar.h" #include <algorithm>
#include <memory> #include <memory>
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include "taskbar.h"
#include "ui/ui_renderer.h" #include "ui/ui_renderer.h"
#include "ui/ui_window.h" #include "ui/ui_window.h"
@ -17,15 +18,28 @@ Taskbar::Taskbar(int screen_width, int screen_height) {
Taskbar::~Taskbar(void) {} Taskbar::~Taskbar(void) {}
void Taskbar::render(UIRenderer* ui_renderer, void Taskbar::add_window(UIWindow* window) {
const std::vector<std::unique_ptr<UIWindow>>& windows, _buttons.push_back({window, window->get_title()});
UIWindow* focused_window) { }
void Taskbar::remove_window(UIWindow* window) {
_buttons.erase(
std::remove_if(_buttons.begin(), _buttons.end(),
[window](const TaskbarButton& btn) {
return btn.window == window;
}),
_buttons.end());
}
void Taskbar::render(UIRenderer* ui_renderer, UIWindow* focused_window) {
const Color taskbar_color = { 0.1f, 0.12f, 0.14f }; const Color taskbar_color = { 0.1f, 0.12f, 0.14f };
const Color button_color = { 0.2f, 0.22f, 0.24f }; const Color button_color = { 0.2f, 0.22f, 0.24f };
const Color button_focused_color = { 0.3f, 0.32f, 0.34f }; const Color button_focused_color = { 0.3f, 0.32f, 0.34f };
const Color button_text_color = { 0.9f, 0.9f, 0.9f }; const Color button_text_color = { 0.9f, 0.9f, 0.9f };
ui_renderer->begin_shapes();
ui_renderer->begin_text();
ui_renderer->draw_rect(0, _y_pos, _width, _height, taskbar_color); ui_renderer->draw_rect(0, _y_pos, _width, _height, taskbar_color);
/* Draw start button. */ /* Draw start button. */
@ -38,29 +52,31 @@ void Taskbar::render(UIRenderer* ui_renderer,
int padding = 5; int padding = 5;
int x_offset = _start_button_width + padding; int x_offset = _start_button_width + padding;
for(const auto& window : windows) { for(const auto& button: _buttons) {
bool is_focused = (window.get() == focused_window); bool is_focused = (button.window == focused_window);
ui_renderer->draw_rect(x_offset, _y_pos + padding, button_width, ui_renderer->draw_rect(x_offset, _y_pos + padding, button_width,
_height - (padding * 2), _height - (padding * 2),
is_focused ? button_focused_color : button_color); is_focused ? button_focused_color : button_color);
/* TODO: Truncate text when too long. */ /* TODO: Truncate text when too long. */
ui_renderer->render_text(window->get_title().c_str(), x_offset + 10, ui_renderer->render_text(button.title.c_str(), x_offset + 10,
_y_pos + 20, button_text_color); _y_pos + 20, button_text_color);
x_offset += button_width + padding; x_offset += button_width + padding;
} }
/* Draw clock. */ /* Draw clock. */
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now); auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss; std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t), "%H:%M"); ss << std::put_time(std::localtime(&in_time_t), "%H:%M");
std::string time_str = ss.str(); std::string time_str = ss.str();
ui_renderer->render_text(time_str.c_str(), _width-50, _y_pos+20, button_text_color); ui_renderer->render_text(time_str.c_str(), _width-50, _y_pos+20, button_text_color);
ui_renderer->flush_shapes();
ui_renderer->flush_text();
} }
UIWindow* Taskbar::handle_event(SDL_Event* event, int screen_height, UIWindow* Taskbar::handle_event(SDL_Event* event, int screen_height) {
const std::vector<std::unique_ptr<UIWindow>>& windows) {
if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) { if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
int mouse_x = event->button.x; int mouse_x = event->button.x;
int mouse_y = event->button.y; int mouse_y = event->button.y;
@ -68,10 +84,10 @@ UIWindow* Taskbar::handle_event(SDL_Event* event, int screen_height,
int button_width = 150; int button_width = 150;
int padding = 5; int padding = 5;
int x_offset = _start_button_width + padding; int x_offset = _start_button_width + padding;
for(const auto& window : windows) { for(const auto& button : _buttons) {
if(mouse_x >= x_offset && mouse_x <= x_offset + button_width && if(mouse_x >= x_offset && mouse_x <= x_offset + button_width &&
mouse_y >= _y_pos && mouse_y <= _y_pos + _height) { mouse_y >= _y_pos && mouse_y <= _y_pos + _height) {
return window.get(); /* Return clicked window. */ return button.window; /* Return clicked window. */
} }
x_offset += button_width + padding; x_offset += button_width + padding;
} }
@ -92,3 +108,7 @@ bool Taskbar::is_start_button_clicked(SDL_Event* event, int screen_height) {
int Taskbar::get_height(void) const { int Taskbar::get_height(void) const {
return _height; return _height;
} }
bool Taskbar::is_point_inside(int x, int y) const {
return (y >= _y_pos && y<= _y_pos + _height);
}

View File

@ -1,27 +1,33 @@
#pragma once #pragma once
#include <SDL3/SDL_events.h> #include <SDL3/SDL_events.h>
#include <string>
#include <vector> #include <vector>
#include <memory>
class UIWindow; class UIWindow;
class UIRenderer; class UIRenderer;
struct TaskbarButton {
UIWindow* window;
std::string title;
};
class Taskbar { class Taskbar {
public: public:
Taskbar(int screen_width, int screen_height); Taskbar(int screen_width, int screen_height);
~Taskbar(void); ~Taskbar(void);
void render(UIRenderer* ui_renderer, void add_window(UIWindow* window);
const std::vector<std::unique_ptr<UIWindow>>& windows, void remove_window(UIWindow* window);
UIWindow* focused_window);
UIWindow* handle_event(SDL_Event* event, int screen_height, void render(UIRenderer* ui_renderer, UIWindow* focused_window);
const std::vector<std::unique_ptr<UIWindow>>& windows); UIWindow* handle_event(SDL_Event* event, int screen_height);
bool is_start_button_clicked(SDL_Event* event, int screen_height); bool is_start_button_clicked(SDL_Event* event, int screen_height);
bool is_point_inside(int x, int y) const;
int get_height(void) const; int get_height(void) const;
private: private:
std::vector<TaskbarButton> _buttons;
int _width; int _width;
int _height; int _height;
int _y_pos; int _y_pos;

View File

@ -38,6 +38,16 @@ void UIRenderer::flush_text(void) {
_txt_renderer->flush(); _txt_renderer->flush();
} }
void UIRenderer::begin_shapes(void) {
if(!_shape_renderer) return;
_shape_renderer->begin();
}
void UIRenderer::flush_shapes(void) {
if(!_shape_renderer) return;
_shape_renderer->flush();
}
TextRenderer* UIRenderer::get_text_renderer(void) { TextRenderer* UIRenderer::get_text_renderer(void) {
return _txt_renderer; return _txt_renderer;
} }

View File

@ -20,6 +20,9 @@ public:
void begin_text(void); void begin_text(void);
void flush_text(void); void flush_text(void);
void begin_shapes(void);
void flush_shapes(void);
/* Expose underlying text renderer for things like width calculation. */ /* Expose underlying text renderer for things like width calculation. */
TextRenderer* get_text_renderer(void); TextRenderer* get_text_renderer(void);

View File

@ -83,6 +83,7 @@ bool UIWindow::is_point_inside(int x, int y) {
void UIWindow::render(const RenderContext& context) { void UIWindow::render(const RenderContext& context) {
int title_bar_height = 30; int title_bar_height = 30;
context.ui_renderer->begin_shapes();
context.ui_renderer->begin_text(); context.ui_renderer->begin_text();
/* Define colours. */ /* Define colours. */
@ -121,6 +122,8 @@ void UIWindow::render(const RenderContext& context) {
context.ui_renderer->draw_triangle(corner_x, corner_y - 10, corner_x - 10, context.ui_renderer->draw_triangle(corner_x, corner_y - 10, corner_x - 10,
corner_y, corner_x, corner_y, resize_handle_color); corner_y, corner_x, corner_y, resize_handle_color);
/* Flush the shapes and text for the window frame and title bar. */
context.ui_renderer->flush_shapes();
context.ui_renderer->flush_text(); context.ui_renderer->flush_text();
if(_content) { if(_content) {