bettola/client/src/ui/desktop.cpp

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);
}
}
}