319 lines
11 KiB
C++
319 lines
11 KiB
C++
#include <algorithm>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <ui/cursor_manager.h>
|
|
#include <SDL3/SDL_events.h>
|
|
#include <SDL3/SDL_video.h>
|
|
|
|
#include "gfx/types.h"
|
|
#include "game_state.h"
|
|
#include "desktop.h"
|
|
#include "terminal.h"
|
|
#include "ui/i_window_content.h"
|
|
#include "ui/launcher.h"
|
|
#include "ui/taskbar.h"
|
|
#include "ui/ui_window.h"
|
|
#include "ui/editor.h"
|
|
#include "ui/window_action.h"
|
|
|
|
static const std::string& get_random_snippet(const std::vector<std::string>& snippets) {
|
|
if(snippets.empty()) {
|
|
static const std::string err = "ERR";
|
|
return err;
|
|
}
|
|
return snippets[rand() % snippets.size()];
|
|
}
|
|
|
|
Desktop::Desktop(int screen_width, int screen_height, GameState* game_state,
|
|
uint32_t initial_session_id) {
|
|
_taskbar = std::make_unique<Taskbar>(screen_width, screen_height);
|
|
_initial_session_id = initial_session_id;
|
|
_game_state= game_state;
|
|
_focused_window = nullptr;
|
|
_window_awaiting_session_id = nullptr;
|
|
_launcher_is_open = false;
|
|
_launcher = std::make_unique<Launcher>(5, 0, 200); /* Tmp y-coord. */
|
|
int launcher_y = screen_height - _taskbar->get_height() - _launcher->get_height();
|
|
_launcher->set_y(launcher_y);
|
|
|
|
/* Load snippets for temp wallpaper. */
|
|
std::ifstream snippet_file("assets/menu_background_snippets.txt");
|
|
if(snippet_file.is_open()) {
|
|
std::string line;
|
|
while(std::getline(snippet_file, line)) {
|
|
if(!line.empty()) { _snippets.push_back(line); }
|
|
}
|
|
}
|
|
|
|
/* Init animated background. */
|
|
for(int i = 0; i < 100; ++i) {
|
|
float y_pos = (float)(rand() % screen_height);
|
|
_background_text.push_back(
|
|
{get_random_snippet(_snippets), (float)(rand() % screen_width),
|
|
y_pos,
|
|
(float)(rand() % 30 + 10) / 100.0f,
|
|
(int)y_pos
|
|
});
|
|
}
|
|
}
|
|
|
|
/* Desktop owns UIWindow, make sure we delete them. */
|
|
Desktop::~Desktop(void) {}
|
|
|
|
void Desktop::add_window(std::unique_ptr<UIWindow> window) {
|
|
UIWindow* window_ptr = window.get();
|
|
_windows.push_back(std::move(window));
|
|
_taskbar->add_window(window_ptr);
|
|
_set_focused_window(window_ptr);
|
|
}
|
|
|
|
void Desktop::_set_focused_window(UIWindow* window) {
|
|
if(window == _focused_window) {
|
|
return;
|
|
}
|
|
|
|
/* Unfocus old window. */
|
|
if(_focused_window) {
|
|
_focused_window->set_focused(false);
|
|
}
|
|
|
|
/* Set new focused window. */
|
|
_focused_window = window;
|
|
if(_focused_window) {
|
|
_focused_window->set_focused(true);
|
|
|
|
/* Move newly focused window to end of vector so it's rendered on top. */
|
|
auto it = std::find_if(_windows.begin(), _windows.end(),
|
|
[window](const std::unique_ptr<UIWindow>& p) {
|
|
return p.get() == window;
|
|
});
|
|
if(it != _windows.end()) {
|
|
std::rotate(it, it+1, _windows.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_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 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)) {
|
|
/* 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);
|
|
}
|
|
} else if(event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
|
|
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>(_game_state);
|
|
auto term_window = std::make_unique<UIWindow>("Terminal", 150, 150, 800, 500);
|
|
_window_awaiting_session_id = term_window.get();
|
|
term_window->set_content(std::move(term));
|
|
add_window(std::move(term_window));
|
|
_launcher_is_open = false;
|
|
_game_state->send_create_session_request();
|
|
} else if(app_to_launch == "Editor") {
|
|
auto editor = std::make_unique<Editor>();
|
|
auto editor_window = std::make_unique<UIWindow>("Editor", 200, 200, 600, 400);
|
|
editor_window->set_session_id(_initial_session_id);
|
|
editor_window->set_content(std::move(editor));
|
|
add_window(std::move(editor_window));
|
|
_launcher_is_open = false;
|
|
}
|
|
} else {
|
|
UIWindow* clicked_window = _taskbar->handle_event(event, screen_height);
|
|
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) {
|
|
/* Update cursor if hovering over any resize handle. */
|
|
for(int i = _windows.size() - 1; i >= 0; --i) {
|
|
if(_windows[i]->is_mouse_over_resize_handle(event->motion.x,
|
|
event->motion.y)) {
|
|
return CursorManager::set_cursor(CursorType::RESIZE_NWSE);
|
|
}
|
|
}
|
|
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) {
|
|
/* Remove closed windows. */
|
|
if(_focused_window) {
|
|
IWindowContent* content = _focused_window->get_content();
|
|
if(content) {
|
|
WindowAction action = content->get_pending_action();
|
|
uint32_t session_id = _focused_window->get_session_id();
|
|
switch(action.type) {
|
|
case ActionType::WRITE_FILE: {
|
|
if(session_id != 0) {
|
|
_game_state->send_file_write_request(session_id, action.payload1, action.payload2);
|
|
}
|
|
break;
|
|
}
|
|
case ActionType::READ_FILE: {
|
|
if(session_id != 0) {
|
|
_game_state->send_file_read_request(session_id, action.payload1);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for(auto& window : _windows) {
|
|
if(window) {
|
|
window->update();
|
|
}
|
|
}
|
|
_update_wallpaper(dt, screen_width, screen_height);
|
|
_windows.erase(std::remove_if(_windows.begin(), _windows.end(),
|
|
[this](const std::unique_ptr<UIWindow>& w) {
|
|
if (w->should_close()) {
|
|
/* Also remove from session map. */
|
|
uint32_t session_to_remove = 0;
|
|
for(auto const& [sid, win] : _session_windows) {
|
|
if(win == w.get()) {
|
|
session_to_remove = sid;
|
|
break;
|
|
}
|
|
}
|
|
if(session_to_remove != 0)
|
|
_session_windows.erase(session_to_remove);
|
|
_taskbar->remove_window(w.get());
|
|
if (w.get() == _focused_window) {
|
|
_focused_window = nullptr;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}),
|
|
_windows.end());
|
|
}
|
|
|
|
void Desktop::register_session(uint32_t session_id, UIWindow* window) {
|
|
_session_windows[session_id] = window;
|
|
if(_window_awaiting_session_id == window) {
|
|
_window_awaiting_session_id = nullptr;
|
|
}
|
|
}
|
|
|
|
UIWindow* Desktop::get_window_by_session_id(uint32_t session_id) {
|
|
if(_session_windows.count(session_id)) {
|
|
return _session_windows.at(session_id);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UIWindow* Desktop::get_focused_window(void) {
|
|
return _focused_window;
|
|
}
|
|
|
|
UIWindow* Desktop::get_window_awaiting_session_id(void) {
|
|
return _window_awaiting_session_id;
|
|
}
|
|
|
|
void Desktop::render(const RenderContext& context) {
|
|
/* Pass 1: Background. */
|
|
context.ui_renderer->begin_text();
|
|
_render_wallpaper(context.ui_renderer);
|
|
context.ui_renderer->flush_text();
|
|
|
|
/* Pass 2: Windows Render in order, last window is top-most. */
|
|
for(size_t i = 0; i < _windows.size(); ++i) {
|
|
auto& win_i = _windows[i];
|
|
if(win_i->is_minimized()) continue;
|
|
|
|
bool is_occluded = false;
|
|
Rect rect_i = win_i->get_rect();
|
|
|
|
/* Check against all windows on top of this one. */
|
|
for(size_t j = i+1; j < _windows.size(); ++j) {
|
|
auto& win_j = _windows[j];
|
|
if(win_j->is_minimized()) continue;
|
|
|
|
Rect rect_j = win_j->get_rect();
|
|
if(rect_i.x >= rect_j.x && (rect_i.x + rect_i.w) <= (rect_j.x + rect_j.w) &&
|
|
rect_i.y >= rect_j.y && (rect_i.y + rect_i.h) <= (rect_j.y + rect_j.h)) {
|
|
is_occluded = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!is_occluded) {
|
|
win_i->render(context);
|
|
}
|
|
}
|
|
|
|
/* Pass 3: Top-level static UI (taskbar, Launcher, etc.). */
|
|
context.ui_renderer->begin_text();
|
|
if(_launcher_is_open) {
|
|
_launcher->render(context.ui_renderer);
|
|
}
|
|
_taskbar->render(context.ui_renderer, _focused_window);
|
|
context.ui_renderer->flush_text();
|
|
}
|
|
|
|
void Desktop::_render_wallpaper(UIRenderer* ui_renderer) {
|
|
const Color wallpaper_color = { 0.0f, 0.15f, 0.08f };
|
|
for(auto& line : _background_text) {
|
|
ui_renderer->render_text(line.text.c_str(), std::round(line.x),
|
|
line.render_y, wallpaper_color);
|
|
}
|
|
}
|
|
|
|
void Desktop::_update_wallpaper(float dt, int screen_width, int screen_height) {
|
|
for(auto& line : _background_text) {
|
|
line.y_precise -= line.speed * dt * 100.0f;
|
|
line.render_y = static_cast<int>(std::round(line.y_precise));
|
|
if(line.render_y < 0) {
|
|
line.y_precise = screen_height;
|
|
line.x = (float)(rand() % screen_width);
|
|
line.text = get_random_snippet(_snippets);
|
|
}
|
|
}
|
|
}
|