diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
| commit | e4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch) | |
| tree | ed58c3c246e3af1af337697695d780aa31f6ad9a /src/client/window | |
| parent | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff) | |
Update to most recent version (old initial commit)
Diffstat (limited to 'src/client/window')
| -rw-r--r-- | src/client/window/basic_window.cc | 33 | ||||
| -rw-r--r-- | src/client/window/basic_window.hh | 52 | ||||
| -rw-r--r-- | src/client/window/button_window.cc | 71 | ||||
| -rw-r--r-- | src/client/window/button_window.hh | 40 | ||||
| -rw-r--r-- | src/client/window/hud_window.cc | 88 | ||||
| -rw-r--r-- | src/client/window/hud_window.hh | 32 | ||||
| -rw-r--r-- | src/client/window/inventory_window.cc | 196 | ||||
| -rw-r--r-- | src/client/window/inventory_window.hh | 40 | ||||
| -rw-r--r-- | src/client/window/text_input_window.cc | 81 | ||||
| -rw-r--r-- | src/client/window/text_input_window.hh | 43 | ||||
| -rw-r--r-- | src/client/window/window.cc | 318 | ||||
| -rw-r--r-- | src/client/window/window.hh | 38 |
12 files changed, 1032 insertions, 0 deletions
diff --git a/src/client/window/basic_window.cc b/src/client/window/basic_window.cc new file mode 100644 index 0000000..c96f94a --- /dev/null +++ b/src/client/window/basic_window.cc @@ -0,0 +1,33 @@ +#include "client/window/basic_window.hh" + +namespace client { +namespace window { + +using bw = basic_window; + +bool bw::is_inside(const glm::vec2& v, const glm::vec2& p, + const glm::vec2& s) noexcept { + if (v.x < p.x || v.x > p.x + s.x) { + return false; + } + if (v.y < p.y || v.y > p.y + s.y) { + return false; + } + return true; +} + +bool bw::is_inside(const glm::vec2& v) const noexcept { + return is_inside(v, this->pos, this->size); +} + +void bw::draw() noexcept { + client::render::draw_rectangle({.pos = {.offset = this->pos + 6.0f}, + .size = {.offset = this->size}, + .colour = {this->tertiary_clr, 0.9f}}); + client::render::draw_rectangle({.pos = {.offset = this->pos}, + .size = {.offset = this->size}, + .colour = {this->primary_clr, 1.0f}}); +} + +} // namespace window +} // namespace client diff --git a/src/client/window/basic_window.hh b/src/client/window/basic_window.hh new file mode 100644 index 0000000..292cc64 --- /dev/null +++ b/src/client/window/basic_window.hh @@ -0,0 +1,52 @@ +#ifndef CLIENT_WINDOW_BASIC_WINDOW_HH_ +#define CLIENT_WINDOW_BASIC_WINDOW_HH_ + +#include <glm/glm.hpp> + +#include "client/entity/player.hh" +#include "client/render/render.hh" +#include "client/render/struct.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: + static constexpr float OUTLINE_WIDTH = 2.0f; + float get_item_size() const noexcept { return this->size.x / 10.0f; } + +protected: + glm::vec2 pos; + glm::vec2 size; + +public: + // Test if a vec2 v is inside a square starting at position p with size s. + static bool is_inside(const glm::vec2& v, const glm::vec2& p, + const glm::vec2& s) noexcept; + // Test if v is inside this->pos, this->size + bool is_inside(const glm::vec2& v) const noexcept; + +public: + basic_window(const client::render::relative_arg& pos, + const client::render::relative_arg& size) noexcept + : pos(pos.to_vec2()), size(size.to_vec2()) {} + virtual ~basic_window() noexcept {} + + virtual bool maybe_handle_event(const SDL_Event&) noexcept { return false; } + virtual void draw() noexcept; +}; + +} // namespace window +} // namespace client + +#endif diff --git a/src/client/window/button_window.cc b/src/client/window/button_window.cc new file mode 100644 index 0000000..4743c0a --- /dev/null +++ b/src/client/window/button_window.cc @@ -0,0 +1,71 @@ +#include "client/window/button_window.hh" + +namespace client { +namespace window { + +using bw = class button_window; + +void bw::handle_mousebuttondown(const SDL_Event& event) noexcept { + if (event.button.button != SDL_BUTTON_LEFT) { + return; + } + this->is_pressed = true; +} + +void bw::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& bw::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; +} + +bool bw::maybe_handle_event(const SDL_Event& event) noexcept { + 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); +} + +void bw::draw() noexcept { + basic_window::draw(); + + client::render::draw_rectangle({.pos = {.offset = this->pos}, + .size = {.offset = this->size}, + .colour = {this->get_draw_colour(), 1.0f}}); + client::render::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}); +}; + +} // namespace window +} // namespace client diff --git a/src/client/window/button_window.hh b/src/client/window/button_window.hh new file mode 100644 index 0000000..6847696 --- /dev/null +++ b/src/client/window/button_window.hh @@ -0,0 +1,40 @@ +#ifndef CLIENT_WINDOW_BUTTON_WINDOW_HH_ +#define CLIENT_WINDOW_BUTTON_WINDOW_HH_ + +#include <functional> +#include <string> + +#include "client/input.hh" +#include "client/render/render.hh" +#include "client/window/basic_window.hh" + +namespace client { +namespace window { + +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; + void handle_mousebuttonup(const SDL_Event& event) noexcept; + const glm::vec3& get_draw_colour() noexcept; + +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), + is_pressed(false) {} + virtual ~button_window() noexcept {} + + virtual bool maybe_handle_event(const SDL_Event& event) noexcept override; + virtual void draw() noexcept override; +}; + +} // namespace window +} // namespace client + +#endif diff --git a/src/client/window/hud_window.cc b/src/client/window/hud_window.cc new file mode 100644 index 0000000..52995ca --- /dev/null +++ b/src/client/window/hud_window.cc @@ -0,0 +1,88 @@ +#include "client/window/hud_window.hh" + +namespace client { +namespace window { + +using hw = class hud_window; +bool hw::maybe_handle_keydown(const SDL_Event& event) noexcept { + const auto& sym = event.key.keysym.sym; + const auto index = static_cast<int>(sym); + + const auto min = static_cast<int>(SDLK_0); + const auto max = static_cast<int>(SDLK_9); + if (index < min || index > max) { + return false; + } + + const auto active = + static_cast<std::uint32_t>((((sym - min) - 1) + 10) % 10); + client::get_localplayer().get_mutable_active_item() = active; + return true; +} + +bool hw::maybe_handle_event(const SDL_Event& event) noexcept { + if (is_open()) { + return false; + } + + switch (event.type) { + case SDL_KEYDOWN: + return this->maybe_handle_keydown(event); + default: + break; + } + return false; +} + +void hw::draw() noexcept { + this->basic_window::draw(); + + const float item_size = this->get_item_size(); + + const auto& localplayer = client::get_localplayer(); + client::render::draw_rectangle( + {.pos = {.offset = {this->pos.x + static_cast<float>( + localplayer.get_active_item()) * + item_size, + this->pos.y}}, + .size = {.offset = {item_size, item_size}}, + .colour = {this->secondary_clr, 1.0f}}); + + for (int i = 1; i < shared::player::INVENTORY_COLS; ++i) { + const float off = item_size * static_cast<float>(i); + client::render::draw_rectangle( + {.pos = {.offset = {this->pos.x + off, + this->pos.y + OUTLINE_WIDTH}}, + .size = {.offset = {OUTLINE_WIDTH, + item_size - 2.0f * OUTLINE_WIDTH}}, + .colour = {this->tertiary_clr, 1.0f}}); + } + + const auto& inventory = localplayer.inventory; + for (int x = 0; x < shared::player::INVENTORY_COLS; ++x) { + const auto& item = inventory.contents[static_cast<unsigned long>(x)]; + if (item == nullptr) { + continue; + } + + const glm::vec2 off{glm::vec2{x, 0} * item_size}; + const auto item_ptr = dynamic_cast<client::item::item*>(&*item); + if (item_ptr == nullptr) { + continue; + } + item_ptr->draw(this->pos + off, glm::vec2{item_size, item_size}); + + client::render::draw_text( + std::to_string(item->quantity), + {.pos = {.offset = this->pos + off + + glm::vec2{item_size * 0.75f, item_size * 0.2f}}, + .offset_height = item_size * 0.40f, + .colour = {this->font_colour, 1.0f}, + .has_backing = false, + .is_centered = true, + .is_vcentered = true}); + } +} + +} // namespace window +} // namespace client diff --git a/src/client/window/hud_window.hh b/src/client/window/hud_window.hh new file mode 100644 index 0000000..7c03e0e --- /dev/null +++ b/src/client/window/hud_window.hh @@ -0,0 +1,32 @@ +#ifndef CLIENT_WINDOW_HUD_WINDOW_HH_ +#define CLIENT_WINDOW_HUD_WINDOW_HH_ + +#include "client/render/struct.hh" +#include "client/window/basic_window.hh" +#include "client/window/window.hh" +#include "shared/entity/player.hh" + +namespace client { +namespace window { + +class hud_window : public basic_window { + +private: + bool maybe_handle_keydown(const SDL_Event& event) noexcept; + +public: + template <typename... Args> + hud_window(const float size) noexcept + : basic_window( + client::render::relative_arg{.extent = {0.5f, 0.0f}, + .offset = {-size / 2.0f, 0.0f}}, + client::render::relative_arg{.offset = {size, size / 10.0f}}) {} + + virtual void draw() noexcept override; + virtual bool maybe_handle_event(const SDL_Event& event) noexcept override; +}; + +} // namespace window +} // namespace client + +#endif diff --git a/src/client/window/inventory_window.cc b/src/client/window/inventory_window.cc new file mode 100644 index 0000000..5023f1d --- /dev/null +++ b/src/client/window/inventory_window.cc @@ -0,0 +1,196 @@ +#include "client/window/inventory_window.hh" + +namespace client { +namespace window { + +using iw = class inventory_window; + +bool iw::maybe_handle_mousebuttondown(const SDL_Event& event) noexcept { + + if (event.button.button != SDL_BUTTON_LEFT) { + return false; + } + if (!this->is_inside(client::input::state.mouse_pos)) { + return false; + } + + const auto index = + this->maybe_get_inventory_index(client::input::state.mouse_pos); + if (!index.has_value()) { + return false; + } + + const auto& inventory = client::get_localplayer().inventory; + if (inventory.contents[*index] != nullptr) { + this->grabbed.emplace(*index); + } + return true; +} + +static proto::packet make_swap_packet(const std::uint32_t& a, + const std::uint32_t& b) noexcept { + proto::packet ret; + const auto packet = ret.mutable_item_swap_packet(); + packet->set_index_a(a); + packet->set_index_b(b); + return ret; +} + +bool iw::maybe_handle_mousebuttonup(const SDL_Event& event) noexcept { + + if (event.button.button != SDL_BUTTON_LEFT) { + return false; + } + if (!this->grabbed.has_value()) { + return false; + } + + if (const auto index = + this->maybe_get_inventory_index(input::state.mouse_pos); + index.has_value() && index != *grabbed) { + + auto& inventory = client::get_localplayer().inventory; + std::swap(inventory.contents[*grabbed], inventory.contents[*index]); + + // replicate on server + state::connection->rsend_packet( + make_swap_packet(static_cast<std::uint32_t>(*grabbed), + static_cast<std::uint32_t>(*index))); + } + + this->grabbed.reset(); + return true; +} + +std::optional<unsigned long> +iw::maybe_get_inventory_index(const glm::vec2& pos) const noexcept { + + const float item_size = this->get_item_size(); + for (int x = 0; x < shared::player::INVENTORY_COLS; ++x) { + for (int y = 0; y < shared::player::INVENTORY_ROWS; ++y) { + const glm::vec2 off{this->pos + + glm::vec2{OUTLINE_WIDTH, OUTLINE_WIDTH} + + glm::vec2{x, y} * item_size}; + if (!basic_window::is_inside(pos, off, + glm::vec2{item_size, item_size})) { + continue; + } + return x + y * shared::player::INVENTORY_COLS; + } + } + return std::nullopt; +} + +bool iw::maybe_handle_event(const SDL_Event& event) noexcept { + switch (event.type) { + case SDL_MOUSEBUTTONDOWN: + return this->maybe_handle_mousebuttondown(event); + case SDL_MOUSEBUTTONUP: + return this->maybe_handle_mousebuttonup(event); + } + + return basic_window::maybe_handle_event(event); +} + +void iw::draw() noexcept { + basic_window::draw(); + + const float item_size = this->get_item_size(); + + const auto& localplayer = client::get_localplayer(); + client::render::draw_rectangle( + {.pos = {.offset = {this->pos.x + static_cast<float>( + localplayer.get_active_item()) * + item_size, + this->pos.y}}, + .size = {.offset = {item_size, item_size}}, + .colour = {this->secondary_clr, 1.0f}}); + for (int i = 1; i < shared::player::INVENTORY_COLS; ++i) { + const float off = item_size * static_cast<float>(i); + client::render::draw_rectangle( + {.pos = {.offset = {this->pos.x + off, + this->pos.y + OUTLINE_WIDTH}}, + .size = {.offset = {OUTLINE_WIDTH, + item_size * shared::player::INVENTORY_ROWS - + OUTLINE_WIDTH}}, + .colour = {this->tertiary_clr, 1.0f}}); + + if (i <= shared::player::INVENTORY_ROWS) { + client::render::draw_rectangle( + {.pos = {.offset = {pos.x + OUTLINE_WIDTH, pos.y + off}}, + .size = {.offset = {this->size.x - 2.0f * OUTLINE_WIDTH, + OUTLINE_WIDTH}}, + .colour = {this->tertiary_clr, 1.0f}}); + } + } + + const auto& inventory = localplayer.inventory; + if (const auto index = + this->maybe_get_inventory_index(client::input::state.mouse_pos); + index.has_value()) { + const glm::vec2 off{glm::vec2{*index % shared::player::INVENTORY_COLS, + *index / shared::player::INVENTORY_COLS} * + item_size}; + client::render::draw_rectangle( + {.pos = {.offset = this->pos + off + + glm::vec2{OUTLINE_WIDTH, OUTLINE_WIDTH}}, + .size = {.offset = glm::vec2{item_size, item_size} - + glm::vec2{OUTLINE_WIDTH, OUTLINE_WIDTH}}, + .colour = {this->secondary_clr, 1.0f}}); + } + + for (int x = 0; x < shared::player::INVENTORY_COLS; ++x) { + for (int y = 0; y < shared::player::INVENTORY_ROWS; ++y) { + const auto index = static_cast<unsigned long>( + x + y * shared::player::INVENTORY_COLS); + const auto& item = inventory.contents[index]; + if (item == nullptr) { + continue; + } + if (this->grabbed.has_value() && *this->grabbed == index) { + continue; + } + + const glm::vec2 off{glm::vec2{x, y} * item_size}; + const auto item_ptr = dynamic_cast<client::item::item*>(&*item); + if (item_ptr == nullptr) { + continue; + } + item_ptr->draw(this->pos + off, glm::vec2{item_size, item_size}); + + client::render::draw_text( + std::to_string(item->quantity), + {.pos = {.offset = + this->pos + off + + glm::vec2{item_size * 0.75f, item_size * 0.2f}}, + .offset_height = item_size * 0.40f, + .colour = {this->font_colour, 1.0f}, + .has_backing = false, + .is_centered = true, + .is_vcentered = true}); + } + } + + if (this->grabbed.has_value()) { + const auto item_ptr = + dynamic_cast<client::item::item*>(&*inventory.contents[*grabbed]); + if (item_ptr != nullptr) { + item_ptr->draw(client::input::state.mouse_pos - + glm::vec2{item_size, item_size} * 0.5f, + glm::vec2{item_size, item_size}); + client::render::draw_text( + std::to_string(item_ptr->quantity), + {.pos = {.offset = + client::input::state.mouse_pos + + glm::vec2{item_size * 0.25f, item_size * -0.3f}}, + .offset_height = item_size * 0.40f, + .colour = {this->font_colour, 1.0f}, + .has_backing = false, + .is_centered = true, + .is_vcentered = true}); + } + } +} + +} // namespace window +} // namespace client diff --git a/src/client/window/inventory_window.hh b/src/client/window/inventory_window.hh new file mode 100644 index 0000000..a77ae2f --- /dev/null +++ b/src/client/window/inventory_window.hh @@ -0,0 +1,40 @@ +#ifndef CLIENT_WINDOW_INVENTORY_WINDOW_HH_ +#define CLIENT_WINDOW_INVENTORY_WINDOW_HH_ + +#include <cstdint> +#include <optional> + +#include "client/item/item.hh" +#include "client/render/draw.hh" +#include "client/state/state.hh" +#include "client/window/basic_window.hh" + +namespace client { +namespace window { + +class inventory_window : public basic_window { +private: + std::optional<unsigned long> grabbed; + +private: + std::optional<unsigned long> + maybe_get_inventory_index(const glm::vec2& pos) const noexcept; + +private: + bool maybe_handle_mousebuttondown(const SDL_Event& event) noexcept; + bool maybe_handle_mousebuttonup(const SDL_Event& event) noexcept; + +public: + template <typename... Args> + inventory_window(Args&&... args) noexcept + : basic_window(std::forward<Args>(args)...) {} + virtual ~inventory_window() noexcept {} + + virtual bool maybe_handle_event(const SDL_Event& event) noexcept override; + virtual void draw() noexcept override; +}; + +} // namespace window +} // namespace client + +#endif diff --git a/src/client/window/text_input_window.cc b/src/client/window/text_input_window.cc new file mode 100644 index 0000000..0d556a4 --- /dev/null +++ b/src/client/window/text_input_window.cc @@ -0,0 +1,81 @@ +#include "client/window/text_input_window.hh" + +namespace client { +namespace window { + +using tiw = class text_input_window; + +const std::string& tiw::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; +} + +proto::packet tiw::make_say_packet() noexcept { + proto::packet packet; + + const auto sub_say_packet = packet.mutable_say_packet(); + sub_say_packet->set_text(get_send_text()); + + return packet; +} + +bool tiw::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::state::connection->rsend_packet(make_say_packet()); + } + + pop_window(); + return true; +} + +const glm::vec3& tiw::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; +} + +bool tiw::maybe_handle_event(const SDL_Event& event) noexcept { + switch (event.type) { + case SDL_KEYDOWN: + return this->maybe_handle_keydown(event); + } + + return basic_window::maybe_handle_event(event); +} + +void tiw::draw() noexcept { + basic_window::draw(); + + client::render::draw_rectangle({.pos = {.offset = this->pos}, + .size = {.offset = this->size}, + .colour = {this->get_draw_colour(), 1.0f}}); + + client::render::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}); +}; + +} // namespace window +} // namespace client diff --git a/src/client/window/text_input_window.hh b/src/client/window/text_input_window.hh new file mode 100644 index 0000000..edff672 --- /dev/null +++ b/src/client/window/text_input_window.hh @@ -0,0 +1,43 @@ +#ifndef CLIENT_WINDOW_TEXT_INPUT_WINDOW_HH_ +#define CLIENT_WINDOW_TEXT_INPUT_WINDOW_HH_ + +#include <string> + +#include "client/input.hh" +#include "client/shared.hh" +#include "client/state/state.hh" +#include "client/window/basic_window.hh" +#include "shared/net/proto.hh" + +namespace client { +namespace window { + +void pop_window() noexcept; // forward declaration + +class text_input_window : public basic_window { +private: + // text restricted to a size of 32. + static const std::string& get_send_text() noexcept; + static proto::packet make_say_packet() noexcept; + + static bool maybe_handle_keydown(const SDL_Event& event) noexcept; + static const glm::vec3& get_draw_colour() noexcept; + +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; + + virtual void draw() noexcept override; +}; + +} // namespace window +} // namespace client + +#endif diff --git a/src/client/window/window.cc b/src/client/window/window.cc new file mode 100644 index 0000000..648997f --- /dev/null +++ b/src/client/window/window.cc @@ -0,0 +1,318 @@ +#include "window.hh" + +namespace client { +namespace window { + +// TODO + +// 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 + +static hud_window& get_hud_window() noexcept { + static hud_window ret = []() { + const auto& window = client::render::get_window_size(); + const float size = std::min(static_cast<float>(window.x) / 2.0f, + static_cast<float>(window.y) / 2.0f); + client::window::hud_window ret{size}; + return ret; + }(); + return ret; +} + +// All dynamic 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; +} + +void pop_window() 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, + pop_window, + 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", pop_window, + client::render::relative_arg{ + .extent = center_extent({0.3, 0.3}, ssize_extent)}, + client::render::relative_arg{.extent = ssize_extent}); +} + +static void make_main_menu() noexcept { + get_layers().push_front({}); + + push_window<button_window>( + "Return to Game", pop_window, + client::render::relative_arg{ + .extent = center_extent({0.3, 0.7}, lsize_extent)}, + client::render::relative_arg{.extent = lsize_extent}); + + push_window<button_window>( + "Options", make_options_menu, + client::render::relative_arg{ + .extent = center_extent({0.55, 0.6}, ssize_extent)}, + client::render::relative_arg{.extent = ssize_extent}); + + push_window<button_window>( + "?", pop_window, + client::render::relative_arg{ + .extent = center_extent({0.55, 0.5}, ssize_extent)}, + client::render::relative_arg{.extent = ssize_extent}); + + push_window<button_window>( + "?", pop_window, + client::render::relative_arg{ + .extent = center_extent({0.55, 0.4}, ssize_extent)}, + client::render::relative_arg{.extent = ssize_extent}); + + push_window<button_window>( + "Exit Game", [] { shared::should_exit = true; }, + client::render::relative_arg{ + .extent = center_extent({0.3, 0.3}, lsize_extent)}, + client::render::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::render::relative_arg{ + .extent = center_extent({0.3, 0.3}, lsize_extent)}, + client::render::relative_arg{.extent = lsize_extent}); + + client::input::set_mouse_relative(false); + center_mouse_position(); +} + +static void make_inventory_window() noexcept { + get_layers().push_front({}); + + const glm::vec2& window = client::render::get_window_size(); + + const float size = std::min(window.x / 2.0f, window.y / 2.0f); + const glm::vec2 pos{(window - size) / 2.0f}; + + push_window<inventory_window>( + client::render::relative_arg{.offset = pos}, + client::render::relative_arg{.offset = glm::vec2{size, size}}); + 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; + } + + pop_window(); +} + +static void handle_meta_e() noexcept { + if (!is_open()) { + make_inventory_window(); + return; + } + + // The inventory window must be open. + if (!dynamic_cast<inventory_window*>(&*get_layers().front().front())) { + return; + } + + pop_window(); +} + +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; + case SDLK_e: + handle_meta_e(); + break; + case SDLK_0: + case SDLK_1: + case SDLK_2: + case SDLK_3: + case SDLK_4: + case SDLK_5: + case SDLK_6: + case SDLK_7: + case SDLK_8: + case SDLK_9: + get_hud_window().maybe_handle_event(event); + default: + 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()) { + + if (!client::input::is_key_toggled(SDLK_F1)) { + get_hud_window().draw(); + } + + return; + } + + client::render::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 diff --git a/src/client/window/window.hh b/src/client/window/window.hh new file mode 100644 index 0000000..d112d18 --- /dev/null +++ b/src/client/window/window.hh @@ -0,0 +1,38 @@ +#ifndef CLIENT_WINDOW_WINDOW_HH_ +#define CLIENT_WINDOW_WINDOW_HH_ + +#include <algorithm> +#include <forward_list> +#include <functional> +#include <memory> +#include <optional> +#include <string> +#include <utility> + +#include <SDL2/SDL.h> +#include <glm/glm.hpp> + +#include "client/client.hh" +#include "client/input.hh" +#include "client/render/render.hh" +#include "client/settings.hh" +#include "client/window/basic_window.hh" +#include "client/window/button_window.hh" +#include "client/window/hud_window.hh" +#include "client/window/inventory_window.hh" +#include "client/window/text_input_window.hh" +#include "shared/shared.hh" + +namespace client { +namespace window { + +// removes the topmost window +void pop_window() noexcept; +bool is_open() noexcept; + +void draw() noexcept; + +} // namespace window +} // namespace client + +#endif |
