diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 18:05:18 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 18:05:18 +1100 |
| commit | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (patch) | |
| tree | 222dfcd07a1e40716127a347bbfd7119ce3d0984 /src/client/window.cc | |
initial commit
Diffstat (limited to 'src/client/window.cc')
| -rw-r--r-- | src/client/window.cc | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/src/client/window.cc b/src/client/window.cc new file mode 100644 index 0000000..6a11252 --- /dev/null +++ b/src/client/window.cc @@ -0,0 +1,449 @@ +#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<long>(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 <typename... Args> + text_input_window(Args&&... args) noexcept + : basic_window(std::forward<Args>(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<void()> 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 <typename... Args> + button_window(const std::string_view n, const decltype(callback)& c, + Args&&... args) noexcept + : basic_window(std::forward<Args>(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 <typename T> +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 <typename... Args> + 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>(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<std::unique_ptr<basic_window>>; +using layers = std::forward_list<layer>; +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 <typename T, typename... Args> +void push_window(Args&&... args) noexcept { + get_layers().front().push_front( + std::make_unique<T>(std::forward<Args>(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<float>>( + "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<button_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<button_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<button_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<button_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<button_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<button_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<text_input_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<int>(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 |
