[Add] Taskbar and window management work
- Adds a taskbar that displays and manages open application windows - close, minimise and restore functionality - resizeable windows - Refactored desktop event handling to manage window focus and render order
This commit is contained in:
		
							parent
							
								
									6876cbff95
								
							
						
					
					
						commit
						ea25cb6cc7
					
				@ -15,7 +15,7 @@
 | 
			
		||||
#include <ui/boot_sequence.h>
 | 
			
		||||
 | 
			
		||||
void GameState::_init_desktop(void) {
 | 
			
		||||
  _desktop = std::make_unique<Desktop>();
 | 
			
		||||
  _desktop = std::make_unique<Desktop>(_screen_width, _screen_height);
 | 
			
		||||
 | 
			
		||||
  auto term = std::make_unique<Terminal>(_network.get());
 | 
			
		||||
  auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
 | 
			
		||||
@ -40,11 +40,16 @@ void GameState::_run_server(void) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GameState::GameState(void) : _current_screen(Screen::MAIN_MENU) {}
 | 
			
		||||
GameState::GameState(void) :
 | 
			
		||||
    _current_screen(Screen::MAIN_MENU),
 | 
			
		||||
    _screen_width(0),
 | 
			
		||||
    _screen_height(0) {}
 | 
			
		||||
 | 
			
		||||
GameState::~GameState(void) = default;
 | 
			
		||||
 | 
			
		||||
void GameState::init(int screen_width, int screen_height) {
 | 
			
		||||
  _screen_width   = screen_width;
 | 
			
		||||
  _screen_height  = screen_height;
 | 
			
		||||
  /* Create and connect the network client. */
 | 
			
		||||
  _network = std::make_unique<ClientNetwork>();
 | 
			
		||||
 | 
			
		||||
@ -77,7 +82,7 @@ void GameState::handle_event(SDL_Event* event) {
 | 
			
		||||
    break;
 | 
			
		||||
  case Screen::DESKTOP:
 | 
			
		||||
    if(_desktop) {
 | 
			
		||||
      _desktop->handle_event(event);
 | 
			
		||||
      _desktop->handle_event(event, _screen_height);
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,8 @@ private:
 | 
			
		||||
  std::unique_ptr<BootSequence>  _boot_sequence;
 | 
			
		||||
  std::unique_ptr<MainMenu>      _main_menu;
 | 
			
		||||
  Screen                         _current_screen;
 | 
			
		||||
  int                            _screen_width;
 | 
			
		||||
  int                            _screen_height;
 | 
			
		||||
 | 
			
		||||
  void _init_desktop(void);
 | 
			
		||||
  void _run_server(void);
 | 
			
		||||
 | 
			
		||||
@ -54,3 +54,25 @@ void ShapeRenderer::draw_rect(int x, int y, int width, int height, const Color&
 | 
			
		||||
  glDrawArrays(GL_TRIANGLES, 0, 6);
 | 
			
		||||
  glBindVertexArray(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,8 @@ public:
 | 
			
		||||
  ~ShapeRenderer(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);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  Shader* _shape_shader;
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@
 | 
			
		||||
#include "gfx/shape_renderer.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "game_state.h"
 | 
			
		||||
#include "ui/cursor_manager.h"
 | 
			
		||||
 | 
			
		||||
const int SCREEN_WIDTH  = 1280;
 | 
			
		||||
const int SCREEN_HEIGHT = 720;
 | 
			
		||||
@ -66,6 +67,9 @@ int main(int argc, char** argv) {
 | 
			
		||||
  /* Listen for text input. */
 | 
			
		||||
  SDL_StartTextInput(window);
 | 
			
		||||
 | 
			
		||||
  /* Init cursor manager. */
 | 
			
		||||
  CursorManager::init();
 | 
			
		||||
 | 
			
		||||
  /* Init text renderer. */
 | 
			
		||||
  TextRenderer* txt_render_instance = new TextRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
 | 
			
		||||
  txt_render_instance->load_font("assets/fonts/hack/Hack-Regular.ttf", 14);
 | 
			
		||||
@ -116,6 +120,7 @@ int main(int argc, char** argv) {
 | 
			
		||||
  delete shape_renderer_instance;
 | 
			
		||||
  delete txt_render_instance;
 | 
			
		||||
  SDL_GL_DestroyContext(context);
 | 
			
		||||
  CursorManager::quit();
 | 
			
		||||
  SDL_DestroyWindow(window);
 | 
			
		||||
  SDL_Quit();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								client/src/ui/cursor_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								client/src/ui/cursor_manager.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
#include "cursor_manager.h"
 | 
			
		||||
#include <SDL3/SDL_error.h>
 | 
			
		||||
#include <SDL3/SDL_mouse.h>
 | 
			
		||||
 | 
			
		||||
SDL_Cursor* CursorManager::_arrow_cursor  = nullptr;
 | 
			
		||||
SDL_Cursor* CursorManager::_resize_cursor = nullptr;
 | 
			
		||||
 | 
			
		||||
void CursorManager::init(void) {
 | 
			
		||||
  _arrow_cursor   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
 | 
			
		||||
  _resize_cursor  = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE);
 | 
			
		||||
 | 
			
		||||
  if(!_arrow_cursor || !_resize_cursor) {
 | 
			
		||||
    printf("Failed to create system cursors! SDL_Error: %s\n", SDL_GetError());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CursorManager::set_cursor(CursorType type) {
 | 
			
		||||
  SDL_Cursor* cursor_to_set = _arrow_cursor;
 | 
			
		||||
  switch(type) {
 | 
			
		||||
  case CursorType::ARROW:
 | 
			
		||||
    cursor_to_set = _arrow_cursor;
 | 
			
		||||
    break;
 | 
			
		||||
  case CursorType::RESIZE_NWSE:
 | 
			
		||||
    cursor_to_set = _resize_cursor;
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if(SDL_GetCursor() != cursor_to_set) {
 | 
			
		||||
    SDL_SetCursor(cursor_to_set);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CursorManager::quit(void) {
 | 
			
		||||
  SDL_DestroyCursor(_arrow_cursor);
 | 
			
		||||
  SDL_DestroyCursor(_resize_cursor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								client/src/ui/cursor_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								client/src/ui/cursor_manager.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <SDL3/SDL_mouse.h>
 | 
			
		||||
 | 
			
		||||
enum class CursorType {
 | 
			
		||||
  ARROW,
 | 
			
		||||
  RESIZE_NWSE /* Diagonal resize arrow. */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CursorManager {
 | 
			
		||||
public:
 | 
			
		||||
  static void init(void);
 | 
			
		||||
  static void set_cursor(CursorType type);
 | 
			
		||||
  static void quit(void);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  static SDL_Cursor* _arrow_cursor;
 | 
			
		||||
  static SDL_Cursor* _resize_cursor;
 | 
			
		||||
};
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <ctime>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#include "desktop.h"
 | 
			
		||||
@ -6,9 +7,13 @@
 | 
			
		||||
#include "gfx/shape_renderer.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "terminal.h"
 | 
			
		||||
#include "ui/taskbar.h"
 | 
			
		||||
#include <SDL3/SDL_video.h>
 | 
			
		||||
#include <ui/cursor_manager.h>
 | 
			
		||||
#include "ui/ui_window.h"
 | 
			
		||||
 | 
			
		||||
Desktop::Desktop(void) {
 | 
			
		||||
Desktop::Desktop(int screen_width, int screen_height) {
 | 
			
		||||
_taskbar = std::make_unique<Taskbar>(screen_width, screen_height);
 | 
			
		||||
  _focused_window = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -24,7 +29,33 @@ void Desktop::add_window(std::unique_ptr<UIWindow> window) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Desktop::handle_event(SDL_Event* event) {
 | 
			
		||||
void Desktop::_set_focused_window(UIWindow* window) {
 | 
			
		||||
  if(window == _focused_window) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if(_focused_window) {
 | 
			
		||||
    _focused_window->set_focused(false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _focused_window = window;
 | 
			
		||||
  if(_focused_window) {
 | 
			
		||||
    _focused_window->set_focused(true);
 | 
			
		||||
 | 
			
		||||
    /* Find window in vector and move it to the end. */
 | 
			
		||||
    auto it = std::find_if(_windows.begin(), _windows.end(),
 | 
			
		||||
                           [window](const std::unique_ptr<UIWindow>& w)
 | 
			
		||||
                           { return w.get() == window; });
 | 
			
		||||
 | 
			
		||||
    if(it != _windows.end()) {
 | 
			
		||||
      auto window_to_move = std::move(*it);
 | 
			
		||||
      _windows.erase(it);
 | 
			
		||||
      _windows.push_back(std::move(window_to_move));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Desktop::handle_event(SDL_Event* event, int screen_height) {
 | 
			
		||||
  if(event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
 | 
			
		||||
    int mouse_x = event->button.x;
 | 
			
		||||
    int mouse_y = event->button.y;
 | 
			
		||||
@ -32,22 +63,35 @@ void Desktop::handle_event(SDL_Event* event) {
 | 
			
		||||
    /* Find the top-most window that was clicked. */
 | 
			
		||||
    for(int i = _windows.size()-1; i >= 0; --i) {
 | 
			
		||||
      if(_windows[i].get()->is_point_inside(mouse_x, mouse_y)) {
 | 
			
		||||
        /* If not focused, focus it. */
 | 
			
		||||
        if(_windows[i].get() != _focused_window) {
 | 
			
		||||
          if(_focused_window) {
 | 
			
		||||
            _focused_window->set_focused(false);
 | 
			
		||||
          }
 | 
			
		||||
          _focused_window = _windows[i].get();
 | 
			
		||||
          _focused_window->set_focused(true);
 | 
			
		||||
 | 
			
		||||
          /* Move window to the front. */
 | 
			
		||||
          auto window_to_move = std::move(_windows[i]);
 | 
			
		||||
          _windows.erase(_windows.begin() + i);
 | 
			
		||||
          _windows.push_back(std::move(window_to_move));
 | 
			
		||||
        if(!_windows[i]->is_minimized()) {
 | 
			
		||||
          _set_focused_window(_windows[i].get());
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
 | 
			
		||||
    UIWindow* clicked_window = _taskbar->handle_event(event, screen_height, _windows);
 | 
			
		||||
    if(clicked_window) {
 | 
			
		||||
      if(clicked_window == _focused_window && !clicked_window->is_minimized()) {
 | 
			
		||||
        clicked_window->minimize();
 | 
			
		||||
        _set_focused_window(nullptr);
 | 
			
		||||
      } else {
 | 
			
		||||
        clicked_window->restore();
 | 
			
		||||
        _set_focused_window(clicked_window);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else if(event->type == SDL_EVENT_MOUSE_MOTION) {
 | 
			
		||||
    bool on_resize_handle = false;
 | 
			
		||||
    /* Iterate backwards since top-most windows are at the end. */
 | 
			
		||||
    for(int i = _windows.size() - 1; i >= 0; --i) {
 | 
			
		||||
      if(_windows[i]->is_mouse_over_resize_handle(event->motion.x, 
 | 
			
		||||
                                                  event->motion.y)) {
 | 
			
		||||
        on_resize_handle = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    CursorManager::set_cursor(on_resize_handle ? CursorType::RESIZE_NWSE
 | 
			
		||||
                                               : CursorType::ARROW);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if(_focused_window) {
 | 
			
		||||
@ -61,12 +105,10 @@ void Desktop::handle_event(SDL_Event* event) {
 | 
			
		||||
 | 
			
		||||
void Desktop::update(void) {
 | 
			
		||||
  /* Remove closed windows. */
 | 
			
		||||
  _windows.erase(std::remove_if(_windows.begin(), _windows.end(),
 | 
			
		||||
                                [](const std::unique_ptr<UIWindow>& window) {
 | 
			
		||||
                                  Terminal* term = window->get_content();
 | 
			
		||||
                                  return term && term->close();
 | 
			
		||||
                                }),
 | 
			
		||||
                              _windows.end());
 | 
			
		||||
  _windows.erase(
 | 
			
		||||
    std::remove_if(_windows.begin(), _windows.end(),
 | 
			
		||||
      [](const std::unique_ptr<UIWindow>& w) { return w->should_close(); }),
 | 
			
		||||
  _windows.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
UIWindow* Desktop::get_focused_window(void) {
 | 
			
		||||
@ -75,7 +117,11 @@ UIWindow* Desktop::get_focused_window(void) {
 | 
			
		||||
 | 
			
		||||
void Desktop::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
                     int screen_height, bool show_cursor) {
 | 
			
		||||
  /* TODO: Render wallpaper here. */
 | 
			
		||||
  _taskbar->render(shape_renderer, txt_renderer, _windows, _focused_window);
 | 
			
		||||
  for(const auto& win : _windows) {
 | 
			
		||||
    win.get()->render(shape_renderer, txt_renderer, screen_height, show_cursor);
 | 
			
		||||
    if(!win->is_minimized()) {
 | 
			
		||||
      win.get()->render(shape_renderer, txt_renderer, screen_height, show_cursor);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,14 +7,15 @@
 | 
			
		||||
#include "gfx/shape_renderer.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "ui/ui_window.h"
 | 
			
		||||
#include "ui/taskbar.h"
 | 
			
		||||
 | 
			
		||||
class Desktop {
 | 
			
		||||
public:
 | 
			
		||||
  Desktop(void);
 | 
			
		||||
  Desktop(int screen_width, int screen_height);
 | 
			
		||||
  ~Desktop(void);
 | 
			
		||||
 | 
			
		||||
  void add_window(std::unique_ptr<UIWindow> window);
 | 
			
		||||
  void handle_event(SDL_Event* event);
 | 
			
		||||
  void handle_event(SDL_Event* event, int screen_height);
 | 
			
		||||
  void update(void);
 | 
			
		||||
  void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height,
 | 
			
		||||
              bool show_cursor);
 | 
			
		||||
@ -22,6 +23,8 @@ public:
 | 
			
		||||
  UIWindow* get_focused_window(void);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  void _set_focused_window(UIWindow* window);
 | 
			
		||||
  std::vector<std::unique_ptr<UIWindow>> _windows;
 | 
			
		||||
  std::unique_ptr<Taskbar> _taskbar;
 | 
			
		||||
  UIWindow* _focused_window;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										68
									
								
								client/src/ui/taskbar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								client/src/ui/taskbar.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
#include "taskbar.h"
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "gfx/shape_renderer.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "gfx/types.h"
 | 
			
		||||
#include "ui/ui_window.h"
 | 
			
		||||
 | 
			
		||||
Taskbar::Taskbar(int screen_width, int screen_height) {
 | 
			
		||||
  _width  = screen_width;
 | 
			
		||||
  _height = 32;
 | 
			
		||||
  _y_pos  = 0; /* Taksbar at bottom because boring? */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Taskbar::~Taskbar(void) {}
 | 
			
		||||
 | 
			
		||||
void Taskbar::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
                     const std::vector<std::unique_ptr<UIWindow>>& windows,
 | 
			
		||||
                     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  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  shape_renderer->draw_rect(0, _y_pos, _width, _height, taskbar_color);
 | 
			
		||||
 | 
			
		||||
  int button_width  = 150;
 | 
			
		||||
  int padding       = 5;
 | 
			
		||||
  int x_offset      = padding;
 | 
			
		||||
 | 
			
		||||
  for(const auto& window : windows) {
 | 
			
		||||
    bool is_focused = (window.get() == focused_window);
 | 
			
		||||
    shape_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. */
 | 
			
		||||
    txt_renderer->render_text(window->get_title().c_str(), x_offset + 10, 
 | 
			
		||||
                              _y_pos + 11, 1.0f, button_text_color);
 | 
			
		||||
    x_offset += button_width + padding;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    int mouse_x = event->button.x;
 | 
			
		||||
    int mouse_y = screen_height - event->button.y; /* Convert to UI coords. */
 | 
			
		||||
 | 
			
		||||
    if(mouse_y >= _y_pos && mouse_y <= _y_pos + _height) {
 | 
			
		||||
      int button_width  = 150;
 | 
			
		||||
      int padding       = 5;
 | 
			
		||||
      int x_offset      = padding;
 | 
			
		||||
      for(auto& window : windows) {
 | 
			
		||||
        if(mouse_x >= x_offset && mouse_x <= x_offset + button_width) {
 | 
			
		||||
          return window.get(); /* Return clicked window. */
 | 
			
		||||
        }
 | 
			
		||||
        x_offset += button_width + padding;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr; /* No window button was clicked. */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Taskbar::get_height(void) const {
 | 
			
		||||
  return _height;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								client/src/ui/taskbar.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								client/src/ui/taskbar.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <SDL3/SDL_events.h>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
class UIWindow;
 | 
			
		||||
class ShapeRenderer;
 | 
			
		||||
class TextRenderer;
 | 
			
		||||
 | 
			
		||||
class Taskbar {
 | 
			
		||||
public:
 | 
			
		||||
  Taskbar(int screen_width, int screen_height);
 | 
			
		||||
  ~Taskbar(void);
 | 
			
		||||
 | 
			
		||||
  void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
              const std::vector<std::unique_ptr<UIWindow>>& windows,
 | 
			
		||||
              UIWindow* focused_window);
 | 
			
		||||
 | 
			
		||||
  UIWindow* handle_event(SDL_Event* event, int screen_height,
 | 
			
		||||
                    const std::vector<std::unique_ptr<UIWindow>>& windows);
 | 
			
		||||
 | 
			
		||||
  int get_height(void) const;
 | 
			
		||||
private:
 | 
			
		||||
  int _width;
 | 
			
		||||
  int _height;
 | 
			
		||||
  int _y_pos;
 | 
			
		||||
};
 | 
			
		||||
@ -5,20 +5,49 @@
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "gfx/types.h"
 | 
			
		||||
 | 
			
		||||
UIWindow::UIWindow(const char* title, int x, int y, int width, int height) {
 | 
			
		||||
  _title = title;
 | 
			
		||||
  _x = x;
 | 
			
		||||
  _y = y;
 | 
			
		||||
  _width        = width;
 | 
			
		||||
  _height       = height;
 | 
			
		||||
  _content      = nullptr;
 | 
			
		||||
  _is_dragging  = false;
 | 
			
		||||
  _is_hovered   = false;
 | 
			
		||||
  _is_focused   = false; /* Not focused by default? */
 | 
			
		||||
}
 | 
			
		||||
UIWindow::UIWindow(const char* title, int x, int y, int width, int height) :
 | 
			
		||||
    _title(title),
 | 
			
		||||
    _x(x),
 | 
			
		||||
    _y(y),
 | 
			
		||||
    _width(width),
 | 
			
		||||
    _height(height),
 | 
			
		||||
    _content(nullptr),
 | 
			
		||||
    _is_dragging(false),
 | 
			
		||||
    _is_hovered(false),
 | 
			
		||||
    _is_focused(false),
 | 
			
		||||
    _should_close(false),
 | 
			
		||||
    _state(WindowState::NORMAL),
 | 
			
		||||
    _is_resizing(false),
 | 
			
		||||
    _resize_margin(10) {}
 | 
			
		||||
 | 
			
		||||
UIWindow::~UIWindow(void) {}
 | 
			
		||||
 | 
			
		||||
void UIWindow::minimize(void) {
 | 
			
		||||
  _state = WindowState::MINIMIZED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UIWindow::restore(void) {
 | 
			
		||||
  _state = WindowState::NORMAL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UIWindow::is_minimized(void) const {
 | 
			
		||||
  return _state == WindowState::MINIMIZED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::string& UIWindow::get_title(void) const {
 | 
			
		||||
  return _title;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UIWindow::is_mouse_over_resize_handle(int mouse_x, int mouse_y) const {
 | 
			
		||||
  return (mouse_x >= _x + _width  - _resize_margin && mouse_x <= _x + _width &&
 | 
			
		||||
          mouse_y >= _y + _height - _resize_margin &&
 | 
			
		||||
          mouse_y <= _y + _height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UIWindow::should_close(void) const {
 | 
			
		||||
  return _should_close;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UIWindow::set_content(std::unique_ptr<Terminal> term) {
 | 
			
		||||
  _content = std::move(term);
 | 
			
		||||
}
 | 
			
		||||
@ -45,6 +74,9 @@ void UIWindow::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
  const Color title_bar_color         = { 0.15f, 0.15f, 0.2f  };
 | 
			
		||||
  const Color focused_title_bar_color = { 0.3f,  0.3f,  0.4f  };
 | 
			
		||||
  const Color title_text_color        = { 0.9f,  0.9f,  0.9f  };
 | 
			
		||||
  const Color close_button_color      = { 0.8f,  0.2f,  0.2f  };
 | 
			
		||||
  const Color minimize_button_color   = { 0.8f,  0.8f,  0.2f  };
 | 
			
		||||
  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;
 | 
			
		||||
@ -65,6 +97,21 @@ void UIWindow::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
  txt_renderer->render_text(_title.c_str(), _x+5, y_gl+_height-title_bar_height+8,
 | 
			
		||||
                            1.0f, title_text_color);
 | 
			
		||||
 | 
			
		||||
  /* Draw close button. */
 | 
			
		||||
  shape_renderer->draw_rect(_x + _width - 25, y_gl + _height - title_bar_height + 5, 
 | 
			
		||||
                            20, 20, close_button_color);
 | 
			
		||||
 | 
			
		||||
  /* Draw minimize button. */
 | 
			
		||||
  shape_renderer->draw_rect(_x + _width - 50, 
 | 
			
		||||
                            y_gl + _height - title_bar_height + 5, 20, 20, 
 | 
			
		||||
                            minimize_button_color);
 | 
			
		||||
 | 
			
		||||
  /* 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,
 | 
			
		||||
                                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;
 | 
			
		||||
@ -79,19 +126,46 @@ void UIWindow::handle_event(SDL_Event* event) {
 | 
			
		||||
    int mouse_x = event->button.x;
 | 
			
		||||
    int mouse_y = event->button.y;
 | 
			
		||||
 | 
			
		||||
    /* Is click within title bar? */
 | 
			
		||||
    if(mouse_x >= _x && mouse_x <= _x + _width &&
 | 
			
		||||
    /* Check for close button click. */
 | 
			
		||||
    int close_button_x = _x + _width - 25;
 | 
			
		||||
    int close_button_y = _y + 5;
 | 
			
		||||
    if(mouse_x >= close_button_x && mouse_x <= close_button_x + 20 &&
 | 
			
		||||
       mouse_y >= close_button_y && mouse_y <= close_button_y + 20) {
 | 
			
		||||
      _should_close = true;
 | 
			
		||||
      return; /* Stop processing this event. */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Check for minimize button click. */
 | 
			
		||||
    int minimize_button_x = _x + _width - 50;
 | 
			
		||||
    int minimize_button_y = _y + 5;
 | 
			
		||||
    if(mouse_x >= minimize_button_x && mouse_x <= minimize_button_x + 20 &&
 | 
			
		||||
       mouse_y >= minimize_button_y && mouse_y <= minimize_button_y + 20) {
 | 
			
		||||
      minimize();
 | 
			
		||||
      return; /* Stop processing this event. */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Check for resize handle click (bottom-right corner). */
 | 
			
		||||
    if(is_mouse_over_resize_handle(mouse_x, mouse_y)) {
 | 
			
		||||
      _is_resizing = true;
 | 
			
		||||
    } else if(mouse_x >= _x && mouse_x <= _x + _width &&
 | 
			
		||||
       mouse_y >= _y && mouse_y <= _y + title_bar_height) {
 | 
			
		||||
      /* Is click within title bar? */
 | 
			
		||||
      _is_dragging = true;
 | 
			
		||||
      _drag_offset_x = mouse_x - _x;
 | 
			
		||||
      _drag_offset_y = mouse_y - _y;
 | 
			
		||||
    }
 | 
			
		||||
  } else if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
 | 
			
		||||
    _is_dragging = false;
 | 
			
		||||
    _is_resizing = false;
 | 
			
		||||
  } else if(event->type == SDL_EVENT_MOUSE_MOTION) {
 | 
			
		||||
    if(_is_dragging) {
 | 
			
		||||
      _x = event->motion.x - _drag_offset_x;
 | 
			
		||||
      _y = event->motion.y - _drag_offset_y;
 | 
			
		||||
    } else if(_is_resizing) {
 | 
			
		||||
      int new_width   = event->motion.x - _x;
 | 
			
		||||
      int new_height  = event->motion.y - _y;
 | 
			
		||||
      _width  = (new_width  > 100) ? new_width  : 100; /* Min width.  */
 | 
			
		||||
      _height = (new_height >  80) ? new_height :  80; /* Min height. */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Check if mouse hovered over window. */
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,12 @@
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "terminal.h"
 | 
			
		||||
 | 
			
		||||
enum class WindowState {
 | 
			
		||||
  NORMAL,
 | 
			
		||||
  MINIMIZED,
 | 
			
		||||
  MAXIMIZED
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UIWindow {
 | 
			
		||||
public:
 | 
			
		||||
  UIWindow(const char* title, int x, int y, int width, int height);
 | 
			
		||||
@ -15,19 +21,32 @@ public:
 | 
			
		||||
  void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height,
 | 
			
		||||
              bool show_cursor);
 | 
			
		||||
  void handle_event(SDL_Event* event);
 | 
			
		||||
  void minimize(void);
 | 
			
		||||
  void restore(void);
 | 
			
		||||
  bool is_minimized(void) const;
 | 
			
		||||
  bool should_close(void) const;
 | 
			
		||||
  void set_focused(bool focused);
 | 
			
		||||
  bool is_point_inside(int x, int y);
 | 
			
		||||
  void set_content(std::unique_ptr<Terminal> term);
 | 
			
		||||
  Terminal* get_content(void);
 | 
			
		||||
  bool is_mouse_over_resize_handle(int mouse_x, int mouse_y) const;
 | 
			
		||||
  const std::string& get_title(void) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  friend class Taskbar; /* Allow taskbar to access private members. */
 | 
			
		||||
  int _x, _y, _width, _height;
 | 
			
		||||
  Rect _pre_maximize_rect;
 | 
			
		||||
  std::string _title;
 | 
			
		||||
  std::unique_ptr<Terminal> _content;
 | 
			
		||||
  bool _is_focused; /* Managed by desktop. */
 | 
			
		||||
  bool _is_hovered; /* Send scroll events even if not focused. */
 | 
			
		||||
  bool _should_close;
 | 
			
		||||
 | 
			
		||||
  WindowState _state;
 | 
			
		||||
 | 
			
		||||
  bool _is_dragging;
 | 
			
		||||
  int _drag_offset_x, _drag_offset_y;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  bool _is_resizing;
 | 
			
		||||
  int _resize_margin;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user