[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:
parent
d992cb54bf
commit
d37f632344
@ -1,8 +1,8 @@
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
|
||||
uniform vec3 objectColor;
|
||||
in vec3 ourColor;
|
||||
|
||||
void main() {
|
||||
FragColor = vec4(objectColor, 1.0);
|
||||
FragColor = vec4(ourColor, 1.0);
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec2 aPos;
|
||||
layout (location = 1) in vec3 aColor;
|
||||
|
||||
uniform mat4 projection;
|
||||
|
||||
out vec3 ourColor;
|
||||
|
||||
void main() {
|
||||
gl_Position = projection * vec4(aPos.x, aPos.y, 0.0, 1.0);
|
||||
ourColor = aColor;
|
||||
}
|
||||
|
||||
@ -13,8 +13,10 @@
|
||||
#include "ui/i_window_content.h"
|
||||
#include "ui/ui_window.h"
|
||||
#include "ui/editor.h"
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <ui/main_menu.h>
|
||||
#include <ui/boot_sequence.h>
|
||||
#include "debug/debug_overlay.h"
|
||||
|
||||
void GameState::_init_desktop(void) {
|
||||
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, _network.get());
|
||||
@ -46,7 +48,8 @@ GameState::GameState(void) :
|
||||
_current_screen(Screen::MAIN_MENU),
|
||||
_screen_width(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;
|
||||
|
||||
@ -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) {
|
||||
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) {
|
||||
case Screen::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) {
|
||||
case Screen::MAIN_MENU: {
|
||||
if(!_main_menu) break;
|
||||
@ -221,4 +230,8 @@ void GameState::render(const RenderContext& context) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(_show_debug_overlay) {
|
||||
_debug_overlay->render(context.ui_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include "gfx/types.h"
|
||||
|
||||
class DebugOverlay;
|
||||
class ClientNetwork;
|
||||
class Desktop;
|
||||
class BootSequence;
|
||||
@ -26,18 +27,20 @@ public:
|
||||
void init(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 update(float dt);
|
||||
void update(float dt, int draw_calls, int shape_verts, int text_verts);
|
||||
void render(const RenderContext& context);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ClientNetwork> _network;
|
||||
std::unique_ptr<Desktop> _desktop;
|
||||
std::unique_ptr<BootSequence> _boot_sequence;
|
||||
std::unique_ptr<MainMenu> _main_menu;
|
||||
Screen _current_screen;
|
||||
int _screen_width;
|
||||
int _screen_height;
|
||||
bool _is_single_player;
|
||||
std::unique_ptr<ClientNetwork> _network;
|
||||
std::unique_ptr<Desktop> _desktop;
|
||||
std::unique_ptr<BootSequence> _boot_sequence;
|
||||
std::unique_ptr<MainMenu> _main_menu;
|
||||
std::unique_ptr<DebugOverlay> _debug_overlay;
|
||||
bool _show_debug_overlay;
|
||||
Screen _current_screen;
|
||||
int _screen_width;
|
||||
int _screen_height;
|
||||
bool _is_single_player;
|
||||
|
||||
void _init_desktop(void);
|
||||
void _run_server(void);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "shape_renderer.h"
|
||||
#include "debug/debug_stats.h"
|
||||
#include "math/math.h"
|
||||
|
||||
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->set_mat4("projection", _projection);
|
||||
|
||||
/* Configure VAO/VBO. */
|
||||
/* Configure VAO/VBO for batch rendering. */
|
||||
glGenVertexArrays(1, &_vao);
|
||||
glGenBuffers(1, &_vbo);
|
||||
glBindVertexArray(_vao);
|
||||
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);
|
||||
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);
|
||||
glBindVertexArray(0);
|
||||
|
||||
_vertices.reserve(MAX_SHAPE_VERTICES);
|
||||
}
|
||||
|
||||
ShapeRenderer::~ShapeRenderer(void) {
|
||||
delete _shape_shader;
|
||||
}
|
||||
|
||||
void ShapeRenderer::draw_rect(int x, int y, int width, int height, const Color& color) {
|
||||
_shape_shader->use();
|
||||
_shape_shader->set_vec3("objectColor", color.r, color.g, color.b);
|
||||
void ShapeRenderer::begin(void) {
|
||||
_vertices.clear();
|
||||
}
|
||||
|
||||
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 y_f = (float)y;
|
||||
float w_f = (float)width;
|
||||
float h_f = (float)height;
|
||||
|
||||
/* This is ugly :) */
|
||||
float vertices[6][2] = {
|
||||
{ x_f, y_f + h_f },
|
||||
{ x_f, y_f },
|
||||
{ x_f + w_f, y_f },
|
||||
_vertices.push_back({x_f, y_f+h_f, color.r, color.g, color.b});
|
||||
_vertices.push_back({x_f, y_f, color.r, color.g, color.b});
|
||||
_vertices.push_back({x_f+w_f, y_f, color.r, color.g, color.b});
|
||||
|
||||
{ x_f, y_f + h_f },
|
||||
{ x_f + w_f, y_f },
|
||||
{x_f + w_f, y_f + h_f }
|
||||
};
|
||||
|
||||
glBindVertexArray(_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
glBindVertexArray(0);
|
||||
_vertices.push_back({x_f, y_f+h_f, color.r, color.g, color.b});
|
||||
_vertices.push_back({x_f+w_f, y_f, color.r, color.g, color.b});
|
||||
_vertices.push_back({x_f+w_f, y_f+h_f, color.r, color.g, color.b});
|
||||
}
|
||||
|
||||
void ShapeRenderer::draw_triangle(int x1, int y1, int x2, int y2, int x3,
|
||||
int y3, const Color& color) {
|
||||
_shape_shader->use();
|
||||
_shape_shader->set_vec3("objectColor", 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);
|
||||
_vertices.push_back({(float)x1, (float)y1, 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});
|
||||
}
|
||||
|
||||
@ -1,13 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "shader.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 {
|
||||
public:
|
||||
ShapeRenderer(unsigned int screen_width, unsigned int screen_height);
|
||||
~ShapeRenderer(void);
|
||||
|
||||
void begin(void);
|
||||
void flush(void);
|
||||
|
||||
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,
|
||||
const Color& color);
|
||||
@ -15,5 +28,6 @@ public:
|
||||
private:
|
||||
Shader* _shape_shader;
|
||||
unsigned int _vao, _vbo;
|
||||
std::vector<ShapeVertex> _vertices;
|
||||
float _projection[16];
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <freetype/freetype.h>
|
||||
|
||||
#include "txt_renderer.h"
|
||||
#include "debug/debug_stats.h"
|
||||
#include "math/math.h"
|
||||
|
||||
TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height) {
|
||||
@ -141,6 +142,9 @@ void TextRenderer::flush(void) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||
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());
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "ui/ui_renderer.h"
|
||||
#include "gfx/types.h"
|
||||
#include "ui/cursor_manager.h"
|
||||
#include "debug/debug_stats.h"
|
||||
|
||||
const int SCREEN_WIDTH = 1280;
|
||||
const int SCREEN_HEIGHT = 720;
|
||||
@ -101,6 +102,9 @@ int main(int argc, char** argv) {
|
||||
|
||||
bool running = true;
|
||||
while(running) {
|
||||
/* Reset per-frame stats. */
|
||||
DebugStats::reset();
|
||||
|
||||
/* Event handling. */
|
||||
SDL_Event event;
|
||||
while(SDL_PollEvent(&event)) {
|
||||
@ -116,8 +120,6 @@ int main(int argc, char** argv) {
|
||||
/* Clamp dt to avoid large jumps. */
|
||||
if(dt > 0.1f) dt = 0.1f;
|
||||
|
||||
game_state->update(dt);
|
||||
|
||||
Uint32 current_time = SDL_GetTicks();
|
||||
if(current_time - last_blink_time > 500) { /* Every 500ms. */
|
||||
show_cursor = !show_cursor;
|
||||
@ -135,6 +137,13 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
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. */
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
@ -60,9 +60,10 @@ Desktop::Desktop(int screen_width, int screen_height, ClientNetwork* network) {
|
||||
Desktop::~Desktop(void) {}
|
||||
|
||||
void Desktop::add_window(std::unique_ptr<UIWindow> window) {
|
||||
UIWindow* window_ptr = window.get();
|
||||
_windows.push_back(std::move(window));
|
||||
/* Focus new window. */
|
||||
_set_focused_window(_windows.back().get());
|
||||
_taskbar->add_window(window_ptr);
|
||||
_set_focused_window(window_ptr);
|
||||
}
|
||||
|
||||
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) {
|
||||
/* 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) {
|
||||
int mouse_x = event->button.x;
|
||||
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. */
|
||||
if(_launcher_is_open && !_launcher->is_point_inside(mouse_x, mouse_y, screen_height)) {
|
||||
_launcher_is_open = false;
|
||||
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;
|
||||
for(int i = _windows.size()-1; i >= 0; --i) {
|
||||
if(_windows[i].get()->is_point_inside(mouse_x, mouse_y)) {
|
||||
if(!_windows[i]->is_minimized()) {
|
||||
_set_focused_window(_windows[i].get());
|
||||
/* Top most window. Focus it. */
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Click wasn't on a window. Unfocus. */
|
||||
if(!click_on_window) {
|
||||
_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;
|
||||
}
|
||||
} 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 == _focused_window && !clicked_window->is_minimized()) {
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
@ -193,6 +205,7 @@ void Desktop::update(float dt, int screen_width, int screen_height) {
|
||||
_windows.erase(std::remove_if(_windows.begin(), _windows.end(),
|
||||
[this](const std::unique_ptr<UIWindow>& w) {
|
||||
if (w->should_close()) {
|
||||
_taskbar->remove_window(w.get());
|
||||
if (w.get() == _focused_window) {
|
||||
_focused_window = nullptr;
|
||||
}
|
||||
@ -243,7 +256,7 @@ void Desktop::render(const RenderContext& context) {
|
||||
if(_launcher_is_open) {
|
||||
_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();
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,9 @@ void Launcher::render(UIRenderer* ui_renderer) {
|
||||
const Color text_color = { 0.9f, 0.9f, 0.9f };
|
||||
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. */
|
||||
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);
|
||||
item_y += item_height;
|
||||
}
|
||||
|
||||
ui_renderer->flush_shapes();
|
||||
ui_renderer->flush_text();
|
||||
}
|
||||
|
||||
std::string Launcher::handle_event(SDL_Event* event, int screen_height) {
|
||||
|
||||
@ -101,6 +101,7 @@ void MainMenu::render(UIRenderer* ui_renderer) {
|
||||
ui_renderer->flush_text();
|
||||
|
||||
/* Pass 2: Buttons. */
|
||||
ui_renderer->begin_shapes();
|
||||
ui_renderer->begin_text();
|
||||
|
||||
/* 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->flush_shapes();
|
||||
ui_renderer->flush_text();
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
const Color bg_color = {0.15f, 0.17f, 0.19f};
|
||||
const Color text_color = {0.9f, 0.9f, 0.9f};
|
||||
const Color bg_color = { 0.15f, 0.17f, 0.19f };
|
||||
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);
|
||||
|
||||
@ -85,6 +88,9 @@ void MenuBar::render_bar(UIRenderer* ui_renderer, int x, int y, int 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 ) {
|
||||
@ -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 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 item_y = y + _height;
|
||||
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);
|
||||
item_y += item_height;
|
||||
}
|
||||
|
||||
ui_renderer->flush_shapes();
|
||||
ui_renderer->flush_text();
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
#include "taskbar.h"
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "taskbar.h"
|
||||
#include "ui/ui_renderer.h"
|
||||
#include "ui/ui_window.h"
|
||||
|
||||
@ -17,15 +18,28 @@ Taskbar::Taskbar(int screen_width, int screen_height) {
|
||||
|
||||
Taskbar::~Taskbar(void) {}
|
||||
|
||||
void Taskbar::render(UIRenderer* ui_renderer,
|
||||
const std::vector<std::unique_ptr<UIWindow>>& windows,
|
||||
UIWindow* focused_window) {
|
||||
void Taskbar::add_window(UIWindow* window) {
|
||||
_buttons.push_back({window, window->get_title()});
|
||||
}
|
||||
|
||||
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 button_color = { 0.2f, 0.22f, 0.24f };
|
||||
const Color button_focused_color = { 0.3f, 0.32f, 0.34f };
|
||||
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);
|
||||
|
||||
/* Draw start button. */
|
||||
@ -38,29 +52,31 @@ void Taskbar::render(UIRenderer* ui_renderer,
|
||||
int padding = 5;
|
||||
int x_offset = _start_button_width + padding;
|
||||
|
||||
for(const auto& window : windows) {
|
||||
bool is_focused = (window.get() == focused_window);
|
||||
for(const auto& button: _buttons) {
|
||||
bool is_focused = (button.window == focused_window);
|
||||
ui_renderer->draw_rect(x_offset, _y_pos + padding, button_width,
|
||||
_height - (padding * 2),
|
||||
is_focused ? button_focused_color : button_color);
|
||||
|
||||
/* 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);
|
||||
x_offset += button_width + padding;
|
||||
}
|
||||
/* Draw clock. */
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&in_time_t), "%H:%M");
|
||||
std::string time_str = ss.str();
|
||||
/* Draw clock. */
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&in_time_t), "%H:%M");
|
||||
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,
|
||||
const std::vector<std::unique_ptr<UIWindow>>& windows) {
|
||||
UIWindow* Taskbar::handle_event(SDL_Event* event, int screen_height) {
|
||||
if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
|
||||
int mouse_x = event->button.x;
|
||||
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 padding = 5;
|
||||
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 &&
|
||||
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;
|
||||
}
|
||||
@ -92,3 +108,7 @@ bool Taskbar::is_start_button_clicked(SDL_Event* event, int screen_height) {
|
||||
int Taskbar::get_height(void) const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
bool Taskbar::is_point_inside(int x, int y) const {
|
||||
return (y >= _y_pos && y<= _y_pos + _height);
|
||||
}
|
||||
|
||||
@ -1,27 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
class UIWindow;
|
||||
class UIRenderer;
|
||||
|
||||
struct TaskbarButton {
|
||||
UIWindow* window;
|
||||
std::string title;
|
||||
};
|
||||
|
||||
class Taskbar {
|
||||
public:
|
||||
Taskbar(int screen_width, int screen_height);
|
||||
~Taskbar(void);
|
||||
|
||||
void render(UIRenderer* ui_renderer,
|
||||
const std::vector<std::unique_ptr<UIWindow>>& windows,
|
||||
UIWindow* focused_window);
|
||||
void add_window(UIWindow* window);
|
||||
void remove_window(UIWindow* window);
|
||||
|
||||
UIWindow* handle_event(SDL_Event* event, int screen_height,
|
||||
const std::vector<std::unique_ptr<UIWindow>>& windows);
|
||||
void render(UIRenderer* ui_renderer, UIWindow* focused_window);
|
||||
UIWindow* handle_event(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;
|
||||
private:
|
||||
std::vector<TaskbarButton> _buttons;
|
||||
int _width;
|
||||
int _height;
|
||||
int _y_pos;
|
||||
|
||||
@ -38,6 +38,16 @@ void UIRenderer::flush_text(void) {
|
||||
_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) {
|
||||
return _txt_renderer;
|
||||
}
|
||||
|
||||
@ -20,6 +20,9 @@ public:
|
||||
void begin_text(void);
|
||||
void flush_text(void);
|
||||
|
||||
void begin_shapes(void);
|
||||
void flush_shapes(void);
|
||||
|
||||
/* Expose underlying text renderer for things like width calculation. */
|
||||
TextRenderer* get_text_renderer(void);
|
||||
|
||||
|
||||
@ -83,6 +83,7 @@ bool UIWindow::is_point_inside(int x, int y) {
|
||||
void UIWindow::render(const RenderContext& context) {
|
||||
int title_bar_height = 30;
|
||||
|
||||
context.ui_renderer->begin_shapes();
|
||||
context.ui_renderer->begin_text();
|
||||
|
||||
/* 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,
|
||||
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();
|
||||
|
||||
if(_content) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user