Compare commits
3 Commits
3e284af672
...
35008f9304
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35008f9304 | ||
|
|
e4aded6099 | ||
|
|
b6f6b28c98 |
@ -114,6 +114,8 @@ set(CLIENT_ONLY_SOURCES
|
||||
"src/gui/context.cpp"
|
||||
"src/gui/font.hpp"
|
||||
"src/gui/font.cpp"
|
||||
"src/gui/menu.hpp"
|
||||
"src/gui/menu.cpp"
|
||||
"src/utils/files.cpp"
|
||||
)
|
||||
|
||||
|
||||
@ -35,27 +35,6 @@ void App::Frame()
|
||||
delta_time_ = 0.1f; // Cap delta time to avoid large jumps
|
||||
}
|
||||
|
||||
// detect inputs originating in this frame
|
||||
//game::PlayerInputFlags new_input = input_ & ~prev_input_;
|
||||
|
||||
// detect input changes
|
||||
for (size_t i = 0; i < game::IN__COUNT; ++i)
|
||||
{
|
||||
auto in_old = prev_input_ & (1 << i);
|
||||
auto in_new = input_ & (1 << i);
|
||||
|
||||
if (in_old > in_new) // released
|
||||
{
|
||||
SendInput(static_cast<game::PlayerInputType>(i), false);
|
||||
}
|
||||
else if (in_new > in_old) // pressed
|
||||
{
|
||||
SendInput(static_cast<game::PlayerInputType>(i), true);
|
||||
}
|
||||
}
|
||||
|
||||
prev_input_ = input_;
|
||||
|
||||
if (session_)
|
||||
{
|
||||
game::view::UpdateInfo updinfo;
|
||||
@ -76,18 +55,31 @@ void App::Frame()
|
||||
|
||||
gui_.Begin();
|
||||
|
||||
const game::view::WorldView* world;
|
||||
// draw session
|
||||
if (session_)
|
||||
{
|
||||
session_->Draw(dlist_, params, gui_);
|
||||
}
|
||||
|
||||
// draw stats
|
||||
UpdateStats();
|
||||
DrawStats();
|
||||
|
||||
// draw chat
|
||||
UpdateChat();
|
||||
DrawChat();
|
||||
|
||||
// draw menu
|
||||
if (menu_)
|
||||
{
|
||||
auto menu_size = menu_->MeasureSize();
|
||||
menu_->Draw(gui_, glm::vec2(viewport_size_) - menu_size - 10.0f);
|
||||
}
|
||||
|
||||
gui_.Render();
|
||||
renderer_.DrawList(dlist_, params);
|
||||
|
||||
++stat_frames_;
|
||||
}
|
||||
|
||||
void App::Connected()
|
||||
@ -97,11 +89,6 @@ void App::Connected()
|
||||
|
||||
// init session
|
||||
session_ = std::make_unique<game::view::ClientSession>(*this);
|
||||
|
||||
// send login
|
||||
auto msg = BeginMsg(net::MSG_ID);
|
||||
net::PlayerName name;
|
||||
msg.Write(name);
|
||||
}
|
||||
|
||||
void App::ProcessMessage(net::InMessage& msg)
|
||||
@ -118,6 +105,12 @@ void App::ProcessMessage(net::InMessage& msg)
|
||||
{
|
||||
std::cerr << "FAILED to process message!" << std::endl;
|
||||
}
|
||||
|
||||
// record stats
|
||||
++stat_msgs_;
|
||||
stat_msglen_total_ += s;
|
||||
stat_msglen_min_ = std::min(stat_msglen_min_, s);
|
||||
stat_msglen_max_ = std::max(stat_msglen_max_, s);
|
||||
}
|
||||
|
||||
void App::Disconnected(const std::string& reason)
|
||||
@ -130,6 +123,40 @@ 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)
|
||||
{
|
||||
OpenSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
gui::MenuInput mi;
|
||||
if (menu_ && pressed && InputToMenuInput(in, mi))
|
||||
{
|
||||
menu_->Input(mi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (session_)
|
||||
session_->Input(in, pressed, repeated);
|
||||
|
||||
}
|
||||
|
||||
void App::MouseMove(const glm::vec2& delta)
|
||||
{
|
||||
float sensitivity = 0.002f; // Sensitivity factor for mouse movement
|
||||
@ -156,15 +183,6 @@ void App::AddChatMessagePrefix(const std::string& prefix, const std::string& tex
|
||||
|
||||
App::~App() {}
|
||||
|
||||
void App::SendInput(game::PlayerInputType type, bool enable)
|
||||
{
|
||||
auto msg = BeginMsg(net::MSG_IN);
|
||||
uint8_t val = type;
|
||||
if (enable)
|
||||
val |= 128;
|
||||
msg.Write(val);
|
||||
}
|
||||
|
||||
void App::UpdateChat()
|
||||
{
|
||||
// remove expired or over the limit messages
|
||||
@ -191,3 +209,74 @@ void App::DrawChat()
|
||||
gui_.DrawText(chat_[i].text, pos, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddSlider(gui::Menu& menu, std::string text, int& value, int min, int max)
|
||||
{
|
||||
auto& slider = menu.Add<gui::SelectMenuItem>(std::move(text));
|
||||
auto on_switch = [&slider, &value, min, max] (int v) {
|
||||
value += v;
|
||||
|
||||
// clamp
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > max)
|
||||
value = max;
|
||||
|
||||
slider.SetSelectionText(std::to_string(value));
|
||||
};
|
||||
|
||||
slider.SetSwitchCallback(on_switch);
|
||||
on_switch(0);
|
||||
}
|
||||
|
||||
void App::OpenSettings()
|
||||
{
|
||||
menu_ = std::make_unique<gui::Menu>();
|
||||
|
||||
AddSlider(*menu_, "jak moc to řve", volume_, 0, 100);
|
||||
|
||||
auto& ok = menu_->Add<gui::ButtonMenuItem>("0k");
|
||||
ok.SetClickCallback([this] { menu_.reset(); });
|
||||
}
|
||||
|
||||
#define COL_LABEL "^ccc"
|
||||
#define COL_VALUE "^5ff"
|
||||
|
||||
void App::UpdateStats()
|
||||
{
|
||||
if (time_ < stats_time_ + 1.0f)
|
||||
return;
|
||||
|
||||
stats_time_ = time_;
|
||||
|
||||
fps_text_.clear();
|
||||
fps_text_ += COL_VALUE;
|
||||
fps_text_ += std::to_string(stat_frames_);
|
||||
fps_text_ += COL_LABEL " fps";
|
||||
|
||||
if (stat_msgs_ > 0)
|
||||
{
|
||||
msglen_text_ = COL_LABEL "net: n=" COL_VALUE;
|
||||
msglen_text_ += std::to_string(stat_msgs_);
|
||||
msglen_text_ += COL_LABEL " min=" COL_VALUE;
|
||||
msglen_text_ += std::to_string(stat_msglen_min_);
|
||||
msglen_text_ += COL_LABEL " max=" COL_VALUE;
|
||||
msglen_text_ += std::to_string(stat_msglen_max_);
|
||||
msglen_text_ += COL_LABEL " total=" COL_VALUE;
|
||||
msglen_text_ += std::to_string(stat_msglen_total_);
|
||||
}
|
||||
|
||||
stat_frames_ = 0;
|
||||
stat_msgs_ = 0;
|
||||
stat_msglen_total_ = 0;
|
||||
stat_msglen_min_ = SIZE_MAX;
|
||||
stat_msglen_max_ = 0;
|
||||
}
|
||||
|
||||
void App::DrawStats()
|
||||
{
|
||||
glm::vec2 pos(viewport_size_.x - 5.0f, 5.0f);
|
||||
gui_.DrawTextAligned(fps_text_, pos, glm::vec2(-1.0f, 0.0f));
|
||||
pos.y += 30.0f;
|
||||
gui_.DrawTextAligned(msglen_text_, pos, glm::vec2(-1.0f, 0.0f));
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
#include "audio/master.hpp"
|
||||
#include "net/msg_producer.hpp"
|
||||
#include "net/inmessage.hpp"
|
||||
|
||||
#include "gui/menu.hpp"
|
||||
#include "gameview/client_session.hpp"
|
||||
|
||||
struct ChatMessage
|
||||
@ -20,7 +20,7 @@ struct ChatMessage
|
||||
glm::vec4 color = glm::vec4(1.0f);
|
||||
};
|
||||
|
||||
class App : public net::MsgProducer
|
||||
class App
|
||||
{
|
||||
public:
|
||||
App();
|
||||
@ -33,12 +33,15 @@ public:
|
||||
|
||||
void SetTime(float time) { time_ = time; }
|
||||
void SetViewportSize(int width, int height) { viewport_size_ = {width, height}; }
|
||||
void SetInput(game::PlayerInputFlags input) { input_ = input; }
|
||||
|
||||
void Input(game::PlayerInputType in, bool pressed, bool repeated);
|
||||
void MouseMove(const glm::vec2& delta);
|
||||
|
||||
float GetTime() const { return time_; }
|
||||
float GetDeltaTime() const { return delta_time_; }
|
||||
|
||||
game::view::ClientSession* GetSession() { return session_.get(); }
|
||||
|
||||
audio::Master& GetAudioMaster() { return audiomaster_; }
|
||||
|
||||
void AddChatMessage(const std::string& text);
|
||||
@ -47,16 +50,17 @@ public:
|
||||
~App();
|
||||
|
||||
private:
|
||||
void SendInput(game::PlayerInputType type, bool enable);
|
||||
|
||||
void UpdateChat();
|
||||
void DrawChat();
|
||||
|
||||
void OpenSettings();
|
||||
|
||||
void UpdateStats();
|
||||
void DrawStats();
|
||||
|
||||
private:
|
||||
float time_ = 0.0f;
|
||||
glm::ivec2 viewport_size_ = {800, 600};
|
||||
game::PlayerInputFlags input_ = 0;
|
||||
game::PlayerInputFlags prev_input_ = 0;
|
||||
|
||||
float prev_time_ = 0.0f;
|
||||
float delta_time_ = 0.0f;
|
||||
@ -70,4 +74,19 @@ private:
|
||||
std::unique_ptr<game::view::ClientSession> session_;
|
||||
|
||||
std::deque<ChatMessage> chat_;
|
||||
|
||||
std::unique_ptr<gui::Menu> menu_;
|
||||
|
||||
// settings
|
||||
int volume_ = 50;
|
||||
|
||||
// stats
|
||||
float stats_time_ = 0.0f;
|
||||
size_t stat_frames_ = 0;
|
||||
size_t stat_msgs_ = 0;
|
||||
size_t stat_msglen_total_ = 0;
|
||||
size_t stat_msglen_min_ = SIZE_MAX;
|
||||
size_t stat_msglen_max_ = 0;
|
||||
std::string fps_text_ = { 0 };
|
||||
std::string msglen_text_ = { 0 };
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
@ -138,6 +139,20 @@ static void ShutdownSDL()
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static const std::map<SDL_Scancode, game::PlayerInputType> s_inputmap = {
|
||||
{ SDL_SCANCODE_W, game::IN_FORWARD },
|
||||
{ SDL_SCANCODE_S, game::IN_BACKWARD },
|
||||
{ SDL_SCANCODE_A, game::IN_LEFT },
|
||||
{ SDL_SCANCODE_D, game::IN_RIGHT },
|
||||
{ SDL_SCANCODE_SPACE, game::IN_JUMP },
|
||||
{ SDL_SCANCODE_LCTRL, game::IN_CROUCH },
|
||||
{ SDL_SCANCODE_E, game::IN_USE },
|
||||
{ SDL_SCANCODE_F3, game::IN_DEBUG1 },
|
||||
{ SDL_SCANCODE_F4, game::IN_DEBUG2 },
|
||||
{ SDL_SCANCODE_F5, game::IN_DEBUG3 },
|
||||
{ SDL_SCANCODE_TAB, game::IN_MENU },
|
||||
};
|
||||
|
||||
static void PollEvents()
|
||||
{
|
||||
SDL_Event event;
|
||||
@ -150,13 +165,27 @@ static void PollEvents()
|
||||
return;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
int xrel = event.motion.xrel;
|
||||
int yrel = event.motion.yrel;
|
||||
if (xrel != 0 || yrel != 0)
|
||||
{
|
||||
s_app->MouseMove(glm::vec2(static_cast<float>(xrel), static_cast<float>(yrel)));
|
||||
int xrel = event.motion.xrel;
|
||||
int yrel = event.motion.yrel;
|
||||
if (xrel != 0 || yrel != 0)
|
||||
{
|
||||
s_app->MouseMove(glm::vec2(static_cast<float>(xrel), static_cast<float>(yrel)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
auto in_it = s_inputmap.find(event.key.keysym.scancode);
|
||||
if (in_it != s_inputmap.end())
|
||||
{
|
||||
s_app->Input(in_it->second, event.key.state == SDL_PRESSED, event.key.repeat != 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -326,55 +355,29 @@ static void Frame()
|
||||
game::PlayerInputFlags input = 0;
|
||||
const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_w)])
|
||||
input |= (1 << game::IN_FORWARD);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_s)])
|
||||
input |= (1 << game::IN_BACKWARD);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_a)])
|
||||
input |= (1 << game::IN_LEFT);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_d)])
|
||||
input |= (1 << game::IN_RIGHT);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_SPACE)])
|
||||
input |= (1 << game::IN_JUMP);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_LCTRL)])
|
||||
input |= (1 << game::IN_CROUCH);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_e)])
|
||||
input |= (1 << game::IN_USE);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F3)])
|
||||
input |= (1 << game::IN_DEBUG1);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F4)])
|
||||
input |= (1 << game::IN_DEBUG2);
|
||||
|
||||
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F5)])
|
||||
input |= (1 << game::IN_DEBUG3);
|
||||
|
||||
int mouse_state = SDL_GetMouseState(nullptr, nullptr);
|
||||
|
||||
if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT))
|
||||
input |= (1 << game::IN_ATTACK);
|
||||
|
||||
s_app->SetInput(input);
|
||||
|
||||
s_app->Frame();
|
||||
|
||||
if (s_ws_connected)
|
||||
auto session = s_app->GetSession();
|
||||
if (session)
|
||||
{
|
||||
auto msg = s_app->GetMsg();
|
||||
if (!msg.empty())
|
||||
if (s_ws_connected)
|
||||
{
|
||||
WSSend(msg);
|
||||
auto msg = session->GetMsg();
|
||||
if (!msg.empty())
|
||||
{
|
||||
WSSend(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_app->ResetMsg();
|
||||
session->ResetMsg();
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(s_window);
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ namespace game
|
||||
IN_DEBUG3,
|
||||
IN_DEBUG4,
|
||||
IN_DEBUG5,
|
||||
IN_MENU,
|
||||
|
||||
IN__COUNT,
|
||||
};
|
||||
|
||||
@ -6,7 +6,13 @@
|
||||
|
||||
#include "vehicleview.hpp"
|
||||
|
||||
game::view::ClientSession::ClientSession(App& app) : app_(app) {}
|
||||
game::view::ClientSession::ClientSession(App& app) : app_(app)
|
||||
{
|
||||
// send login
|
||||
auto msg = BeginMsg(net::MSG_ID);
|
||||
net::PlayerName name;
|
||||
msg.Write(name);
|
||||
}
|
||||
|
||||
bool game::view::ClientSession::ProcessMessage(net::InMessage& msg)
|
||||
{
|
||||
@ -48,6 +54,17 @@ bool game::view::ClientSession::ProcessSingleMessage(net::MessageType type, net:
|
||||
}
|
||||
}
|
||||
|
||||
void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bool repeated)
|
||||
{
|
||||
|
||||
|
||||
if (repeated)
|
||||
return;
|
||||
|
||||
SendInput(in, pressed);
|
||||
|
||||
}
|
||||
|
||||
void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch)
|
||||
{
|
||||
yaw_ = glm::mod(yaw_ + delta_yaw, glm::two_pi<float>());
|
||||
@ -171,6 +188,15 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar
|
||||
GetAudioMaster().SetListenerOrientation(camera_world);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void game::view::ClientSession::SendViewAngles(float time)
|
||||
{
|
||||
if (time - last_send_time_ < 0.040f)
|
||||
@ -184,7 +210,7 @@ void game::view::ClientSession::SendViewAngles(float time)
|
||||
if (yaw_q.value == view_yaw_q_.value && pitch_q.value == view_pitch_q_.value)
|
||||
return;
|
||||
|
||||
auto msg = app_.BeginMsg(net::MSG_VIEWANGLES);
|
||||
auto msg = BeginMsg(net::MSG_VIEWANGLES);
|
||||
msg.Write(yaw_q.value);
|
||||
msg.Write(pitch_q.value);
|
||||
|
||||
|
||||
@ -8,13 +8,15 @@
|
||||
#include "gfx/renderer.hpp"
|
||||
#include "net/defs.hpp"
|
||||
#include "net/inmessage.hpp"
|
||||
#include "net/msg_producer.hpp"
|
||||
#include "game/player_input.hpp"
|
||||
|
||||
class App;
|
||||
|
||||
namespace game::view
|
||||
{
|
||||
|
||||
class ClientSession
|
||||
class ClientSession : public net::MsgProducer
|
||||
{
|
||||
public:
|
||||
ClientSession(App& app);
|
||||
@ -22,15 +24,14 @@ public:
|
||||
bool ProcessMessage(net::InMessage& msg);
|
||||
bool ProcessSingleMessage(net::MessageType type, net::InMessage& msg);
|
||||
|
||||
void Input(game::PlayerInputType in, bool pressed, bool repeated);
|
||||
void ProcessMouseMove(float delta_yaw, float delta_pitch);
|
||||
|
||||
void Update(const UpdateInfo& info);
|
||||
void Draw(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui);
|
||||
|
||||
const WorldView* GetWorld() const { return world_.get(); }
|
||||
|
||||
void GetViewInfo(glm::vec3& eye, glm::mat4& view) const;
|
||||
|
||||
audio::Master& GetAudioMaster() const;
|
||||
|
||||
private:
|
||||
@ -40,6 +41,8 @@ private:
|
||||
bool ProcessChatMsg(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);
|
||||
|
||||
private:
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
#include "context.hpp"
|
||||
|
||||
#include "assets/cache.hpp"
|
||||
|
||||
gui::Context::Context(gfx::DrawList& dlist, std::shared_ptr<const Font> default_font) :
|
||||
dlist_(dlist),
|
||||
font_(std::move(default_font)),
|
||||
va_(gfx::VA_POSITION | gfx::VA_UV | gfx::VA_COLOR, gfx::VF_CREATE_EBO | gfx::VF_DYNAMIC)
|
||||
{
|
||||
|
||||
white_tex_ = assets::CacheManager::GetTexture("data/white.png");
|
||||
}
|
||||
|
||||
void gui::Context::Begin()
|
||||
@ -15,6 +17,12 @@ void gui::Context::Begin()
|
||||
ranges_.clear();
|
||||
}
|
||||
|
||||
void gui::Context::DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color)
|
||||
{
|
||||
BeginTexture(white_tex_.get());
|
||||
PushRect(p0, glm::vec2(0.0f), p1, glm::vec2(1.0f), color);
|
||||
}
|
||||
|
||||
static uint32_t DecodeUTF8Codepoint(const char*& p, const char* end)
|
||||
{
|
||||
if (p == end)
|
||||
@ -131,7 +139,7 @@ void gui::Context::DrawText(std::string_view text, const glm::vec2& pos, uint32_
|
||||
BeginTexture(font_->GetTexture().get());
|
||||
|
||||
uint32_t cp = 0;
|
||||
const float line_height = font_->GetLineHeight();
|
||||
const float line_height = font_->GetLineHeight() * scale;
|
||||
float space_size = line_height * 0.3f;
|
||||
|
||||
glm::vec2 cursor = pos;
|
||||
@ -191,7 +199,7 @@ void gui::Context::DrawText(std::string_view text, const glm::vec2& pos, uint32_
|
||||
if (!glyph)
|
||||
continue; // Dont even have "missing" glyph, font is shit
|
||||
|
||||
glm::vec2 p0 = cursor + glyph->offset;
|
||||
glm::vec2 p0 = cursor + glyph->offset * scale;
|
||||
glm::vec2 p1 = p0 + glyph->size * scale;
|
||||
|
||||
PushRect(p0, glyph->uv0, p1, glyph->uv1, curr_color);
|
||||
@ -199,6 +207,12 @@ void gui::Context::DrawText(std::string_view text, const glm::vec2& pos, uint32_
|
||||
}
|
||||
}
|
||||
|
||||
void gui::Context::DrawTextAligned(std::string_view text, const glm::vec2& pos, const glm::vec2& align, uint32_t color, float scale)
|
||||
{
|
||||
auto size = MeasureText(text) * scale;
|
||||
DrawText(text, pos + size * align, color, scale);
|
||||
}
|
||||
|
||||
void gui::Context::Render()
|
||||
{
|
||||
va_.SetVBOData(vertices_.data(), vertices_.size() * sizeof(vertices_[0]));
|
||||
@ -221,7 +235,7 @@ void gui::Context::BeginTexture(const gfx::Texture* texture)
|
||||
return;
|
||||
|
||||
auto& range = ranges_.emplace_back();
|
||||
range.start = indices_.size();
|
||||
range.start = indices_.size() / 3;
|
||||
range.count = 0;
|
||||
range.texture = texture;
|
||||
}
|
||||
|
||||
@ -34,8 +34,11 @@ public:
|
||||
|
||||
void Begin();
|
||||
|
||||
void DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color);
|
||||
|
||||
glm::vec2 MeasureText(std::string_view text);
|
||||
void DrawText(std::string_view text, const glm::vec2& pos, uint32_t color = 0xFFFFFFFF, float scale = 1.0f);
|
||||
void DrawTextAligned(std::string_view text, const glm::vec2& pos, const glm::vec2& align, uint32_t color = 0xFFFFFFFF, float scale = 1.0f);
|
||||
|
||||
void Render();
|
||||
|
||||
@ -47,9 +50,12 @@ private:
|
||||
|
||||
private:
|
||||
gfx::DrawList& dlist_;
|
||||
std::shared_ptr<const Font> font_;
|
||||
gfx::VertexArray va_;
|
||||
|
||||
// assets
|
||||
std::shared_ptr<const Font> font_;
|
||||
std::shared_ptr<const gfx::Texture> white_tex_;
|
||||
|
||||
// building
|
||||
std::vector<GuiVertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
|
||||
130
src/gui/menu.cpp
Normal file
130
src/gui/menu.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
#include "menu.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// Menu
|
||||
|
||||
void gui::Menu::Draw(Context& ctx, const glm::vec2& pos) const
|
||||
{
|
||||
// background
|
||||
auto size = MeasureSize();
|
||||
ctx.DrawRect(pos, pos + size, 0x55000000);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void gui::Menu::Input(MenuInput in)
|
||||
{
|
||||
switch (in)
|
||||
{
|
||||
case MI_UP:
|
||||
SwitchFocus(-1);
|
||||
break;
|
||||
|
||||
case MI_DOWN:
|
||||
SwitchFocus(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!items_.empty())
|
||||
items_[focus_]->Input(in);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void gui::Menu::SwitchFocus(int dir)
|
||||
{
|
||||
if (items_.empty())
|
||||
return;
|
||||
|
||||
focus_ = (focus_ + items_.size() + dir) % items_.size();
|
||||
}
|
||||
|
||||
// ButtonMenuItem
|
||||
|
||||
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);
|
||||
args.ctx.DrawTextAligned(text_, center, glm::vec2(0.0f, -0.5f), args.focused ? COLOR_FOCUSED : COLOR_INACTIVE);
|
||||
}
|
||||
|
||||
void gui::ButtonMenuItem::Input(MenuInput in)
|
||||
{
|
||||
if (in == MI_ENTER && click_cb_)
|
||||
click_cb_();
|
||||
}
|
||||
|
||||
void gui::ButtonMenuItem::SetClickCallback(std::function<void()> click_cb)
|
||||
{
|
||||
click_cb_ = std::move(click_cb);
|
||||
}
|
||||
|
||||
// SelectMenuItem
|
||||
|
||||
gui::SelectMenuItem::SelectMenuItem(std::string text)
|
||||
: ButtonMenuItem(std::move(text))
|
||||
{
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void gui::SelectMenuItem::Input(MenuInput in)
|
||||
{
|
||||
switch (in)
|
||||
{
|
||||
case MI_LEFT:
|
||||
if (switch_cb_)
|
||||
switch_cb_(-1);
|
||||
break;
|
||||
|
||||
case MI_RIGHT:
|
||||
if (switch_cb_)
|
||||
switch_cb_(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
Super::Input(in);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void gui::SelectMenuItem::SetSwitchCallback(std::function<void(int)> switch_cb)
|
||||
{
|
||||
switch_cb_ = std::move(switch_cb);
|
||||
}
|
||||
122
src/gui/menu.hpp
Normal file
122
src/gui/menu.hpp
Normal file
@ -0,0 +1,122 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <concepts>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
enum MenuInput
|
||||
{
|
||||
MI_UP,
|
||||
MI_DOWN,
|
||||
MI_LEFT,
|
||||
MI_RIGHT,
|
||||
MI_BACK,
|
||||
MI_ENTER,
|
||||
};
|
||||
|
||||
class MenuItem;
|
||||
|
||||
struct DrawMenuItemArgs
|
||||
{
|
||||
Context& ctx;
|
||||
glm::vec2 pos;
|
||||
bool focused;
|
||||
|
||||
DrawMenuItemArgs(Context& ctx, const glm::vec2& pos, bool focused) : ctx(ctx), pos(pos), focused(focused) {}
|
||||
};
|
||||
|
||||
class Menu
|
||||
{
|
||||
public:
|
||||
Menu() = default;
|
||||
|
||||
template <std::derived_from<MenuItem> T, typename... TArgs>
|
||||
T& Add(TArgs&&... args)
|
||||
{
|
||||
auto item = std::make_unique<T>(std::forward<TArgs>(args)...);
|
||||
auto& item_ref = *item;
|
||||
items_.emplace_back(std::move(item));
|
||||
return item_ref;
|
||||
}
|
||||
|
||||
void Draw(Context& ctx, const glm::vec2& pos) const;
|
||||
void Input(MenuInput in);
|
||||
|
||||
glm::vec2 MeasureSize() const;
|
||||
|
||||
private:
|
||||
void SwitchFocus(int dir);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<MenuItem>> items_;
|
||||
size_t focus_ = 0;
|
||||
|
||||
};
|
||||
|
||||
class MenuItem
|
||||
{
|
||||
public:
|
||||
constexpr static uint32_t COLOR_INACTIVE = 0xFFFFFFFF;
|
||||
constexpr static uint32_t COLOR_FOCUSED = 0xFF00FFFF;
|
||||
|
||||
MenuItem() = default;
|
||||
|
||||
virtual void Draw(const DrawMenuItemArgs& args) const {}
|
||||
virtual void Input(MenuInput in) {}
|
||||
|
||||
const glm::vec2& GetSize() const { return size_; }
|
||||
|
||||
virtual ~MenuItem() = default;
|
||||
|
||||
protected:
|
||||
glm::vec2 size_;
|
||||
|
||||
};
|
||||
|
||||
class ButtonMenuItem : public MenuItem
|
||||
{
|
||||
public:
|
||||
using Super = MenuItem;
|
||||
|
||||
ButtonMenuItem(std::string text);
|
||||
|
||||
virtual void Draw(const DrawMenuItemArgs& args) const override;
|
||||
virtual void Input(MenuInput in) override;
|
||||
|
||||
void SetClickCallback(std::function<void()> click_cb);
|
||||
|
||||
virtual ~ButtonMenuItem() = default;
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
std::function<void()> click_cb_;
|
||||
};
|
||||
|
||||
class SelectMenuItem : public ButtonMenuItem
|
||||
{
|
||||
public:
|
||||
using Super = ButtonMenuItem;
|
||||
|
||||
SelectMenuItem(std::string text);
|
||||
|
||||
virtual void Draw(const DrawMenuItemArgs& args) const override;
|
||||
virtual void Input(MenuInput in) override;
|
||||
|
||||
void SetSwitchCallback(std::function<void(int)> switch_cb);
|
||||
void SetSelectionText(std::string select_text) { select_text_ = std::move(select_text); }
|
||||
|
||||
virtual ~SelectMenuItem() = default;
|
||||
|
||||
protected:
|
||||
std::string select_text_;
|
||||
std::function<void(int)> switch_cb_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user