diff --git a/CMakeLists.txt b/CMakeLists.txt index b3f7d98..5126add 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,8 @@ set(CLIENT_ONLY_SOURCES "src/gameview/entityview.cpp" "src/gameview/mapinstanceview.hpp" "src/gameview/mapinstanceview.cpp" + "src/gameview/remote_menu_view.hpp" + "src/gameview/remote_menu_view.cpp" "src/gameview/simple_entity_view.hpp" "src/gameview/simple_entity_view.cpp" "src/gameview/skinning_ubo.hpp" diff --git a/src/client/app.cpp b/src/client/app.cpp index ada3577..f579a01 100644 --- a/src/client/app.cpp +++ b/src/client/app.cpp @@ -6,6 +6,7 @@ #include "net/outmessage.hpp" #include "assets/cache.hpp" #include "gameview/worldview.hpp" +#include "gameview/utils.hpp" App::App() : gui_(dlist_, assets::CacheManager::GetFont("data/comic32.font")) @@ -64,7 +65,7 @@ void App::Frame() if (menu_) { auto menu_size = menu_->MeasureSize(); - menu_->Draw(gui_, glm::vec2(viewport_size_) - menu_size - 10.0f); + menu_->Draw(gui_, (glm::vec2(viewport_size_) - menu_size) * 0.5f); } gui_.Render(); @@ -114,20 +115,6 @@ void App::Disconnected(const std::string& reason) session_.reset(); } -static bool InputToMenuInput(game::PlayerInputType in, gui::MenuInput& mi) -{ - switch (in) - { - case game::IN_FORWARD: mi = gui::MI_UP; return true; - case game::IN_BACKWARD: mi = gui::MI_DOWN; return true; - case game::IN_LEFT: mi = gui::MI_LEFT; return true; - case game::IN_RIGHT: mi = gui::MI_RIGHT; return true; - case game::IN_JUMP: mi = gui::MI_ENTER; return true; - case game::IN_CROUCH: mi = gui::MI_BACK; return true; - default: return false; - } -}; - void App::Input(game::PlayerInputType in, bool pressed, bool repeated) { if (in == game::IN_MENU && pressed) @@ -215,7 +202,7 @@ static void AddSlider(gui::Menu& menu, std::string text, int& value, int min, in else if (value > max) value = max; - slider.SetSelectionText(std::to_string(value)); + slider.SetSelectionText(std::to_string(value) + " %"); changed(); }; @@ -226,6 +213,7 @@ static void AddSlider(gui::Menu& menu, std::string text, int& value, int min, in void App::OpenSettings() { menu_ = std::make_unique(); + menu_->SetTitle("nastavení"); AddSlider(*menu_, "jak moc to řve", volume_, 0, 100, [this]{ ApplyVolume(); diff --git a/src/game/game.cpp b/src/game/game.cpp index da24713..8080b1e 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -86,6 +86,10 @@ void game::Game::PlayerInput(Player& player, PlayerInputType type, bool enabled) break; } + case IN_DEBUG3: + DisplayTestMenu(player); + break; + default: { auto world = FindPlayerWorld(player); if (world) @@ -201,3 +205,36 @@ game::EnterableWorld* game::Game::FindPlayerWorld(Player& player) const return it->second.world; } + +void game::Game::DisplayTestMenu(Player& player) +{ + if (player.HasOpenMenu()) + return; + + auto& menu = player.DisplayMenu("test"); + + auto& btn_echo = menu.AddItem(RM_BUTTON, "echo"); + btn_echo.SetOnClick([&player] { + player.SendChat("echo test"); + }); + + auto& btn_bc = menu.AddItem(RM_BUTTON, "broadcast"); + btn_bc.SetOnClick([this, &player] { + BroadcastChat(player.GetName() + "^r mele hovna"); + }); + + int test = 0; + auto& sel_test = menu.AddItem(RM_SELECT, "výběr"); + sel_test.SetOnSelect([test, &sel_test] (int dir) mutable { + test += dir; + sel_test.SetSelection(std::to_string(test)); + }); + sel_test.SetSelection(std::to_string(test)); + + + auto& btn_close = menu.AddItem(RM_BUTTON, "zavřít"); + btn_close.SetOnClick([&menu, &player] { + player.CloseMenu(menu); + }); + +} diff --git a/src/game/game.hpp b/src/game/game.hpp index 2dc4472..94d3850 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -42,6 +42,8 @@ private: PlayerGameInfo& GetPlayerInfo(Player& player); EnterableWorld* FindPlayerWorld(Player& player) const; + void DisplayTestMenu(Player& player); + private: std::shared_ptr openworld_; std::shared_ptr testworld_; diff --git a/src/game/player.cpp b/src/game/player.cpp index c07b433..f5c0743 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -23,6 +23,9 @@ bool game::Player::ProcessMsg(net::MessageType type, net::InMessage& msg) case net::MSG_VIEWANGLES: return ProcessViewAnglesMsg(msg); + case net::MSG_MENUACTION: + return ProcessMenuActionMsg(msg); + default: return false; } @@ -30,20 +33,8 @@ bool game::Player::ProcessMsg(net::MessageType type, net::InMessage& msg) void game::Player::Update() { - if (world_ != known_world_) - { - SendWorldMsg(); - known_world_ = world_; - known_ents_.clear(); - - return; // send updates next frame - } - - if (world_) - { - SendWorldUpdateMsg(); - SyncEntities(); - } + SyncWorld(); + SendMenuMsgs(); } void game::Player::SetWorld(World* world) @@ -77,11 +68,58 @@ void game::Player::SetUseTarget(const std::string& text, const std::string& erro msg.Write(delay); } +game::RemoteMenu& game::Player::DisplayMenu(std::string title) +{ + if (remote_menu_) + throw std::runtime_error("cannot display multiple menus atm"); + + net::MenuId id = ++menu_id_; + remote_menu_ = std::make_unique(id, std::move(title)); + + // send msg + auto msg = BeginMsg(net::MSG_REMOTEMENU); + msg.Write(id); + msg.Write(net::MMSG_CREATE); + + return *remote_menu_; +} + +void game::Player::CloseMenu(const RemoteMenu& menu) +{ + if (&menu != remote_menu_.get()) + return; + + // send msg + auto msg = BeginMsg(net::MSG_REMOTEMENU); + msg.Write(menu.GetId()); + msg.Write(net::MMSG_CLOSE); + + remote_menu_.reset(); +} + game::Player::~Player() { game_.PlayerLeft(*this); } +void game::Player::SyncWorld() +{ + if (world_ != known_world_) + { + SendWorldMsg(); + known_world_ = world_; + known_ents_.clear(); + + return; // send updates next frame + } + + if (world_) + { + SendWorldUpdateMsg(); + SyncEntities(); + } +} + void game::Player::SendWorldMsg() { MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;) @@ -255,6 +293,20 @@ bool game::Player::ProcessViewAnglesMsg(net::InMessage& msg) return true; } +bool game::Player::ProcessMenuActionMsg(net::InMessage& msg) +{ + net::MenuId id; + net::MenuActionType type; + + if (!msg.Read(id) || !msg.Read(type)) + return false; + + if (!remote_menu_ || remote_menu_->GetId() != id) + return true; // not illegal, might be just a late message + + return remote_menu_->ProcessActionMsg(msg, type); +} + void game::Player::Input(PlayerInputType type, bool enabled) { if (enabled) @@ -265,3 +317,20 @@ void game::Player::Input(PlayerInputType type, bool enabled) game_.PlayerInput(*this, type, enabled); } +void game::Player::SendMenuMsgs() +{ + if (!remote_menu_) + return; + + remote_menu_->Update(); + auto menu_msg = remote_menu_->GetMsg(); + + if (menu_msg.empty()) + return; + + auto msg = BeginMsg(); + msg.Write(menu_msg); + + remote_menu_->ResetMsg(); + +} diff --git a/src/game/player.hpp b/src/game/player.hpp index 6ecde1a..0bc0689 100644 --- a/src/game/player.hpp +++ b/src/game/player.hpp @@ -12,6 +12,8 @@ #include "player_input.hpp" +#include "remote_menu.hpp" + namespace game { @@ -34,6 +36,10 @@ public: void SendChat(const std::string& text); void SetUseTarget(const std::string& text, const std::string& error_text, float delay); + RemoteMenu& DisplayMenu(std::string title); + void CloseMenu(const RemoteMenu& menu); + bool HasOpenMenu() const { return (bool)remote_menu_; } + const std::string& GetName() const { return name_; } PlayerInputFlags GetInput() const { return in_; } @@ -44,6 +50,7 @@ public: private: // world sync + void SyncWorld(); void SendWorldMsg(); void SendWorldUpdateMsg(); @@ -56,10 +63,14 @@ private: // msg handlers bool ProcessInputMsg(net::InMessage& msg); bool ProcessViewAnglesMsg(net::InMessage& msg); + bool ProcessMenuActionMsg(net::InMessage& msg); // events void Input(PlayerInputType type, bool enabled); + // menu sync + void SendMenuMsgs(); + private: Game& game_; std::string name_; @@ -73,6 +84,11 @@ private: net::EntNum cam_ent_ = 0; glm::vec3 cull_pos_ = glm::vec3(0.0f); + + // menus + // TODO: allow more menus + net::MenuId menu_id_ = 0; + std::unique_ptr remote_menu_; }; } \ No newline at end of file diff --git a/src/game/remote_menu.cpp b/src/game/remote_menu.cpp index 628f859..7aa42b0 100644 --- a/src/game/remote_menu.cpp +++ b/src/game/remote_menu.cpp @@ -1,9 +1,8 @@ #include "remote_menu.hpp" #include "net/defs.hpp" -game::RemoteMenuItem::RemoteMenuItem(RemoteMenu& menu, RemoteMenuItemType type, std::string text) : menu_(menu), type_(type) +game::RemoteMenuItem::RemoteMenuItem(RemoteMenu& menu, RemoteMenuItemType type, std::string text) : menu_(menu), type_(type), text_(std::move(text)) { - } void game::RemoteMenuItem::SetText(std::string text) @@ -26,7 +25,7 @@ void game::RemoteMenuItem::SetSelection(std::string selection) menu_.items_synced_ = false; } -game::RemoteMenu::RemoteMenu(net::MenuId id) : id_(id) +game::RemoteMenu::RemoteMenu(net::MenuId id, std::string title) : id_(id), title_(std::move(title)) { } @@ -41,6 +40,20 @@ game::RemoteMenuItem& game::RemoteMenu::AddItem(RemoteMenuItemType type, std::st return item_ref; } +bool game::RemoteMenu::ProcessActionMsg(net::InMessage& msg, net::MenuActionType type) +{ + switch (type) + { + case net::MA_CLICK: + return ProcessClickMsg(msg); + case net::MA_SELECT: + return ProcessSelectMsg(msg); + case net::MA_HOVER: + return ProcessHoverMsg(msg); + default: + return false; + } +} void game::RemoteMenu::Update() { @@ -51,7 +64,7 @@ void game::RemoteMenu::Update() if (!synced_) { auto msg = BeginMenuMsg(net::MMSG_UPDATE); - + msg.Write(net::MenuTitle(title_)); msg.Write(items_.size()); for (auto& item : items_) { @@ -103,3 +116,62 @@ net::OutMessage game::RemoteMenu::BeginMenuMsg(net::MenuMessageType type) msg.Write(type); return msg; } + +bool game::RemoteMenu::ProcessClickMsg(net::InMessage& msg) +{ + net::MenuItemId id; + + if (!msg.Read(id)) + return false; + + if (id > items_.size()) + return true; // not illegal + + auto& cb = items_[id]->on_click_; + if (cb) + cb(); + + return true; +} + +bool game::RemoteMenu::ProcessSelectMsg(net::InMessage& msg) +{ + net::MenuItemId id; + net::MenuSelectDir dir; + + if (!msg.Read(id) || !msg.Read(dir)) + return false; + + if (id > items_.size()) + return true; // not illegal + + int idir = dir ? 1 : -1; + + auto& cb = items_[id]->on_select_; + if (cb) + cb(idir); + + return true; +} + +bool game::RemoteMenu::ProcessHoverMsg(net::InMessage& msg) +{ + net::MenuItemId id; + + if (!msg.Read(id)) + return false; + + if (id > items_.size()) + return true; // not illegal + + if (id == hovered_) + return true; // already hovered + + hovered_ = id; + + auto& cb = items_[id]->on_hovered_; + if (cb) + cb(); + + return true; +} diff --git a/src/game/remote_menu.hpp b/src/game/remote_menu.hpp index c65ce61..8b23462 100644 --- a/src/game/remote_menu.hpp +++ b/src/game/remote_menu.hpp @@ -6,6 +6,7 @@ #include "net/defs.hpp" #include "net/msg_producer.hpp" #include "net/outmessage.hpp" +#include "net/inmessage.hpp" namespace game { @@ -54,17 +55,27 @@ private: class RemoteMenu : public net::MsgProducer { public: - RemoteMenu(net::MenuId id); + RemoteMenu(net::MenuId id, std::string title); RemoteMenuItem& AddItem(RemoteMenuItemType type, std::string text); + net::MenuId GetId() const { return id_; } + + bool ProcessActionMsg(net::InMessage& msg, net::MenuActionType type); + void Update(); private: net::OutMessage BeginMenuMsg(net::MenuMessageType type); + // action handlers + bool ProcessClickMsg(net::InMessage& msg); + bool ProcessSelectMsg(net::InMessage& msg); + bool ProcessHoverMsg(net::InMessage& msg); + private: net::MenuId id_; + std::string title_; bool synced_ = false; bool items_synced_ = false; std::vector> items_; diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index 78d35a4..1bfc380 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -4,15 +4,16 @@ #include // #include -#include "vehicleview.hpp" #include "utils/version.hpp" +#include "utils.hpp" +#include "vehicleview.hpp" game::view::ClientSession::ClientSession(App& app) : app_(app), use_target_hud_(app.GetTime()) { - // send login - auto msg = BeginMsg(net::MSG_ID); + // send login + auto msg = BeginMsg(net::MSG_ID); msg.Write(FEKAL_VERSION); - msg.Write(net::PlayerName(app.GetUserName())); + msg.Write(net::PlayerName(app.GetUserName())); } bool game::view::ClientSession::ProcessMessage(net::InMessage& msg) @@ -49,6 +50,9 @@ bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net: case net::MSG_USETARGET: return ProcessUseTargetMsg(msg); + case net::MSG_REMOTEMENU: + return ProcessMenuMsg(msg); + default: // try pass the msg to world if (world_ && world_->ProcessMsg(type, msg)) @@ -60,13 +64,13 @@ bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net: void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bool repeated) { - + if (pressed && ProcessMenuInput(in)) + return; if (repeated) return; SendInput(in, pressed); - } void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch) @@ -102,6 +106,8 @@ void game::view::ClientSession::Draw(gfx::DrawList& dlist, gfx::DrawListParams& } use_target_hud_.Draw(gui); + + DrawMenus(gui); } void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) const @@ -115,7 +121,7 @@ void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) con if (ent) { start += ent->GetRoot().GetGlobalPosition(); - + if (dynamic_cast(ent)) distance = 8.0f; } @@ -129,7 +135,7 @@ void game::view::ClientSession::GetViewInfo(glm::vec3& eye, glm::mat4& view) con glm::vec3 end = start - dir * distance; - //start.z -= 0.5f; // shift this a bit to make it better when occluded + // start.z -= 0.5f; // shift this a bit to make it better when occluded eye = world_->CameraSweep(start, end); view = glm::lookAt(eye, eye + dir, glm::vec3(0, 0, 1)); } @@ -183,6 +189,39 @@ bool game::view::ClientSession::ProcessUseTargetMsg(net::InMessage& msg) return true; } +bool game::view::ClientSession::ProcessMenuMsg(net::InMessage& msg) +{ + net::MenuId id; + net::MenuMessageType type; + + if (!msg.Read(id) || !msg.Read(type)) + return false; + + switch (type) + { + case net::MMSG_CREATE: { + if (FindMenu(id)) + return false; + + remote_menus_.push_back(std::make_unique(*this, id)); + return true; + } + + case net::MMSG_CLOSE: { + std::erase_if(remote_menus_, [id](auto& menu) { return menu->GetId() == id; }); + return true; + } + + default: { + auto menu = FindMenu(id); + if (!menu) + return false; + + return menu->ProcessMessage(type, msg); + } + } +} + void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui) { // glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f, @@ -199,7 +238,8 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar // glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view; - game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye, glm::ivec2(params.screen_width, params.screen_height), 500.0f); + game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye, + glm::ivec2(params.screen_width, params.screen_height), 500.0f); world_->Draw(draw_args); glm::mat4 camera_world = glm::inverse(view); @@ -209,10 +249,10 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar void game::view::ClientSession::SendInput(game::PlayerInputType type, bool enable) { auto msg = BeginMsg(net::MSG_IN); - uint8_t val = type; - if (enable) - val |= 128; - msg.Write(val); + uint8_t val = type; + if (enable) + val |= 128; + msg.Write(val); } void game::view::ClientSession::SendViewAngles(float time) @@ -236,3 +276,37 @@ void game::view::ClientSession::SendViewAngles(float time) view_pitch_q_.value = pitch_q.value; last_send_time_ = time; } + +void game::view::ClientSession::DrawMenus(gui::Context& gui) const +{ + if (remote_menus_.empty()) + return; + + auto& top_menu = *remote_menus_.back(); + top_menu.Draw(gui, glm::vec2(20.0f, 100.0f)); + +} + +bool game::view::ClientSession::ProcessMenuInput(game::PlayerInputType in) +{ + if (remote_menus_.empty()) + return false; + + gui::MenuInput mi; + if (!InputToMenuInput(in, mi)) + return false; + + remote_menus_.back()->Input(mi); + return true; +} + +game::view::RemoteMenuView* game::view::ClientSession::FindMenu(net::MenuId id) const +{ + for (auto& menu : remote_menus_) + { + if (menu->GetId() == id) + return menu.get(); + } + + return nullptr; +} diff --git a/src/gameview/client_session.hpp b/src/gameview/client_session.hpp index 7c99908..da3fc7c 100644 --- a/src/gameview/client_session.hpp +++ b/src/gameview/client_session.hpp @@ -11,6 +11,7 @@ #include "net/msg_producer.hpp" #include "game/player_input.hpp" #include "gui/use_target_hud.hpp" +#include "remote_menu_view.hpp" class App; @@ -41,12 +42,17 @@ private: bool ProcessCameraMsg(net::InMessage& msg); bool ProcessChatMsg(net::InMessage& msg); bool ProcessUseTargetMsg(net::InMessage& msg); + bool ProcessMenuMsg(net::InMessage& msg); void DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui); void SendInput(game::PlayerInputType type, bool enable); void SendViewAngles(float time); + void DrawMenus(gui::Context& gui) const; + bool ProcessMenuInput(game::PlayerInputType in); + RemoteMenuView* FindMenu(net::MenuId id) const; + private: App& app_; @@ -60,6 +66,8 @@ private: float last_send_time_ = 0.0f; gui::UseTargetHud use_target_hud_; + + std::vector> remote_menus_; }; } // namespace game::view \ No newline at end of file diff --git a/src/gameview/remote_menu_view.cpp b/src/gameview/remote_menu_view.cpp new file mode 100644 index 0000000..63e112b --- /dev/null +++ b/src/gameview/remote_menu_view.cpp @@ -0,0 +1,144 @@ +#include "remote_menu_view.hpp" +#include "game/remote_menu.hpp" +#include "client_session.hpp" + +game::view::RemoteMenuView::RemoteMenuView(ClientSession& session, net::MenuId id) : session_(session), id_(id) {} + +bool game::view::RemoteMenuView::ProcessMessage(net::MenuMessageType type, net::InMessage& msg) +{ + switch (type) + { + case net::MMSG_UPDATE: + return ProcessUpdateMsg(msg); + case net::MMSG_ITEM_UPDATE_TEXT: + return ProcessItemUpdateTextMsg(msg); + case net::MMSG_ITEM_UPDATE_SELECTION: + return ProcessItemUpdateSelectionMsg(msg); + default: + return false; + } +} + +bool game::view::RemoteMenuView::ProcessUpdateMsg(net::InMessage& msg) +{ + net::MenuTitle title; + net::MenuItemCount itemcount; + + if (!msg.Read(title) || !msg.Read(itemcount)) + return false; + + SetTitle(title); + Clear(); + + for (net::MenuItemId i = 0; i < itemcount; ++i) + { + game::RemoteMenuItemType type; + net::MenuItemText text; + net::MenuItemSelection selection; + + if (!msg.Read(type) || !msg.Read(text) || !msg.Read(selection)) + return false; + + switch (type) + { + case RM_BUTTON: + { + auto& btn = Add(text); + btn.SetClickCallback([this, i] { OnItemClick(i); }); + break; + } + + case RM_SELECT: + { + auto& select = Add(text); + select.SetSelectionText(selection); + select.SetClickCallback([this, i] { OnItemClick(i); }); + select.SetSwitchCallback([this, i] (int dir) { OnItemSelectionChange(i, dir); }); + break; + } + + default: + break; + } + + } + + + return true; +} + +bool game::view::RemoteMenuView::ProcessItemUpdateTextMsg(net::InMessage& msg) +{ + net::MenuItemId idx; + net::MenuItemText text; + + if (!msg.Read(idx) || !msg.Read(text)) + return false; + + if (idx >= GetNumItems()) + return false; + + auto& item = GetItem(idx); + + if (auto btn = dynamic_cast(&item); btn) + { + btn->SetText(text); + } + else + { + return false; + } + + return true; +} + +bool game::view::RemoteMenuView::ProcessItemUpdateSelectionMsg(net::InMessage& msg) +{ + net::MenuItemId idx; + net::MenuItemSelection selection; + + if (!msg.Read(idx) || !msg.Read(selection)) + return false; + + if (idx >= GetNumItems()) + return false; + + auto& item = GetItem(idx); + + auto select = dynamic_cast(&item); + if (!select) + return false; + + select->SetSelectionText(selection); + return true; +} + +void game::view::RemoteMenuView::OnItemClick(net::MenuItemId idx) +{ + auto msg = BeginActionMsg(net::MA_CLICK); + msg.Write(idx); +} + +void game::view::RemoteMenuView::OnItemSelectionChange(net::MenuItemId idx, int dir) +{ + if (dir == 0) + return; + + auto msg = BeginActionMsg(net::MA_SELECT); + msg.Write(idx); + msg.Write(dir < 0 ? 0 : 1); +} + +void game::view::RemoteMenuView::OnFocusChanged() +{ + auto msg = BeginActionMsg(net::MA_HOVER); + msg.Write(GetFocusedItemIndex()); +} + +net::OutMessage game::view::RemoteMenuView::BeginActionMsg(net::MenuActionType type) +{ + auto msg = session_.BeginMsg(net::MSG_MENUACTION); + msg.Write(id_); + msg.Write(type); + return msg; +} diff --git a/src/gameview/remote_menu_view.hpp b/src/gameview/remote_menu_view.hpp new file mode 100644 index 0000000..558161c --- /dev/null +++ b/src/gameview/remote_menu_view.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "gui/menu.hpp" +// #include "net/msg_producer.hpp" +#include "net/inmessage.hpp" +#include "net/outmessage.hpp" +#include "net/defs.hpp" + +namespace game::view +{ + +class ClientSession; + +class RemoteMenuView : public gui::Menu +{ +public: + using Super = gui::Menu; + + RemoteMenuView(ClientSession& session, net::MenuId id); + + bool ProcessMessage(net::MenuMessageType type, net::InMessage& msg); + + net::MenuId GetId() const { return id_; } + +private: + bool ProcessUpdateMsg(net::InMessage& msg); + bool ProcessItemUpdateTextMsg(net::InMessage& msg); + bool ProcessItemUpdateSelectionMsg(net::InMessage& msg); + + void OnItemClick(net::MenuItemId idx); + void OnItemSelectionChange(net::MenuItemId idx, int dir); + +protected: + void OnFocusChanged() override; + + net::OutMessage BeginActionMsg(net::MenuActionType type); + +private: + ClientSession& session_; + net::MenuId id_; + + + +}; + +} \ No newline at end of file diff --git a/src/gameview/utils.hpp b/src/gameview/utils.hpp new file mode 100644 index 0000000..33c96d0 --- /dev/null +++ b/src/gameview/utils.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "game/player_input.hpp" +#include "gui/menu.hpp" + +inline bool InputToMenuInput(game::PlayerInputType in, gui::MenuInput& mi) +{ + switch (in) + { + case game::IN_FORWARD: mi = gui::MI_UP; return true; + case game::IN_BACKWARD: mi = gui::MI_DOWN; return true; + case game::IN_LEFT: mi = gui::MI_LEFT; return true; + case game::IN_RIGHT: mi = gui::MI_RIGHT; return true; + case game::IN_JUMP: mi = gui::MI_ENTER; return true; + case game::IN_USE: mi = gui::MI_ENTER; return true; + case game::IN_CROUCH: mi = gui::MI_BACK; return true; + default: return false; + } +}; \ No newline at end of file diff --git a/src/gui/context.cpp b/src/gui/context.cpp index c16d3b9..38af3a3 100644 --- a/src/gui/context.cpp +++ b/src/gui/context.cpp @@ -96,6 +96,7 @@ glm::vec2 gui::Context::MeasureText(std::string_view text) } else if (cp == '\n') { + size.x = glm::max(size.x, cursor.x); cursor.x = 0.0f; cursor.y += line_height; continue; @@ -122,9 +123,9 @@ glm::vec2 gui::Context::MeasureText(std::string_view text) continue; // Dont even have "missing" glyph, font is shit cursor.x += glyph->advance; - size.x = glm::max(size.x, cursor.x); } + size.x = glm::max(size.x, cursor.x); size.y = cursor.y + line_height; return size; diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index c1d363e..2c76088 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -4,17 +4,36 @@ // Menu +static constexpr float menu_title_height = 50.0f; + +void gui::Menu::Clear() +{ + items_.clear(); + focus_ = 0; +} + void gui::Menu::Draw(Context& ctx, const glm::vec2& pos) const { // background auto size = MeasureSize(); - ctx.DrawRect(pos, pos + size, 0x55000000); + ctx.DrawRect(pos, pos + size, 0x88000000); + + // draw title + glm::vec2 title_size(size.x, menu_title_height); + ctx.DrawRect(pos, pos + title_size, 0x55000000); + ctx.DrawTextAligned(title_, pos + title_size * 0.5f, glm::vec2(-0.5f)); + + // draw items + DrawMenuItemArgs args(ctx); + args.size = itemsize_; + args.pos.x = pos.x; - glm::vec2 cursor = pos; for (size_t i = 0; i < items_.size(); ++i) { - items_[i]->Draw(DrawMenuItemArgs(ctx, cursor, focus_ == i)); - cursor.y += items_[i]->GetSize().y; + args.focused = focus_ == i; + args.pos.y = pos.y + menu_title_height + static_cast(i) * itemsize_.y; + + items_[i]->Draw(args); } } @@ -37,18 +56,14 @@ void gui::Menu::Input(MenuInput in) } } +void gui::Menu::SetTitle(std::string title) +{ + title_ = std::move(title); +} + glm::vec2 gui::Menu::MeasureSize() const { - glm::vec2 size(0.0f); - - for (const auto& item : items_) - { - const auto& itemsize = item->GetSize(); - size.x = glm::max(size.x, itemsize.x); - size.y += itemsize.y; - } - - return size; + return glm::vec2(itemsize_.x, menu_title_height + itemsize_.y * static_cast(items_.size())); } void gui::Menu::SwitchFocus(int dir) @@ -56,7 +71,17 @@ void gui::Menu::SwitchFocus(int dir) if (items_.empty()) return; - focus_ = (focus_ + items_.size() + dir) % items_.size(); + size_t old_focus = focus_; + + if (items_.empty()) + focus_ = 0; + else + focus_ = (focus_ + items_.size() + dir) % items_.size(); + + if (focus_ != old_focus) + { + OnFocusChanged(); + } } // ButtonMenuItem @@ -64,13 +89,12 @@ void gui::Menu::SwitchFocus(int dir) gui::ButtonMenuItem::ButtonMenuItem(std::string text) : text_(std::move(text)) { - size_ = glm::vec2(300.0f, 30.0f); } void gui::ButtonMenuItem::Draw(const DrawMenuItemArgs& args) const { Super::Draw(args); - glm::vec2 center = args.pos + glm::vec2(0.0f, size_.y * 0.5f); + glm::vec2 center = args.pos + glm::vec2(10.0f, args.size.y * 0.5f); args.ctx.DrawTextAligned(text_, center, glm::vec2(0.0f, -0.5f), args.focused ? COLOR_FOCUSED : COLOR_INACTIVE); } @@ -96,12 +120,30 @@ void gui::SelectMenuItem::Draw(const DrawMenuItemArgs& args) const { Super::Draw(args); - char buffer[128]; - size_t len = snprintf(buffer, sizeof(buffer), "< %s >", select_text_.c_str()); + auto text_size = args.ctx.MeasureText(select_text_); - glm::vec2 center_right = args.pos + glm::vec2(size_.x, size_.y * 0.5f); - args.ctx.DrawTextAligned(std::string_view(buffer, len), center_right, glm::vec2(-1.0f, -0.5f), - args.focused ? COLOR_FOCUSED : COLOR_INACTIVE); + glm::vec2 cursor = args.pos + glm::vec2(args.size.x - 10.0f, args.size.y * 0.5f); + cursor.y -= text_size.y * 0.5f; // centered + + uint32_t text_color = args.focused ? COLOR_FOCUSED : COLOR_INACTIVE; + uint32_t arrow_color = 0xFFFFFFFF; + + float arrow_width = 0.0f; + if (args.focused) + { + arrow_width = args.ctx.MeasureText("<").x; + cursor.x -= arrow_width; + args.ctx.DrawText(">", cursor, arrow_color); + } + + cursor.x -= text_size.x; + args.ctx.DrawText(select_text_, cursor, text_color); + + if (args.focused) + { + cursor.x -= arrow_width; + args.ctx.DrawText("<", cursor, arrow_color); + } } void gui::SelectMenuItem::Input(MenuInput in) diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index 1daa289..e7f81d2 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -24,9 +24,10 @@ struct DrawMenuItemArgs { Context& ctx; glm::vec2 pos; + glm::vec2 size; bool focused; - DrawMenuItemArgs(Context& ctx, const glm::vec2& pos, bool focused) : ctx(ctx), pos(pos), focused(focused) {} + DrawMenuItemArgs(Context& ctx) : ctx(ctx) {} }; class Menu @@ -43,16 +44,31 @@ public: return item_ref; } + void Clear(); + void Draw(Context& ctx, const glm::vec2& pos) const; void Input(MenuInput in); + void SetTitle(std::string title); + void SetItemSize(const glm::vec2& itemsize) { itemsize_ = itemsize; } + + size_t GetFocusedItemIndex() const { return focus_; } + + size_t GetNumItems() const { return items_.size(); } + MenuItem& GetItem(size_t idx) const { return *items_[idx]; } + glm::vec2 MeasureSize() const; +protected: + virtual void OnFocusChanged() {} + private: void SwitchFocus(int dir); private: + std::string title_; std::vector> items_; + glm::vec2 itemsize_ = glm::vec2(300.0f, 40.0f); size_t focus_ = 0; }; @@ -73,7 +89,7 @@ public: virtual ~MenuItem() = default; protected: - glm::vec2 size_; + glm::vec2 size_ = glm::vec2(0.0f); }; @@ -89,6 +105,8 @@ public: void SetClickCallback(std::function click_cb); + void SetText(std::string text) { text_ = std::move(text); } + virtual ~ButtonMenuItem() = default; private: diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 5ea8a89..5801280 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -23,6 +23,9 @@ enum MessageType : uint8_t // VIEWANGLES MSG_VIEWANGLES, + // MENUACTION ... + MSG_MENUACTION, + /*~~~~~~~~ Session ~~~~~~~~*/ // CHAT MSG_CHAT, @@ -37,7 +40,7 @@ enum MessageType : uint8_t // USETARGET ... MSG_USETARGET, - // REMOTEMENU ... + // REMOTEMENU ... MSG_REMOTEMENU, /*~~~~~~~~ Entity ~~~~~~~~*/ @@ -159,6 +162,7 @@ enum MenuMessageType }; using MenuId = uint8_t; +using MenuTitle = FixedStr<64>; using MenuItemId = uint8_t; using MenuItemCount = MenuItemId; @@ -166,5 +170,16 @@ using MenuItemCount = MenuItemId; using MenuItemText = FixedStr<64>; using MenuItemSelection = FixedStr<64>; +// menu actions + +enum MenuActionType +{ + MA_CLICK, + MA_SELECT, + MA_HOVER, +}; + +using MenuSelectDir = uint8_t; // 0=left, 1=right + } // namespace net \ No newline at end of file