[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,7 +27,7 @@ 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:
 | 
			
		||||
@ -34,6 +35,8 @@ private:
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,6 +76,9 @@ 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  };
 | 
			
		||||
 | 
			
		||||
  ui_renderer->begin_shapes();
 | 
			
		||||
  ui_renderer->begin_text();
 | 
			
		||||
 | 
			
		||||
  ui_renderer->draw_rect(x, y, width, _height, bg_color);
 | 
			
		||||
 | 
			
		||||
  int menu_x = x;
 | 
			
		||||
@ -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,14 +52,14 @@ 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;
 | 
			
		||||
  }
 | 
			
		||||
@ -57,10 +71,12 @@ void Taskbar::render(UIRenderer* ui_renderer,
 | 
			
		||||
  std::string time_str = ss.str();
 | 
			
		||||
 | 
			
		||||
  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