[Add] Graphical application launcher.

This commit is contained in:
Ritchie Cunningham 2025-09-28 20:39:11 +01:00
parent 996bf1c62c
commit 43907509eb
7 changed files with 178 additions and 51 deletions

View File

@ -15,7 +15,7 @@
#include <ui/boot_sequence.h>
void GameState::_init_desktop(void) {
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height);
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, _network.get());
auto term = std::make_unique<Terminal>(_network.get());
auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);

View File

@ -6,6 +6,7 @@
#include "desktop.h"
#include <SDL3/SDL_events.h>
#include "client_network.h"
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "terminal.h"
@ -22,9 +23,12 @@ static const std::string& get_random_snippet(const std::vector<std::string>& sni
return snippets[rand() % snippets.size()];
}
Desktop::Desktop(int screen_width, int screen_height) {
Desktop::Desktop(int screen_width, int screen_height, ClientNetwork* network) {
_taskbar = std::make_unique<Taskbar>(screen_width, screen_height);
_network = network;
_focused_window = nullptr;
_launcher_is_open = false;
_launcher = std::make_unique<Launcher>(5, 5 + _taskbar->get_height(), 200);
/* Load snippets for temp wallpaper. */
std::ifstream snippet_file("assets/menu_background_snippets.txt");
@ -50,11 +54,8 @@ Desktop::~Desktop(void) {}
void Desktop::add_window(std::unique_ptr<UIWindow> window) {
_windows.push_back(std::move(window));
/* I'm sick of reaching for my mouse to focus the terminal.. Stop that! */
if(_windows.size() == 1) {
_focused_window = _windows.back().get();
_focused_window->set_focused(true);
}
/* Focus new window. */
_set_focused_window(_windows.back().get());
}
void Desktop::_set_focused_window(UIWindow* window) {
@ -69,17 +70,6 @@ void Desktop::_set_focused_window(UIWindow* window) {
_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));
}
}
}
@ -88,6 +78,12 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height
int mouse_x = event->button.x;
int mouse_y = event->button.y;
/* 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;
}
/* 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)) {
@ -98,14 +94,27 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height
}
}
} 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);
if(_taskbar->is_start_button_clicked(event, screen_height)) {
_launcher_is_open = !_launcher_is_open;
} else if(_launcher_is_open) {
std::string app_to_launch = _launcher->handle_event(event, screen_height);
if(app_to_launch == "Terminal") {
auto term = std::make_unique<Terminal>(_network);
auto term_window = std::make_unique<UIWindow>("Terminal", 150, 150, 800, 500);
term_window->set_content(std::move(term));
add_window(std::move(term_window));
_launcher_is_open = false;
}
} else {
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) {
@ -154,12 +163,20 @@ UIWindow* Desktop::get_focused_window(void) {
void Desktop::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor) {
_render_wallpaper(txt_renderer);
if(_launcher_is_open) {
_launcher->render(shape_renderer, txt_renderer, screen_height);
}
_taskbar->render(shape_renderer, txt_renderer, _windows, _focused_window);
/* Render non-focused windows first. */
for(const auto& win : _windows) {
if(!win->is_minimized()) {
if(win.get() != _focused_window && !win->is_minimized()) {
win.get()->render(shape_renderer, txt_renderer, screen_height, show_cursor);
}
}
/* Render focused window last so it's on top. */
if(_focused_window && !_focused_window->is_minimized()) {
_focused_window->render(shape_renderer, txt_renderer, screen_height, show_cursor);
}
}
void Desktop::_render_wallpaper(TextRenderer* txt_renderer) {

View File

@ -4,13 +4,15 @@
#include <vector>
#include <SDL3/SDL.h>
#include "client_network.h"
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "ui/ui_window.h"
#include "ui/taskbar.h"
#include "ui/launcher.h"
/* Animated background stuff. */
struct ScollingText {
struct ScrollingText {
std::string text;
float x;
float y;
@ -19,7 +21,7 @@ struct ScollingText {
class Desktop {
public:
Desktop(int screen_width, int screen_height);
Desktop(int screen_width, int screen_height, ClientNetwork* network);
~Desktop(void);
void add_window(std::unique_ptr<UIWindow> window);
@ -35,8 +37,11 @@ private:
void _render_wallpaper(TextRenderer* txt_renderer);
std::vector<std::unique_ptr<UIWindow>> _windows;
std::unique_ptr<Taskbar> _taskbar;
UIWindow* _focused_window;
std::vector<ScollingText> _background_text;
std::vector<std::string> _snippets;
std::unique_ptr<Taskbar> _taskbar;
std::unique_ptr<Launcher> _launcher;
UIWindow* _focused_window;
ClientNetwork* _network;
std::vector<ScrollingText> _background_text;
std::vector<std::string> _snippets;
bool _launcher_is_open;
};

View File

@ -0,0 +1,66 @@
#include "launcher.h"
#include <SDL3/SDL_mouse.h>
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "gfx/types.h"
Launcher::Launcher(int x, int y, int width) : _x(x), _y(y), _width(width) {
/* TODO: Hardcode the launcher apps for now. */
_apps.push_back("Terminal");
int item_height = 30;
_height = _apps.size() * item_height;
}
Launcher::~Launcher(void) {}
bool Launcher::is_point_inside(int x, int y, int screen_height) const {
int ui_y = screen_height - y; /* convert mouse y to GL/UI coords. */
return (x >= _x && x <= _x + _width && ui_y >= _y && ui_y <= _y + _height);
}
void Launcher::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height) {
const Color bg_color = { 0.15f, 0.17f, 0.19f };
const Color text_color = { 0.9f, 0.9f, 0.9f };
const Color hover_color = { 0.3f, 0.32f, 0.34f };
/* Note: y-coord is TOP of launcher menu. */
shape_renderer->draw_rect(_x, _y, _width, _height, bg_color);
int item_height = 30;
int item_y = _y;
float mouse_x, mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
int ui_mouse_y = screen_height - mouse_y;
for(const auto& app_name : _apps) {
/* Check for hover. */
if(mouse_x >= _x && mouse_x <= _x + _width &&
ui_mouse_y >= item_y && ui_mouse_y <= item_y + item_height) {
shape_renderer->draw_rect(_x, item_y, _width, item_height, hover_color);
}
txt_renderer->render_text(app_name.c_str(), _x + 10, item_y + 8, 1.0f, text_color);
item_y += item_height;
}
}
std::string Launcher::handle_event(SDL_Event* event, int screen_height) {
if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
int mouse_x = event->button.x;
int ui_mouse_y = screen_height - event->button.y;
int item_height = 30;
int item_y = _y;
for(const auto& app_name : _apps) {
if(mouse_x >= _x && mouse_x <= _x + _width &&
ui_mouse_y >= item_y && ui_mouse_y <= item_y + item_height) {
return app_name; /* Return name of clicked app. */
}
item_y += item_height;
}
}
return ""; /* Nothing clicked. */
}

22
client/src/ui/launcher.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <string>
#include <vector>
#include <SDL3/SDL_events.h>
class ShapeRenderer;
class TextRenderer;
class Launcher {
public:
Launcher(int x, int y, int width);
~Launcher(void);
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height);
std::string handle_event(SDL_Event* event, int screen_height);
bool is_point_inside(int x, int y, int screen_height) const;
private:
int _x, _y, _width, _height;
std::vector<std::string> _apps;
};

View File

@ -13,7 +13,8 @@
Taskbar::Taskbar(int screen_width, int screen_height) {
_width = screen_width;
_height = 32;
_y_pos = 0; /* Taksbar at bottom because boring? */
_y_pos = 0; /* Taskbar at bottom because boring? */
_start_button_width = 60;
}
Taskbar::~Taskbar(void) {}
@ -27,24 +28,30 @@ void Taskbar::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
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);
/* Draw start button. */
shape_renderer->draw_rect(5, _y_pos + 5, _start_button_width - 10,
_height - 10, button_color);
txt_renderer->render_text("[B]", 20, _y_pos + 11, 1.0f,
button_text_color);
/* Draw app buttons. */
int button_width = 150;
int padding = 5;
int x_offset = padding;
int x_offset = _start_button_width + padding;
for(const auto& window : windows) {
bool is_focused = (window.get() == focused_window);
shape_renderer->draw_rect(x_offset, _y_pos + padding, button_width,
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,
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;
}
/* Draw clock. */
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
@ -52,9 +59,8 @@ void Taskbar::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
ss << std::put_time(std::localtime(&in_time_t), "%H:%M");
std::string time_str = ss.str();
txt_renderer->render_text(time_str.c_str(), _width-50, _y_pos+11, 1.0f,
txt_renderer->render_text(time_str.c_str(), _width-50, _y_pos+11, 1.0f,
button_text_color);
}
}
UIWindow* Taskbar::handle_event(SDL_Event* event, int screen_height,
@ -63,21 +69,30 @@ UIWindow* Taskbar::handle_event(SDL_Event* event, int screen_height,
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;
int button_width = 150;
int padding = 5;
int x_offset = _start_button_width + padding;
for(const auto& window : windows) {
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. */
}
x_offset += button_width + padding;
}
}
return nullptr; /* No window button was clicked. */
}
bool Taskbar::is_start_button_clicked(SDL_Event* event, int screen_height) {
if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
int mouse_x = event->button.x;
int mouse_y = screen_height - event->button.y;
return (mouse_x >= 0 && mouse_x <= _start_button_width && mouse_y >= _y_pos &&
mouse_y <= _y_pos + _height);
}
return false;
}
int Taskbar::get_height(void) const {
return _height;
}

View File

@ -18,11 +18,13 @@ public:
UIWindow* focused_window);
UIWindow* handle_event(SDL_Event* event, int screen_height,
const std::vector<std::unique_ptr<UIWindow>>& windows);
const std::vector<std::unique_ptr<UIWindow>>& windows);
bool is_start_button_clicked(SDL_Event* event, int screen_height);
int get_height(void) const;
private:
int _width;
int _height;
int _y_pos;
int _start_button_width;
};