aboutsummaryrefslogtreecommitdiff
path: root/src/client/window.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/window.cc')
-rw-r--r--src/client/window.cc449
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