#include "window.hh" namespace client { namespace window { // All window objects should derive from basic_window, and make use of multiple // inheritance for specific behaviours. class basic_window { protected: // Colours borrowed from arc-dark(er). static constexpr glm::vec3 primary_clr{0.21f, 0.23f, 0.29f}; // dark static constexpr glm::vec3 secondary_clr{0.29f, 0.32f, 0.38f}; // less dark static constexpr glm::vec3 tertiary_clr{0.48, 0.50, 0.54}; // less dark ^2 static constexpr glm::vec3 highlight_clr{0.32, 0.58, 0.88}; // light blue static constexpr glm::vec3 font_colour{0.88, 0.88, 0.88}; // light grey protected: glm::vec2 pos; glm::vec2 size; public: bool is_inside(const glm::vec2& v) const noexcept { if (v.x < this->pos.x || v.x > this->pos.x + this->size.x) { return false; } if (v.y < this->pos.y || v.y > this->pos.y + this->size.y) { return false; } return true; } public: basic_window(const client::draw::relative_arg& p, const client::draw::relative_arg& s) noexcept : pos(p.to_vec2()), size(s.to_vec2()) {} virtual ~basic_window() noexcept {} virtual bool maybe_handle_event(const SDL_Event&) noexcept { return false; } virtual void draw() noexcept { client::draw::draw_rectangle({.pos = {.offset = this->pos + 6.0f}, .size = {.offset = this->size}, .colour = {this->tertiary_clr, 0.9f}}); client::draw::draw_rectangle({.pos = {.offset = this->pos}, .size = {.offset = this->size}, .colour = {this->primary_clr, 1.0f}}); } }; static void remove_top_layer() noexcept; class text_input_window : public basic_window { private: // text restricted to a size of 32. static const std::string& get_send_text() noexcept { auto& text = client::input::state.text_input; text = std::string{std::begin(text), std::begin(text) + static_cast(std::min( std::size(text), shared::MAX_SAY_LENGTH))}; return text; } static bool maybe_handle_keydown(const SDL_Event& event) noexcept { if (event.key.keysym.sym == SDLK_BACKSPACE) { if (!client::input::state.text_input.empty()) { client::input::state.text_input.pop_back(); } return true; } if (event.key.keysym.sym != SDLK_RETURN || event.key.repeat) { return false; } if (!client::input::state.text_input.empty()) { client::send_say_packet(get_send_text()); } remove_top_layer(); // DELETE ME return true; } static const glm::vec3& get_draw_colour() noexcept { if (client::input::state.text_input.length() >= shared::MAX_SAY_LENGTH) { return basic_window::highlight_clr; } return basic_window::primary_clr; } public: template text_input_window(Args&&... args) noexcept : basic_window(std::forward(args)...) { client::input::set_text_input(true); } virtual ~text_input_window() noexcept { client::input::set_text_input(false); } virtual bool maybe_handle_event(const SDL_Event& event) noexcept override { switch (event.type) { case SDL_KEYDOWN: return this->maybe_handle_keydown(event); } return basic_window::maybe_handle_event(event); } virtual void draw() noexcept override { basic_window::draw(); client::draw::draw_rectangle( {.pos = {.offset = this->pos}, .size = {.offset = this->size}, .colour = {this->get_draw_colour(), 1.0f}}); client::draw::draw_text( this->get_send_text(), {.pos = {.extent = {0.0f, 0.0f}, .offset = this->pos + (this->size / 2.0f)}, .offset_height = this->size.y / 2.0f, .colour = {this->font_colour, 1.0f}, .has_backing = false, .is_centered = true, .is_vcentered = true}); }; }; class button_window : public basic_window { protected: std::string name; std::function callback; bool is_pressed; private: void handle_mousebuttondown(const SDL_Event& event) noexcept { if (event.button.button != SDL_BUTTON_LEFT) { return; } this->is_pressed = true; } void handle_mousebuttonup(const SDL_Event& event) noexcept { if (event.button.button != SDL_BUTTON_LEFT) { return; } if (!this->is_pressed) { return; } this->is_pressed = false; std::invoke(this->callback); // edge } const glm::vec3& get_draw_colour() noexcept { if (!this->is_inside(client::input::state.mouse_pos)) { this->is_pressed = false; return this->primary_clr; } if (!this->is_pressed) { return this->secondary_clr; } return this->highlight_clr; } public: template button_window(const std::string_view n, const decltype(callback)& c, Args&&... args) noexcept : basic_window(std::forward(args)...), name(n), callback(c) {} virtual bool maybe_handle_event(const SDL_Event& event) noexcept override { if (this->is_inside(client::input::state.mouse_pos)) { switch (event.type) { case SDL_MOUSEBUTTONDOWN: this->handle_mousebuttondown(event); return true; case SDL_MOUSEBUTTONUP: this->handle_mousebuttonup(event); return true; } } return basic_window::maybe_handle_event(event); } virtual void draw() noexcept override { basic_window::draw(); client::draw::draw_rectangle( {.pos = {.offset = this->pos}, .size = {.offset = this->size}, .colour = {this->get_draw_colour(), 1.0f}}); client::draw::draw_text( this->name, {.pos = {.extent = {0.0f, 0.0f}, .offset = this->pos + (this->size / 2.0f)}, .offset_height = this->size.y / 2.0f, .colour = {this->font_colour, 1.0f}, .has_backing = false, .is_centered = true, .is_vcentered = true}); }; }; // Sliders are for numerical values of some type T. // TODO /* template class slider_window : public basic_window { protected: std::string name; T min; T cur; T max; T& var; private: void handle_mousebuttondown(const SDL_Event& event) noexcept {} void handle_mousebuttonup(const SDL_Event& event) noexcept {} public: template slider_window(const std::string_view name, const T& min, const T& cur, const T& max, T& var, Args&&... args) noexcept : basic_window(std::forward(args)...), name(name), min(min), cur(cur), max(max), var(var) {} // slider_window( virtual bool maybe_handle_event(const SDL_Event& event) noexcept override { switch (event.type) { case SDL_MOUSEBUTTONDOWN: this->handle_mousebuttondown(event); return true; case SDL_MOUSEBUTTONUP: this->handle_mousebuttonup(event); return true; } return basic_window::maybe_handle_event(event); } virtual void draw() noexcept override { basic_window::draw(); } }; */ static void handle_event(const SDL_Event& event) noexcept; // ignore // All windows go in this list! using layer = std::forward_list>; using layers = std::forward_list; static layers& get_layers() noexcept { // We callbacks for our window manager are initialised here too. static layers ret = []() -> layers { client::input::register_event_handler(&handle_event); client::input::set_text_input(false); client::input::set_mouse_relative(true); return {}; }(); return ret; } static void remove_top_layer() noexcept { if (!get_layers().empty()) { get_layers().pop_front(); } // Our windows might be empty here, so set our mouse mode accordingly. if (!client::window::is_open()) { client::input::set_mouse_relative(true); } } // Constants used for uniform ui sizes. constexpr glm::vec2 lsize_extent{0.4, 0.075}; constexpr glm::vec2 ssize_extent{0.15, 0.075}; static void center_mouse_position() noexcept { const glm::vec2& window = client::render::get_window_size(); client::input::set_mouse_position({window.x / 2.0f, window.y / 2.0f}); } template void push_window(Args&&... args) noexcept { get_layers().front().push_front( std::make_unique(std::forward(args)...)); } constexpr glm::vec2 center_extent(const glm::vec2 pos, const glm::vec2 size) noexcept { return {pos.x, pos.y - size.y / 2.0f}; } static void make_options_menu() noexcept { get_layers().push_front({}); /* push_window<::slider_window>( "Field of Vision", 0.0f, settings::get(std::make_pair("gameplay", "fov"), 100.0f), 145.0f, remove_top_layer, client::draw::relative_arg{.extent = center_extent({0.3, 0.7}, lsize_extent)}, client::draw::relative_arg{.extent = lsize_extent}); */ push_window( "Back", remove_top_layer, client::draw::relative_arg{.extent = center_extent({0.3, 0.3}, ssize_extent)}, client::draw::relative_arg{.extent = ssize_extent}); } static void make_main_menu() noexcept { get_layers().push_front({}); push_window( "Return to Game", remove_top_layer, client::draw::relative_arg{.extent = center_extent({0.3, 0.7}, lsize_extent)}, client::draw::relative_arg{.extent = lsize_extent}); push_window( "Options", make_options_menu, client::draw::relative_arg{ .extent = center_extent({0.55, 0.6}, ssize_extent)}, client::draw::relative_arg{.extent = ssize_extent}); push_window( "?", remove_top_layer, client::draw::relative_arg{ .extent = center_extent({0.55, 0.5}, ssize_extent)}, client::draw::relative_arg{.extent = ssize_extent}); push_window( "?", remove_top_layer, client::draw::relative_arg{ .extent = center_extent({0.55, 0.4}, ssize_extent)}, client::draw::relative_arg{.extent = ssize_extent}); push_window( "Exit Game", [] { shared::should_exit = true; }, client::draw::relative_arg{.extent = center_extent({0.3, 0.3}, lsize_extent)}, client::draw::relative_arg{.extent = lsize_extent}); client::input::set_mouse_relative(false); center_mouse_position(); } static void make_chat_window() noexcept { get_layers().push_front({}); push_window( client::draw::relative_arg{.extent = center_extent({0.3, 0.3}, lsize_extent)}, client::draw::relative_arg{.extent = lsize_extent}); client::input::set_mouse_relative(false); center_mouse_position(); } static void handle_meta_return() noexcept { if (!is_open()) { make_chat_window(); return; } } static void handle_meta_escape() noexcept { if (!is_open()) { make_main_menu(); return; } remove_top_layer(); } static void handle_meta_keydown(const SDL_Event& event) noexcept { if (event.key.repeat) { // only handle keypresses return; } switch (event.key.keysym.sym) { case SDLK_ESCAPE: handle_meta_escape(); break; case SDLK_RETURN: handle_meta_return(); break; } } static void handle_meta_mousemotion(const SDL_Event& event) noexcept { // We convert SDL's weird coordinates into useful ones (0,0 = bottom // left). client::input::state.mouse_pos = { event.motion.x, static_cast(client::render::get_window_size().y) - event.motion.y}; } static void handle_meta_windowevent(const SDL_Event& event) noexcept { if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { if (!is_open()) { make_main_menu(); return; } } } static void handle_meta_event(const SDL_Event& event) noexcept { switch (event.type) { case SDL_KEYDOWN: handle_meta_keydown(event); break; case SDL_MOUSEMOTION: handle_meta_mousemotion(event); break; case SDL_WINDOWEVENT: handle_meta_windowevent(event); break; } } static void handle_event(const SDL_Event& event) noexcept { // We ALWAYS update our mouse position. if (event.type == SDL_MOUSEMOTION) { handle_meta_mousemotion(event); } // Either a window consumes our event, or no window does - so we send // the event to our "meta handler" which does things like closing // windows etc. if (is_open()) { for (const auto& window : get_layers().front()) { if (window->maybe_handle_event(event)) { return; } } } handle_meta_event(event); } void draw() noexcept { if (!is_open()) { return; } client::draw::draw_colour({0.0f, 0.0f, 0.0f, 0.10f}); // very light shade for (const auto& window : get_layers().front()) { window->draw(); } } bool is_open() noexcept { return !get_layers().empty(); } } // namespace window } // namespace client