--- /dev/null
+#include "ChatState.hpp"
+
+#include "Environment.hpp"
+#include "../io/event.hpp"
+
+#include <iostream>
+
+
+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);
+}
+
+}
--- /dev/null
+#ifndef BLANK_APP_CHATSTATE_HPP_
+#define BLANK_APP_CHATSTATE_HPP_
+
+#include "State.hpp"
+
+#include "../ui/TextInput.hpp"
+
+#include <string>
+
+
+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
struct State {
+ friend class Application;
friend class HeadlessApplication;
virtual void Handle(const SDL_Event &) = 0;
virtual void OnPause() { }
virtual void OnExit() { }
+ virtual void OnFocus() { }
+ virtual void OnBlur() { }
+ virtual void OnResize(Viewport &) { }
+
};
};
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;
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;
}
-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;
, 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");
// TODO: spawn
spawn_player = false;
}
+ hud.KeepMessages(false);
}
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);
env.state.Switch(&unload);
}
+void MasterState::OnLineSubmit(const std::string &line) {
+ hud.PostMessage(line);
+}
+
}
}
#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"
class MasterState
: public State
-, public ClientController {
+, public ClientController
+, public ChatState::Responder {
public:
MasterState(
void SetDebug(bool) override;
void Exit() override;
+ void OnLineSubmit(const std::string &) override;
+
private:
Config &config;
Environment &env;
PreloadState preload;
UnloadState unload;
+ ChatState chat;
};
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;
// message box
MessageBox messages;
IntervalTimer msg_timer;
+ bool msg_keep;
// crosshair
PrimitiveMesh crosshair;
Set(f, s.c_str());
}
- void Pivot(Gravity p) {
+ Gravity Pivot() const noexcept { return pivot; }
+ void Pivot(Gravity p) noexcept {
pivot = p;
dirty = true;
}
--- /dev/null
+#ifndef BLANK_UI_TEXTINPUT_HPP_
+#define BLANK_UI_TEXTINPUT_HPP_
+
+#include "Text.hpp"
+#include "../graphics/PrimitiveMesh.hpp"
+
+#include <string>
+#include <SDL.h>
+
+
+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
// 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));
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));
}
// message box
- if (msg_timer.Running()) {
+ if (msg_keep || msg_timer.Running()) {
messages.Render(viewport);
}
#include "MessageBox.hpp"
#include "Progress.hpp"
#include "Text.hpp"
+#include "TextInput.hpp"
#include "../graphics/Font.hpp"
#include "../graphics/Viewport.hpp"
#include <cstdio>
+#include <cstring>
+#include <limits>
+
+using namespace std;
namespace blank {
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));
void MessageBox::Render(Viewport &viewport) noexcept {
viewport.SetCursor(pos, grav);
- if (bg.a > std::numeric_limits<float>::epsilon()) {
+ if (bg.a > numeric_limits<float>::epsilon()) {
if (dirty) {
Recalc();
}
}
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);
}
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<float>::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();
+ }
+}
+
}