From: Daniel Karbach Date: Tue, 20 Oct 2015 14:45:26 +0000 (+0200) Subject: chat state X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=54f3f1260b95a924fcb40d9d6de3fa2e2c382f6f;p=blank.git chat state standalone only atm --- diff --git a/src/app/ChatState.cpp b/src/app/ChatState.cpp new file mode 100644 index 0000000..1eb35be --- /dev/null +++ b/src/app/ChatState.cpp @@ -0,0 +1,107 @@ +#include "ChatState.hpp" + +#include "Environment.hpp" +#include "../io/event.hpp" + +#include + + +namespace blank { + +ChatState::ChatState(Environment &env, State &parent, Responder &responder) +: env(env) +, parent(parent) +, responder(responder) +, input(env.assets.small_ui_font) { + input.Position(glm::vec3(25.0f, -25.0f, -1.0f), Gravity::SOUTH_WEST, Gravity::SOUTH_WEST); + input.Width(env.viewport.Width() - 50.0f); + input.Foreground(glm::vec4(1.0f)); + input.Background(glm::vec4(0.5f)); +} + +void ChatState::OnResume() { + OnResize(env.viewport); + input.Clear(); + input.Focus(env.viewport); +} + +void ChatState::OnPause() { + input.Blur(); +} + +void ChatState::OnResize(Viewport &viewport) { + input.Width(viewport.Width() - 50.0f); +} + + +void ChatState::Handle(const SDL_Event &e) { + switch (e.type) { + case SDL_KEYDOWN: + switch (e.key.keysym.sym) { + case SDLK_ESCAPE: + Quit(); + break; + case SDLK_KP_ENTER: + case SDLK_RETURN: + responder.OnLineSubmit(input.GetInput()); + Quit(); + break; + + case SDLK_BACKSPACE: + input.Backspace(); + break; + case SDLK_DELETE: + input.Delete(); + break; + + case SDLK_LEFT: + input.MoveBackward(); + break; + case SDLK_RIGHT: + input.MoveForward(); + break; + + case SDLK_HOME: + input.MoveBegin(); + break; + case SDLK_END: + input.MoveEnd(); + break; + + default: + break; + } + break; + + case SDL_QUIT: + env.state.PopAll(); + break; + + case SDL_TEXTINPUT: + input.Handle(e.text); + break; + + case SDL_TEXTEDITING: + std::cout << e << std::endl; + input.Handle(e.edit); + break; + + default: + break; + } +} + +void ChatState::Quit() { + env.state.PopUntil(this); +} + +void ChatState::Update(int dt) { + parent.Update(dt); +} + +void ChatState::Render(Viewport &viewport) { + parent.Render(viewport); + input.Render(viewport); +} + +} diff --git a/src/app/ChatState.hpp b/src/app/ChatState.hpp new file mode 100644 index 0000000..56f8538 --- /dev/null +++ b/src/app/ChatState.hpp @@ -0,0 +1,48 @@ +#ifndef BLANK_APP_CHATSTATE_HPP_ +#define BLANK_APP_CHATSTATE_HPP_ + +#include "State.hpp" + +#include "../ui/TextInput.hpp" + +#include + + +namespace blank { + +class Environment; + +class ChatState +: public State { + +public: + struct Responder { + virtual void OnLineSubmit(const std::string &) = 0; + }; + +public: + ChatState(Environment &env, State &parent, Responder &); + + void OnResume() override; + void OnPause() override; + + void OnResize(Viewport &) override; + + void Handle(const SDL_Event &) override; + void Update(int dt) override; + void Render(Viewport &) override; + + void Quit(); + +private: + Environment &env; + State &parent; + Responder &responder; + + TextInput input; + +}; + +} + +#endif diff --git a/src/app/State.hpp b/src/app/State.hpp index f79bc60..b6c20f6 100644 --- a/src/app/State.hpp +++ b/src/app/State.hpp @@ -11,6 +11,7 @@ class Viewport; struct State { + friend class Application; friend class HeadlessApplication; virtual void Handle(const SDL_Event &) = 0; @@ -28,6 +29,10 @@ private: virtual void OnPause() { } virtual void OnExit() { } + virtual void OnFocus() { } + virtual void OnBlur() { } + virtual void OnResize(Viewport &) { } + }; }; diff --git a/src/app/app.cpp b/src/app/app.cpp index b3b3300..aba7093 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -161,13 +161,14 @@ void Application::Handle(const SDL_Event &event) { void Application::Handle(const SDL_WindowEvent &event) { switch (event.event) { case SDL_WINDOWEVENT_FOCUS_GAINED: - env.window.GrabMouse(); + GetState().OnFocus(); break; case SDL_WINDOWEVENT_FOCUS_LOST: - env.window.ReleaseMouse(); + GetState().OnBlur(); break; case SDL_WINDOWEVENT_RESIZED: env.viewport.Resize(event.data1, event.data2); + GetState().OnResize(env.viewport); break; default: break; diff --git a/src/graphics/Viewport.hpp b/src/graphics/Viewport.hpp index a8cb69f..96fbaa4 100644 --- a/src/graphics/Viewport.hpp +++ b/src/graphics/Viewport.hpp @@ -44,9 +44,11 @@ public: void Clear() noexcept; void ClearDepth() noexcept; - void SetCursor(const glm::vec3 &); - void SetCursor(const glm::vec3 &, Gravity); - void MoveCursor(const glm::vec3 &); + glm::vec2 GetPosition(const glm::vec2 &off, Gravity grav) const noexcept; + + void SetCursor(const glm::vec3 &) noexcept; + void SetCursor(const glm::vec3 &, Gravity) noexcept; + void MoveCursor(const glm::vec3 &) noexcept; const glm::mat4 &Cursor() const noexcept { return cursor; } BlockLighting &ChunkProgram() noexcept; diff --git a/src/graphics/viewport.cpp b/src/graphics/viewport.cpp index f9558d0..9abe48f 100644 --- a/src/graphics/viewport.cpp +++ b/src/graphics/viewport.cpp @@ -165,20 +165,24 @@ void Viewport::ClearDepth() noexcept { } -void Viewport::SetCursor(const glm::vec3 &pos) { +glm::vec2 Viewport::GetPosition(const glm::vec2 &off, Gravity grav) const noexcept { + return align(grav, canv.Size(), off + canv.Offset()); +} + +void Viewport::SetCursor(const glm::vec3 &pos) noexcept { cursor[3].x = pos.x; cursor[3].y = pos.y; cursor[3].z = pos.z; } -void Viewport::SetCursor(const glm::vec3 &pos, Gravity grav) { - glm::vec2 p(align(grav, canv.Size(), glm::vec2(pos) + canv.Offset())); +void Viewport::SetCursor(const glm::vec3 &pos, Gravity grav) noexcept { + glm::vec2 p(GetPosition(glm::vec2(pos), grav)); cursor[3].x = p.x; cursor[3].y = p.y; cursor[3].z = pos.z; } -void Viewport::MoveCursor(const glm::vec3 &d) { +void Viewport::MoveCursor(const glm::vec3 &d) noexcept { cursor[3].x += d.x; cursor[3].y += d.y; cursor[3].z += d.z; diff --git a/src/standalone/MasterState.cpp b/src/standalone/MasterState.cpp index 750e491..91ee482 100644 --- a/src/standalone/MasterState.cpp +++ b/src/standalone/MasterState.cpp @@ -37,7 +37,8 @@ MasterState::MasterState( , spawner(world, res.models, env.rng) , sky(env.loader.LoadCubeMap("skybox")) , preload(env, chunk_loader, chunk_renderer) -, unload(env, world.Chunks(), save) { +, unload(env, world.Chunks(), save) +, chat(env, *this, *this) { res.Load(env.loader, "default"); if (res.models.size() < 2) { throw std::runtime_error("need at least two models to run"); @@ -74,6 +75,7 @@ void MasterState::OnResume() { // TODO: spawn spawn_player = false; } + hud.KeepMessages(false); } void MasterState::OnPause() { @@ -84,7 +86,13 @@ void MasterState::OnPause() { void MasterState::Handle(const SDL_Event &event) { switch (event.type) { case SDL_KEYDOWN: - interface.HandlePress(event.key); + // TODO: move to interface? + if (event.key.keysym.sym == SDLK_RETURN) { + env.state.Push(&chat); + hud.KeepMessages(true); + } else { + interface.HandlePress(event.key); + } break; case SDL_KEYUP: interface.HandleRelease(event.key); @@ -187,5 +195,9 @@ void MasterState::Exit() { env.state.Switch(&unload); } +void MasterState::OnLineSubmit(const std::string &line) { + hud.PostMessage(line); +} + } } diff --git a/src/standalone/MasterState.hpp b/src/standalone/MasterState.hpp index 97834e6..173c07c 100644 --- a/src/standalone/MasterState.hpp +++ b/src/standalone/MasterState.hpp @@ -7,6 +7,7 @@ #include "PreloadState.hpp" #include "UnloadState.hpp" #include "../ai/Spawner.hpp" +#include "../app/ChatState.hpp" #include "../audio/SoundBank.hpp" #include "../graphics/SkyBox.hpp" #include "../shared/WorldResources.hpp" @@ -31,7 +32,8 @@ namespace standalone { class MasterState : public State -, public ClientController { +, public ClientController +, public ChatState::Responder { public: MasterState( @@ -59,6 +61,8 @@ public: void SetDebug(bool) override; void Exit() override; + void OnLineSubmit(const std::string &) override; + private: Config &config; Environment &env; @@ -82,6 +86,7 @@ private: PreloadState preload; UnloadState unload; + ChatState chat; }; diff --git a/src/ui/HUD.hpp b/src/ui/HUD.hpp index cab8f93..7db0258 100644 --- a/src/ui/HUD.hpp +++ b/src/ui/HUD.hpp @@ -47,6 +47,8 @@ public: void PostMessage(const std::string &msg) { PostMessage(msg.c_str()); } + // whether to always render message box regardless of last post + void KeepMessages(bool k) { msg_keep = k; } void Update(int dt); void Render(Viewport &) noexcept; @@ -80,6 +82,7 @@ private: // message box MessageBox messages; IntervalTimer msg_timer; + bool msg_keep; // crosshair PrimitiveMesh crosshair; diff --git a/src/ui/Text.hpp b/src/ui/Text.hpp index 84a6190..c431873 100644 --- a/src/ui/Text.hpp +++ b/src/ui/Text.hpp @@ -24,7 +24,8 @@ public: Set(f, s.c_str()); } - void Pivot(Gravity p) { + Gravity Pivot() const noexcept { return pivot; } + void Pivot(Gravity p) noexcept { pivot = p; dirty = true; } diff --git a/src/ui/TextInput.hpp b/src/ui/TextInput.hpp new file mode 100644 index 0000000..44999fa --- /dev/null +++ b/src/ui/TextInput.hpp @@ -0,0 +1,78 @@ +#ifndef BLANK_UI_TEXTINPUT_HPP_ +#define BLANK_UI_TEXTINPUT_HPP_ + +#include "Text.hpp" +#include "../graphics/PrimitiveMesh.hpp" + +#include +#include + + +namespace blank { + +class Viewport; + +class TextInput { + +public: + explicit TextInput(const Font &); + + const std::string &GetInput() const noexcept { return input; } + + void Focus(Viewport &) noexcept; + void Blur() noexcept; + + void Clear() noexcept; + void Backspace() noexcept; + void Delete() noexcept; + + void MoveBegin() noexcept; + void MoveBackward() noexcept; + void MoveForward() noexcept; + void MoveEnd() noexcept; + + void Insert(const char *); + + bool AtBegin() const noexcept; + bool AtEnd() const noexcept; + + void Position(const glm::vec3 &p, Gravity g, Gravity pv) noexcept; + void Width(float) noexcept; + + void Foreground(const glm::vec4 &col) noexcept { fg = col; dirty_cursor = true; } + void Background(const glm::vec4 &col) noexcept { bg = col; dirty_box = true; } + + void Handle(const SDL_TextInputEvent &); + void Handle(const SDL_TextEditingEvent &); + + void Render(Viewport &); + +private: + void Refresh(); + +private: + const Font &font; + std::string input; + std::string::size_type cursor; + Text text; + + PrimitiveMesh bg_mesh; + PrimitiveMesh cursor_mesh; + + glm::vec4 bg; + glm::vec4 fg; + + glm::vec3 position; + glm::vec2 size; + Gravity gravity; + + bool active; + bool dirty_box; + bool dirty_cursor; + bool dirty_text; + +}; + +} + +#endif diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index e7e6b0c..0f82139 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -212,8 +212,11 @@ HUD::HUD(Environment &env, Config &config, const Player &player) // message box , messages(env.assets.small_ui_font) , msg_timer(5000) +, msg_keep(false) // crosshair , crosshair() { + const float ls = env.assets.small_ui_font.LineSkip(); + // "inventory" block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f)); block_transform = glm::scale(block_transform, glm::vec3(50.0f)); @@ -231,23 +234,23 @@ HUD::HUD(Environment &env, Config &config, const Player &player) counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST); counter_text.Foreground(glm::vec4(1.0f)); counter_text.Background(glm::vec4(0.5f)); - position_text.Position(glm::vec3(-25.0f, 25.0f + env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST); + position_text.Position(glm::vec3(-25.0f, 25.0f + ls, 0.0f), Gravity::NORTH_EAST); position_text.Foreground(glm::vec4(1.0f)); position_text.Background(glm::vec4(0.5f)); - orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST); + orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * ls, 0.0f), Gravity::NORTH_EAST); orientation_text.Foreground(glm::vec4(1.0f)); orientation_text.Background(glm::vec4(0.5f)); - block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST); + block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * ls, 0.0f), Gravity::NORTH_EAST); block_text.Foreground(glm::vec4(1.0f)); block_text.Background(glm::vec4(0.5f)); block_text.Set(env.assets.small_ui_font, "Block: none"); - entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST); + entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * ls, 0.0f), Gravity::NORTH_EAST); entity_text.Foreground(glm::vec4(1.0f)); entity_text.Background(glm::vec4(0.5f)); entity_text.Set(env.assets.small_ui_font, "Entity: none"); // message box - messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST); + messages.Position(glm::vec3(25.0f, -25.0f - 2 * ls, 0.0f), Gravity::SOUTH_WEST); messages.Foreground(glm::vec4(1.0f)); messages.Background(glm::vec4(0.5f)); @@ -400,7 +403,7 @@ void HUD::Render(Viewport &viewport) noexcept { } // message box - if (msg_timer.Running()) { + if (msg_keep || msg_timer.Running()) { messages.Render(viewport); } diff --git a/src/ui/widgets.cpp b/src/ui/widgets.cpp index 533703f..d05ea2f 100644 --- a/src/ui/widgets.cpp +++ b/src/ui/widgets.cpp @@ -2,11 +2,16 @@ #include "MessageBox.hpp" #include "Progress.hpp" #include "Text.hpp" +#include "TextInput.hpp" #include "../graphics/Font.hpp" #include "../graphics/Viewport.hpp" #include +#include +#include + +using namespace std; namespace blank { @@ -59,7 +64,7 @@ PrimitiveMesh::Buffer bg_buf; void MessageBox::Recalc() { size = glm::vec2(0.0f, 0.0f); for (const Text &line : lines) { - size.x = std::max(size.x, line.Size().x); + size.x = max(size.x, line.Size().x); size.y += line.Size().y; } bg_buf.FillRect(size.x, size.y, bg, align(grav, size)); @@ -70,7 +75,7 @@ void MessageBox::Recalc() { void MessageBox::Render(Viewport &viewport) noexcept { viewport.SetCursor(pos, grav); - if (bg.a > std::numeric_limits::epsilon()) { + if (bg.a > numeric_limits::epsilon()) { if (dirty) { Recalc(); } @@ -104,7 +109,7 @@ char buf[128] = { '\0' }; } void Progress::Update(int current, int total) { - std::snprintf(buf, sizeof(buf), tpl, current, total, current * 100 / total); + snprintf(buf, sizeof(buf), tpl, current, total, current * 100 / total); text.Set(font, buf); } @@ -168,4 +173,181 @@ void Text::Render(Viewport &viewport) noexcept { sprite.Draw(); } + +TextInput::TextInput(const Font &font) +: font(font) +, input() +, cursor(0) +, text() +, bg_mesh() +, cursor_mesh() +, bg(1.0f, 1.0f, 1.0f, 0.0f) +, fg(1.0f, 1.0f, 1.0f, 1.0f) +, position(0.0f) +, size(font.LineSkip()) +, gravity(Gravity::NORTH_WEST) +, active(false) +, dirty_box(true) +, dirty_cursor(true) +, dirty_text(true) { + +} + +void TextInput::Focus(Viewport &viewport) noexcept { + SDL_StartTextInput(); + active = true; + + glm::vec2 p(viewport.GetPosition(glm::vec2(position), gravity)); + SDL_Rect rect; + rect.x = p.x; + rect.y = p.y; + rect.w = size.x; + rect.h = size.y; + SDL_SetTextInputRect(&rect); +} + +void TextInput::Blur() noexcept { + SDL_StopTextInput(); + active = false; +} + +void TextInput::Clear() noexcept { + input.clear(); + cursor = 0; + dirty_text = true; +} + +void TextInput::Backspace() noexcept { + string::size_type previous(cursor); + MoveBackward(); + input.erase(cursor, previous - cursor); + dirty_text = true; +} + +void TextInput::Delete() noexcept { + string::size_type previous(cursor); + MoveForward(); + input.erase(previous, cursor - previous); + cursor = previous; + dirty_text = true; +} + +void TextInput::MoveBegin() noexcept { + cursor = 0; +} + +void TextInput::MoveBackward() noexcept { + if (AtBegin()) return; + --cursor; + while (cursor > 0 && (input[cursor] & 0xC0) == 0x80) { + --cursor; + } +} + +void TextInput::MoveForward() noexcept { + if (AtEnd()) return; + ++cursor; + while (cursor <= input.size() && (input[cursor] & 0xC0) == 0x80) { + ++cursor; + } +} + +void TextInput::MoveEnd() noexcept { + cursor = input.size(); +} + +void TextInput::Insert(const char *str) { + size_t len = strlen(str); + input.insert(cursor, str, len); + cursor += len; + dirty_text = true; +} + +bool TextInput::AtBegin() const noexcept { + return cursor == 0; +} + +bool TextInput::AtEnd() const noexcept { + return cursor == input.size(); +} + +void TextInput::Position(const glm::vec3 &p, Gravity g, Gravity pv) noexcept { + position = p; + gravity = g; + text.Pivot(pv); + dirty_box = true; +} + +void TextInput::Width(float w) noexcept { + size.x = w; + dirty_box = true; +} + +void TextInput::Handle(const SDL_TextInputEvent &e) { + Insert(e.text); +} + +void TextInput::Handle(const SDL_TextEditingEvent &e) { +} + +void TextInput::Refresh() { + if (dirty_box) { + bg_buf.FillRect(size.x, size.y, bg, align(gravity, size)); + bg_mesh.Update(bg_buf); + bg_buf.Clear(); + dirty_box = false; + } + if (dirty_cursor) { + bg_buf.Reserve(2, 2); + bg_buf.vertices.emplace_back(0.0f, 0.0f, 0.0f); + bg_buf.vertices.emplace_back(0.0f, float(font.LineSkip()), 0.0f); + bg_buf.colors.resize(2, fg); + bg_buf.indices.push_back(0); + bg_buf.indices.push_back(1); + cursor_mesh.Update(bg_buf); + bg_buf.Clear(); + dirty_cursor = false; + } + if (dirty_text) { + if (!input.empty()) { + text.Set(font, input.c_str()); + } + dirty_text = false; + } +} + +void TextInput::Render(Viewport &viewport) { + Refresh(); + viewport.SetCursor(position, gravity); + if (bg.a > numeric_limits::epsilon()) { + viewport.EnableAlphaBlending(); + PlainColor &prog = viewport.HUDColorProgram(); + prog.SetM(viewport.Cursor()); + bg_mesh.DrawTriangles(); + viewport.MoveCursor(glm::vec3(0.0f, 0.0f, -1.0f)); + } + if (!input.empty()) { + BlendedSprite &prog = viewport.SpriteProgram(); + prog.SetBG(glm::vec4(0.0f)); + prog.SetFG(fg); + prog.SetM(viewport.Cursor()); + text.Render(viewport); + } + if (active) { + glm::vec2 offset(0.0f); + if (input.empty() || AtBegin()) { + // a okay + offset = -align(text.Pivot(), glm::vec2(0.0f, font.LineSkip())); + } else if (AtEnd()) { + offset = -align(text.Pivot(), text.Size(), glm::vec2(-text.Size().x, 0.0f)); + } else { + offset = -align(text.Pivot(), text.Size(), glm::vec2(-font.TextSize(input.substr(0, cursor).c_str()).x, 0.0f)); + } + viewport.MoveCursor(glm::vec3(offset, -1.0f)); + PlainColor &prog = viewport.HUDColorProgram(); + prog.SetM(viewport.Cursor()); + cursor_mesh.DrawLines(); + } +} + }