From a1745867ffe6f7a538727d24133dd0b9351c0f9d Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 30 Mar 2020 23:32:52 -0400 Subject: [PATCH 1/9] Move all Lua initialization out of PiGui.cpp - Now LuaPiGui handles it, decoupling the PiGui implementation from lua - Made all externally-facing symbols in LuaPiGui part of the PiGUI namespace This will be renamed to PiGui and the class of the same name will become part of the namespace. --- src/Pi.cpp | 10 +++--- src/lua/Lua.cpp | 1 - src/lua/LuaBody.cpp | 15 ++++---- src/lua/LuaEngine.cpp | 4 +-- src/lua/LuaPiGui.cpp | 83 +++++++++++++++++++++++++++++++++---------- src/lua/LuaPiGui.h | 36 ++++++++++++------- src/pigui/PiGui.cpp | 53 +-------------------------- src/pigui/PiGui.h | 26 +++++--------- 8 files changed, 114 insertions(+), 114 deletions(-) diff --git a/src/Pi.cpp b/src/Pi.cpp index 22e95b0be..3f0413160 100644 --- a/src/Pi.cpp +++ b/src/Pi.cpp @@ -28,6 +28,7 @@ #include "lua/Lua.h" #include "lua/LuaConsole.h" #include "lua/LuaEvent.h" +#include "lua/LuaPiGui.h" #include "lua/LuaTimer.h" #include "profiler/Profiler.h" #include "sound/AmbientSounds.h" @@ -493,10 +494,11 @@ void LoadStep::Start() // and then load all the lua-related state once Lua's registered and online... Pi::pigui.Reset(new PiGui); Pi::pigui->Init(Pi::renderer->GetSDLWindow()); + LuaObject::RegisterClass(); // Don't render the first frame, just make sure all of our fonts are loaded Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); - Pi::pigui->RunHandler(0.01, "INIT"); + PiGUI::RunHandler(0.01, "INIT"); Pi::pigui->EndFrame(); AddStep("UI::AddContext", []() { @@ -609,7 +611,7 @@ void LoadStep::Update(float deltaTime) loader.name.c_str(), timer.milliseconds()); Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); - Pi::pigui->RunHandler(progress, "INIT"); + PiGUI::RunHandler(progress, "INIT"); Pi::pigui->Render(); } else { @@ -679,7 +681,7 @@ void MainMenu::Update(float deltaTime) Pi::intro->Draw(deltaTime); Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); - Pi::pigui->RunHandler(deltaTime, "MAINMENU"); + PiGUI::RunHandler(deltaTime, "MAINMENU"); Pi::pigui->Render(); @@ -1137,7 +1139,7 @@ void GameLoop::Update(float deltaTime) Pi::game->GetWorldView()->BeginCameraFrame(); // FIXME: major hack to work around the fact that the console is in newUI and not pigui if (!Pi::IsConsoleActive()) - Pi::pigui->RunHandler(deltaTime, "GAME"); + PiGUI::RunHandler(deltaTime, "GAME"); Pi::game->GetWorldView()->EndCameraFrame(); } diff --git a/src/lua/Lua.cpp b/src/lua/Lua.cpp index 13768e3cb..6f370cbef 100644 --- a/src/lua/Lua.cpp +++ b/src/lua/Lua.cpp @@ -107,7 +107,6 @@ namespace Lua { GameUI::Lua::Init(); SceneGraph::Lua::Init(); - LuaObject::RegisterClass(); PiGUI::Lua::Init(); // XXX load everything. for now, just modules diff --git a/src/lua/LuaBody.cpp b/src/lua/LuaBody.cpp index 7dcbed194..398b3be99 100644 --- a/src/lua/LuaBody.cpp +++ b/src/lua/LuaBody.cpp @@ -24,11 +24,12 @@ #include "Ship.h" #include "SpaceStation.h" #include "Star.h" -#include "HyperspaceCloud.h" -// Defined in LuaPiGui.h -extern bool first_body_is_more_important_than(Body*, Body*); -extern int pushOnScreenPositionDirection(lua_State *l, vector3d position); +namespace PiGUI { + // Defined in LuaPiGui.h + extern bool first_body_is_more_important_than(Body *, Body *); + extern int pushOnScreenPositionDirection(lua_State *l, vector3d position); +} // namespace PiGUI /* * Class: Body @@ -246,7 +247,7 @@ static int l_body_is_more_important_than(lua_State *l) LuaPush(l, false); return 1; } - LuaPush(l, first_body_is_more_important_than(body, other)); + LuaPush(l, PiGUI::first_body_is_more_important_than(body, other)); return 1; } /* @@ -653,7 +654,7 @@ static int l_body_get_projected_screen_position(lua_State *l) Body *b = LuaObject::CheckFromLua(1); WorldView *wv = Pi::game->GetWorldView(); vector3d p = wv->WorldSpaceToScreenSpace(b); - return pushOnScreenPositionDirection(l, p); + return PiGUI::pushOnScreenPositionDirection(l, p); } static int l_body_get_atmospheric_state(lua_State *l) @@ -685,7 +686,7 @@ static int l_body_get_target_indicator_screen_position(lua_State *l) Body *b = LuaObject::CheckFromLua(1); WorldView *wv = Pi::game->GetWorldView(); vector3d p = wv->GetTargetIndicatorScreenPosition(b); - return pushOnScreenPositionDirection(l, p); + return PiGUI::pushOnScreenPositionDirection(l, p); } static bool push_body_to_lua(Body *body) diff --git a/src/lua/LuaEngine.cpp b/src/lua/LuaEngine.cpp index 674a82e3d..e71ee8fe1 100644 --- a/src/lua/LuaEngine.cpp +++ b/src/lua/LuaEngine.cpp @@ -785,7 +785,7 @@ static int l_engine_world_space_to_screen_space(lua_State *l) { vector3d pos = LuaPull(l, 1); - TScreenSpace res = lua_world_space_to_screen_space(pos); // defined in LuaPiGui.cpp + PiGUI::TScreenSpace res = PiGUI::lua_world_space_to_screen_space(pos); // defined in LuaPiGui.cpp LuaPush(l, res._onScreen); LuaPush(l, res._screenPosition); @@ -1077,7 +1077,7 @@ static int l_engine_get_sector_map_factions(lua_State *l) lua_setfield(l, -2, "faction"); lua_pushboolean(l, hidden.count(f) == 0); lua_setfield(l, -2, "visible"); // inner table - lua_settable(l, -3); // outer table + lua_settable(l, -3); // outer table } return 1; } diff --git a/src/lua/LuaPiGui.cpp b/src/lua/LuaPiGui.cpp index ef9d92f5d..269be635d 100644 --- a/src/lua/LuaPiGui.cpp +++ b/src/lua/LuaPiGui.cpp @@ -29,6 +29,33 @@ // undef it, to avoid including yet another header that undefs it #undef RegisterClass +LuaRef m_handlers; +LuaRef PiGUI::GetHandlers() { return m_handlers; } + +LuaRef m_keys; +LuaRef PiGUI::GetKeys() { return m_keys; } + +static std::vector> m_keycodes = { + { "left", SDLK_LEFT }, + { "right", SDLK_RIGHT }, + { "up", SDLK_UP }, + { "down", SDLK_DOWN }, + { "escape", SDLK_ESCAPE }, + { "f1", SDLK_F1 }, + { "f2", SDLK_F2 }, + { "f3", SDLK_F3 }, + { "f4", SDLK_F4 }, + { "f5", SDLK_F5 }, + { "f6", SDLK_F6 }, + { "f7", SDLK_F7 }, + { "f8", SDLK_F8 }, + { "f9", SDLK_F9 }, + { "f10", SDLK_F10 }, + { "f11", SDLK_F11 }, + { "f12", SDLK_F12 }, + { "tab", SDLK_TAB }, +}; + template static Type parse_imgui_flags(lua_State *l, int index, LuaFlags &lookupTable) { @@ -77,7 +104,7 @@ void pi_lua_generic_pull(lua_State *l, int index, ImVec2 &vec) vec = ImVec2(tr.x, tr.y); } -int pushOnScreenPositionDirection(lua_State *l, vector3d position) +int PiGUI::pushOnScreenPositionDirection(lua_State *l, vector3d position) { PROFILE_SCOPED() const int width = Graphics::GetScreenWidth(); @@ -1560,7 +1587,7 @@ static int l_pigui_get_mouse_clicked_pos(lua_State *l) return 1; } -TScreenSpace lua_world_space_to_screen_space(const vector3d &pos) +PiGUI::TScreenSpace PiGUI::lua_world_space_to_screen_space(const vector3d &pos) { PROFILE_SCOPED() const WorldView *wv = Pi::game->GetWorldView(); @@ -1569,13 +1596,13 @@ TScreenSpace lua_world_space_to_screen_space(const vector3d &pos) const int height = Graphics::GetScreenHeight(); const vector3d direction = (p - vector3d(width / 2, height / 2, 0)).Normalized(); if (vector3d(0, 0, 0) == p || p.x < 0 || p.y < 0 || p.x > width || p.y > height || p.z > 0) { - return TScreenSpace(false, vector2d(0, 0), direction * (p.z > 0 ? -1 : 1)); + return PiGUI::TScreenSpace(false, vector2d(0, 0), direction * (p.z > 0 ? -1 : 1)); } else { - return TScreenSpace(true, vector2d(p.x, p.y), direction); + return PiGUI::TScreenSpace(true, vector2d(p.x, p.y), direction); } } -TScreenSpace lua_world_space_to_screen_space(const Body *body) +PiGUI::TScreenSpace lua_world_space_to_screen_space(const Body *body) { PROFILE_SCOPED() const WorldView *wv = Pi::game->GetWorldView(); @@ -1584,13 +1611,13 @@ TScreenSpace lua_world_space_to_screen_space(const Body *body) const int height = Graphics::GetScreenHeight(); const vector3d direction = (p - vector3d(width / 2, height / 2, 0)).Normalized(); if (vector3d(0, 0, 0) == p || p.x < 0 || p.y < 0 || p.x > width || p.y > height || p.z > 0) { - return TScreenSpace(false, vector2d(0, 0), direction * (p.z > 0 ? -1 : 1)); + return PiGUI::TScreenSpace(false, vector2d(0, 0), direction * (p.z > 0 ? -1 : 1)); } else { - return TScreenSpace(true, vector2d(p.x, p.y), direction); + return PiGUI::TScreenSpace(true, vector2d(p.x, p.y), direction); } } -bool first_body_is_more_important_than(Body *body, Body *other) +bool PiGUI::first_body_is_more_important_than(Body *body, Body *other) { Object::Type a = body->GetType(); @@ -1720,7 +1747,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l) const double cluster_size = LuaPull(l, 1); const double ship_max_distance = LuaPull(l, 2); - TSS_vector filtered; + PiGUI::TSS_vector filtered; filtered.reserve(Pi::game->GetSpace()->GetNumBodies()); for (Body *body : Pi::game->GetSpace()->GetBodies()) { @@ -1728,7 +1755,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l) if (body->GetType() == Object::PROJECTILE) continue; if (body->GetType() == Object::SHIP && body->GetPositionRelTo(Pi::player).Length() > ship_max_distance) continue; - const TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp + const PiGUI::TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp if (!res._onScreen) continue; filtered.emplace_back(res); filtered.back()._body = body; @@ -1757,7 +1784,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l) const Body *combat_target = Pi::game->GetPlayer()->GetCombatTarget(); const Body *setspeed_target = Pi::game->GetPlayer()->GetSetSpeedTarget(); - for (TScreenSpace &obj : filtered) { + for (PiGUI::TScreenSpace &obj : filtered) { bool inserted = false; // never collapse combat target @@ -1773,7 +1800,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l) group.m_hasNavTarget = true; group.m_mainBody = obj._body; group.m_screenCoords = obj._screenPosition; - } else if (!group.m_hasNavTarget && first_body_is_more_important_than(obj._body, group.m_mainBody)) { + } else if (!group.m_hasNavTarget && PiGUI::first_body_is_more_important_than(obj._body, group.m_mainBody)) { group.m_mainBody = obj._body; group.m_screenCoords = obj._screenPosition; } @@ -1797,7 +1824,7 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l) for (GroupInfo &group : groups) { std::sort(begin(group.m_bodies), end(group.m_bodies), [](Body *a, Body *b) { - return first_body_is_more_important_than(a, b); + return PiGUI::first_body_is_more_important_than(a, b); }); } @@ -1826,19 +1853,19 @@ static int l_pigui_get_projected_bodies_grouped(lua_State *l) static int l_pigui_get_projected_bodies(lua_State *l) { PROFILE_SCOPED() - TSS_vector filtered; + PiGUI::TSS_vector filtered; filtered.reserve(Pi::game->GetSpace()->GetNumBodies()); for (Body *body : Pi::game->GetSpace()->GetBodies()) { if (body == Pi::game->GetPlayer()) continue; if (body->GetType() == Object::PROJECTILE) continue; - const TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp + const PiGUI::TScreenSpace res = lua_world_space_to_screen_space(body); // defined in LuaPiGui.cpp if (!res._onScreen) continue; filtered.emplace_back(res); filtered.back()._body = body; } LuaTable result(l, 0, filtered.size()); - for (TScreenSpace &res : filtered) { + for (PiGUI::TScreenSpace &res : filtered) { LuaTable object(l, 0, 3); object.Set("onscreen", res._onScreen); @@ -1942,7 +1969,7 @@ static int l_attr_handlers(lua_State *l) { PROFILE_SCOPED() PiGui *pigui = LuaObject::CheckFromLua(1); - pigui->GetHandlers().PushCopyToStack(); + PiGUI::GetHandlers().PushCopyToStack(); return 1; } @@ -1950,7 +1977,7 @@ static int l_attr_keys(lua_State *l) { PROFILE_SCOPED() PiGui *pigui = LuaObject::CheckFromLua(1); - pigui->GetKeys().PushCopyToStack(); + PiGUI::GetKeys().PushCopyToStack(); return 1; } @@ -2467,6 +2494,16 @@ static int l_pigui_push_text_wrap_pos(lua_State *l) return 0; } +void PiGUI::RunHandler(double delta, std::string handler) +{ + PROFILE_SCOPED() + ScopedTable t(m_handlers); + if (t.Get(handler)) { + t.Call(handler, delta); + Pi::renderer->CheckRenderErrors(__FUNCTION__, __LINE__); + } +} + template <> const char *LuaObject::s_type = "PiGui"; @@ -2629,4 +2666,14 @@ void LuaObject::RegisterClass() imguiStyleVarTable.Register(l, "ImGuiStyleVar"); imguiWindowFlagsTable.Register(l, "ImGuiWindowFlags"); imguiHoveredFlagsTable.Register(l, "ImGuiHoveredFlags"); + + lua_newtable(l); + m_handlers = LuaRef(l, -1); + + lua_newtable(l); + m_keys = LuaRef(l, -1); + LuaTable keys(l, -1); + for (auto p : m_keycodes) { + keys.Set(p.first, p.second); + } } diff --git a/src/lua/LuaPiGui.h b/src/lua/LuaPiGui.h index a1734080e..e4b436966 100644 --- a/src/lua/LuaPiGui.h +++ b/src/lua/LuaPiGui.h @@ -3,6 +3,7 @@ #ifndef _LUAPIGUI_H #define _LUAPIGUI_H + #include "LuaObject.h" #include "LuaPushPull.h" @@ -11,19 +12,30 @@ class Body; -bool first_body_is_more_important_than(Body* body, Body* other); +namespace PiGUI { + bool first_body_is_more_important_than(Body *body, Body *other); -struct TScreenSpace -{ - TScreenSpace(const bool onScreen, const vector2d &screenPos, const vector3d &direction) : _onScreen(onScreen), _screenPosition(screenPos), _direction(direction) {} - bool _onScreen; - vector2d _screenPosition; - vector3d _direction; - Body *_body; -}; + struct TScreenSpace { + TScreenSpace(const bool onScreen, const vector2d &screenPos, const vector3d &direction) : + _onScreen(onScreen), _screenPosition(screenPos), _direction(direction) {} + bool _onScreen; + vector2d _screenPosition; + vector3d _direction; + Body *_body; + }; -typedef std::vector TSS_vector; + typedef std::vector TSS_vector; + + int pushOnScreenPositionDirection(lua_State *l, vector3d position); + TScreenSpace lua_world_space_to_screen_space(const vector3d &pos); + + // Get registered PiGui handlers. + LuaRef GetHandlers(); + // Get a table of key name to SDL-keycode mappings + LuaRef GetKeys(); + + // Run a lua PiGui handler. + void RunHandler(double delta, std::string handler = "GAME"); +} // namespace PiGUI -int pushOnScreenPositionDirection(lua_State *l, vector3d position); -TScreenSpace lua_world_space_to_screen_space(const vector3d &pos); #endif diff --git a/src/pigui/PiGui.cpp b/src/pigui/PiGui.cpp index 958f2a14c..7e9732df4 100644 --- a/src/pigui/PiGui.cpp +++ b/src/pigui/PiGui.cpp @@ -26,35 +26,6 @@ std::vector PiGui::m_svg_textures; -static int to_keycode(int key) -{ - /*if(key & SDLK_SCANCODE_MASK) { - return (key & ~SDLK_SCANCODE_MASK) | 0x100; - }*/ - return key; -} - -static std::vector> keycodes = { - { "left", to_keycode(SDLK_LEFT) }, - { "right", to_keycode(SDLK_RIGHT) }, - { "up", to_keycode(SDLK_UP) }, - { "down", to_keycode(SDLK_DOWN) }, - { "escape", to_keycode(SDLK_ESCAPE) }, - { "f1", to_keycode(SDLK_F1) }, - { "f2", to_keycode(SDLK_F2) }, - { "f3", to_keycode(SDLK_F3) }, - { "f4", to_keycode(SDLK_F4) }, - { "f5", to_keycode(SDLK_F5) }, - { "f6", to_keycode(SDLK_F6) }, - { "f7", to_keycode(SDLK_F7) }, - { "f8", to_keycode(SDLK_F8) }, - { "f9", to_keycode(SDLK_F9) }, - { "f10", to_keycode(SDLK_F10) }, - { "f11", to_keycode(SDLK_F11) }, - { "f12", to_keycode(SDLK_F12) }, - { "tab", to_keycode(SDLK_TAB) }, -}; - ImTextureID PiGui::RenderSVG(std::string svgFilename, int width, int height) { PROFILE_SCOPED() @@ -192,18 +163,6 @@ void PiDefaultStyle(ImGuiStyle &style) void PiGui::Init(SDL_Window *window) { PROFILE_SCOPED() - m_handlers.Unref(); - - lua_State *l = Lua::manager->GetLuaState(); - lua_newtable(l); - m_handlers = LuaRef(l, -1); - - lua_newtable(l); - m_keys = LuaRef(l, -1); - LuaTable keys(l, -1); - for (auto p : keycodes) { - keys.Set(p.first, p.second); - } IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -402,16 +361,6 @@ void PiGui::NewFrame(SDL_Window *window) ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow); } -void PiGui::RunHandler(double delta, std::string handler) -{ - PROFILE_SCOPED() - ScopedTable t(m_handlers); - if (t.Get(handler)) { - t.Call(handler, delta); - Pi::renderer->CheckRenderErrors(__FUNCTION__, __LINE__); - } -} - void PiGui::EndFrame() { PROFILE_SCOPED() @@ -719,7 +668,7 @@ bool PiGui::ButtonImageSized(ImTextureID user_texture_id, const ImVec2 &size, co return pressed; } -void PiGui::Cleanup() +void PiGui::Uninit() { PROFILE_SCOPED() for (auto tex : m_svg_textures) { diff --git a/src/pigui/PiGui.h b/src/pigui/PiGui.h index 8ce5b4ac2..21d10d530 100644 --- a/src/pigui/PiGui.h +++ b/src/pigui/PiGui.h @@ -1,13 +1,15 @@ // Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt +#pragma once + #include "FileSystem.h" #include "RefCounted.h" #include "graphics/opengl/RendererGL.h" #include "imgui/imgui.h" -#include "lua/Lua.h" -#include "lua/LuaRef.h" -#include "lua/LuaTable.h" + +#include "utils.h" + #include class PiFace { @@ -78,12 +80,6 @@ class PiGui : public RefCounted { public: PiGui(); - LuaRef GetHandlers() const { return m_handlers; } - - LuaRef GetKeys() const { return m_keys; } - - void RunHandler(double delta, std::string handler = "GAME"); - // Call at the start of every frame. Calls ImGui::NewFrame() internally. void NewFrame(SDL_Window *window); @@ -97,12 +93,8 @@ public: ImFont *GetFont(const std::string &name, int size); - void Uninit() - { - Cleanup(); - m_handlers.Unref(); - m_keys.Unref(); - } + void Uninit(); + ImFont *AddFont(const std::string &name, int size); void AddGlyph(ImFont *font, unsigned short glyph); @@ -124,17 +116,15 @@ public: { return ImGui::GetIO().WantCaptureKeyboard; } + static int RadialPopupSelectMenu(const ImVec2 ¢er, std::string popup_id, int mouse_button, std::vector tex_ids, std::vector> uvs, unsigned int size, std::vector tooltips); static bool CircularSlider(const ImVec2 ¢er, float *v, float v_min, float v_max); - void Cleanup(); static bool LowThrustButton(const char *label, const ImVec2 &size_arg, int thrust_level, const ImVec4 &bg_col, int frame_padding, ImColor gauge_fg, ImColor gauge_bg); static bool ButtonImageSized(ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &imgSize, const ImVec2 &uv0, const ImVec2 &uv1, int frame_padding, const ImVec4 &bg_col, const ImVec4 &tint_col); static void ThrustIndicator(const std::string &id_string, const ImVec2 &size, const ImVec4 &thrust, const ImVec4 &velocity, const ImVec4 &bg_col, int frame_padding, ImColor vel_fg, ImColor vel_bg, ImColor thrust_fg, ImColor thrust_bg); private: - LuaRef m_handlers; - LuaRef m_keys; static std::vector m_svg_textures; }; From e584780ce9103a407610706abe650291cfd43a88 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 31 Mar 2020 00:59:44 -0400 Subject: [PATCH 2/9] Move Input and PiGui handling to GuiApplication At the moment, the lifecycle is responsible for calling HandleEvents and rendering PiGui. This can change once legacy code no longer depends on HandleEvents running after View::Draw3D(). Added virtual HandleEvent() call to dispatch to legacy UI code. This is suboptimal; we need a better way that doesn't involve virtual function calls. Use std::function or a better, lower-overhead delegate library --- src/Input.cpp | 21 ++++-- src/Input.h | 10 +-- src/Pi.cpp | 142 +++++++++++------------------------- src/Pi.h | 4 +- src/core/Application.cpp | 4 + src/core/Application.h | 2 - src/core/GuiApplication.cpp | 73 ++++++++++++++++++ src/core/GuiApplication.h | 31 +++++++- src/lua/LuaEngine.cpp | 2 +- 9 files changed, 175 insertions(+), 114 deletions(-) diff --git a/src/Input.cpp b/src/Input.cpp index 6895c3b98..2c8fee352 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -9,7 +9,13 @@ #include -void Input::Init(GameConfig *config) +Input::Input(const GameConfig *config) : + m_capturingMouse(false), + mouseYInvert(false), + joystickEnabled(true), + keyModState(0), + mouseButton(), + mouseMotion() { joystickEnabled = (config->Int("EnableJoystick")) ? true : false; mouseYInvert = (config->Int("InvertMouseY")) ? true : false; @@ -22,8 +28,8 @@ void Input::InitGame() //reset input states keyState.clear(); keyModState = 0; - std::fill(mouseButton, mouseButton + COUNTOF(mouseButton), 0); - std::fill(mouseMotion, mouseMotion + COUNTOF(mouseMotion), 0); + mouseButton.fill(0); + mouseMotion.fill(0); for (std::map::iterator stick = joysticks.begin(); stick != joysticks.end(); ++stick) { JoystickState &state = stick->second; std::fill(state.buttons.begin(), state.buttons.end(), false); @@ -32,6 +38,11 @@ void Input::InitGame() } } +void Input::NewFrame() +{ + mouseMotion.fill(0); +} + InputResponse Input::InputFrame::ProcessSDLEvent(SDL_Event &event) { bool matched = false; @@ -130,14 +141,14 @@ void Input::HandleSDLEvent(SDL_Event &event) onKeyRelease.emit(&event.key.keysym); break; case SDL_MOUSEBUTTONDOWN: - if (event.button.button < COUNTOF(mouseButton)) { + if (event.button.button < mouseButton.size()) { mouseButton[event.button.button] = 1; onMouseButtonDown.emit(event.button.button, event.button.x, event.button.y); } break; case SDL_MOUSEBUTTONUP: - if (event.button.button < COUNTOF(mouseButton)) { + if (event.button.button < mouseButton.size()) { mouseButton[event.button.button] = 0; onMouseButtonUp.emit(event.button.button, event.button.x, event.button.y); diff --git a/src/Input.h b/src/Input.h index 157a7abea..e9dfc0e35 100644 --- a/src/Input.h +++ b/src/Input.h @@ -16,10 +16,10 @@ class Input { friend class Pi; public: - Input() = default; - void Init(GameConfig *config); + Input(const GameConfig *config); void InitGame(); void HandleSDLEvent(SDL_Event &ev); + void NewFrame(); // The Page->Group->Binding system serves as a thin veneer for the UI to make // sane reasonings about how to structure the Options dialog. @@ -132,7 +132,7 @@ public: void GetMouseMotion(int motion[2]) { - memcpy(motion, mouseMotion, sizeof(int) * 2); + std::copy_n(mouseMotion.data(), mouseMotion.size(), motion); } // Capturing the mouse hides the cursor, puts the mouse into relative mode, @@ -156,8 +156,8 @@ private: std::map keyState; int keyModState; - char mouseButton[6]; - int mouseMotion[2]; + std::array mouseButton; + std::array mouseMotion; bool m_capturingMouse; bool joystickEnabled; diff --git a/src/Pi.cpp b/src/Pi.cpp index 3f0413160..f339e686c 100644 --- a/src/Pi.cpp +++ b/src/Pi.cpp @@ -134,7 +134,7 @@ float Pi::amountOfBackgroundStarsDisplayed = 1.0f; bool Pi::DrawGUI = true; Graphics::Renderer *Pi::renderer; RefCountedPtr Pi::ui; -RefCountedPtr Pi::pigui; +PiGui *Pi::pigui = nullptr; ModelCache *Pi::modelCache; Intro *Pi::intro; SDLGraphics *Pi::sdl; @@ -362,8 +362,7 @@ void Pi::App::Startup() Pi::rng.IncRefCount(); // so nothing tries to free it Pi::rng.seed(time(0)); - Pi::input = new Input(); - Pi::input->Init(Pi::config); + Pi::input = StartupInput(config); Pi::input->onKeyPress.connect(sigc::ptr_fun(&Pi::HandleKeyDown)); // we can only do bindings once joysticks are initialised. @@ -372,6 +371,8 @@ void Pi::App::Startup() RegisterInputBindings(); + Pi::pigui = StartupPiGui(); + // FIXME: move these into the appropriate class! navTunnelDisplayed = (config->Int("DisplayNavTunnel")) ? true : false; speedLinesDisplayed = (config->Int("SpeedLines")) ? true : false; @@ -439,9 +440,10 @@ void Pi::App::Shutdown() BaseSphere::Uninit(); FaceParts::Uninit(); Graphics::Uninit(); - Pi::pigui->Uninit(); + + ShutdownPiGui(); + Pi::pigui = nullptr; Pi::ui.Reset(0); - Pi::pigui.Reset(0); Lua::UninitModules(); Lua::Uninit(); Gui::Uninit(); @@ -453,6 +455,9 @@ void Pi::App::Shutdown() ShutdownRenderer(); Pi::renderer = nullptr; + ShutdownInput(); + Pi::input = nullptr; + delete Pi::config; delete Pi::planner; asyncJobQueue.reset(); @@ -492,8 +497,6 @@ void LoadStep::Start() // normal init flow, or drawing the init screen in C++ instead? // Ideally we can initialize the ImGui related parts of pigui as soon as the renderer is online, // and then load all the lua-related state once Lua's registered and online... - Pi::pigui.Reset(new PiGui); - Pi::pigui->Init(Pi::renderer->GetSDLWindow()); LuaObject::RegisterClass(); // Don't render the first frame, just make sure all of our fonts are loaded @@ -645,38 +648,7 @@ void MainMenu::Start() void MainMenu::Update(float deltaTime) { - SDL_Event event; - while (SDL_PollEvent(&event)) { - if (event.type == SDL_QUIT) - Pi::RequestQuit(); - else { - Pi::pigui->ProcessEvent(&event); - - if (Pi::pigui->WantCaptureMouse()) { - // don't process mouse event any further, imgui already handled it - switch (event.type) { - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - case SDL_MOUSEWHEEL: - case SDL_MOUSEMOTION: - continue; - default: break; - } - } - if (Pi::pigui->WantCaptureKeyboard()) { - // don't process keyboard event any further, imgui already handled it - switch (event.type) { - case SDL_KEYDOWN: - case SDL_KEYUP: - case SDL_TEXTINPUT: - continue; - default: break; - } - } - - Pi::input->HandleSDLEvent(event); - } - } + Pi::GetApp()->HandleEvents(); Pi::intro->Draw(deltaTime); @@ -849,12 +821,13 @@ void Pi::HandleEscKey() } } -void Pi::App::HandleEvents() +// Return true if the event has been handled and shouldn't be passed through +// to the normal input system. +bool Pi::App::HandleEvent(SDL_Event &event) { PROFILE_SCOPED() - SDL_Event event; - // XXX for most keypresses SDL will generate KEYUP/KEYDOWN and TEXTINPUT + // HACK for most keypresses SDL will generate KEYUP/KEYDOWN and TEXTINPUT // events. keybindings run off KEYUP/KEYDOWN. the console is opened/closed // via keybinding. the console TextInput widget uses TEXTINPUT events. thus // after switching the console, the stray TEXTINPUT event causes the @@ -862,64 +835,37 @@ void Pi::App::HandleEvents() // this by setting this flag if the console was switched. if its set, we // swallow the TEXTINPUT event this hack must remain until we have a // unified input system - bool skipTextInput = false; + // This is safely able to be removed once GUI and newUI are gone + static bool skipTextInput = false; - Pi::input->mouseMotion[0] = Pi::input->mouseMotion[1] = 0; - while (SDL_PollEvent(&event)) { - if (event.type == SDL_QUIT) { - Pi::RequestQuit(); - } - - Pi::pigui->ProcessEvent(&event); - - // Input system takes priority over mouse events when capturing the mouse - if (Pi::pigui->WantCaptureMouse() && !Pi::input->IsCapturingMouse()) { - // don't process mouse event any further, imgui already handled it - switch (event.type) { - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - case SDL_MOUSEWHEEL: - case SDL_MOUSEMOTION: - continue; - default: break; - } - } - if (Pi::pigui->WantCaptureKeyboard()) { - // don't process keyboard event any further, imgui already handled it - switch (event.type) { - case SDL_KEYDOWN: - case SDL_KEYUP: - case SDL_TEXTINPUT: - continue; - default: break; - } - } - - if (skipTextInput && event.type == SDL_TEXTINPUT) { - skipTextInput = false; - continue; - } - if (ui->DispatchSDLEvent(event)) - continue; - - bool consoleActive = Pi::IsConsoleActive(); - if (!consoleActive) { - KeyBindings::DispatchSDLEvent(&event); - if (currentView) - currentView->HandleSDLEvent(event); - } else - KeyBindings::toggleLuaConsole.CheckSDLEventAndDispatch(&event); - if (consoleActive != Pi::IsConsoleActive()) { - skipTextInput = true; - continue; - } - - if (Pi::IsConsoleActive()) - continue; - - Gui::HandleSDLEvent(&event); - input->HandleSDLEvent(event); + if (skipTextInput && event.type == SDL_TEXTINPUT) { + skipTextInput = false; + return true; } + + if (ui->DispatchSDLEvent(event)) + return true; + + bool consoleActive = Pi::IsConsoleActive(); + if (!consoleActive) { + KeyBindings::DispatchSDLEvent(&event); + if (currentView) + currentView->HandleSDLEvent(event); + } else { + KeyBindings::toggleLuaConsole.CheckSDLEventAndDispatch(&event); + } + + if (consoleActive != Pi::IsConsoleActive()) { + skipTextInput = true; + return true; + } + + if (Pi::IsConsoleActive()) + return true; + + Gui::HandleSDLEvent(&event); + + return false; } void Pi::App::HandleRequests() diff --git a/src/Pi.h b/src/Pi.h index 08f13202c..89d3313a7 100644 --- a/src/Pi.h +++ b/src/Pi.h @@ -101,7 +101,7 @@ public: void RunJobs(); void HandleRequests(); - void HandleEvents(); + bool HandleEvent(SDL_Event &ev) override; private: // msgs/requests that can be posted which the game processes at the end of a game loop in HandleRequests @@ -170,7 +170,7 @@ public: #endif static RefCountedPtr ui; - static RefCountedPtr pigui; + static PiGui *pigui; static Random rng; static int statSceneTris; diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 420be1fd2..6ffdd45af 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -4,6 +4,7 @@ #include "Application.h" #include "FileSystem.h" #include "OS.h" +#include "SDL.h" #include "profiler/Profiler.h" #include "utils.h" @@ -33,11 +34,14 @@ void Application::Startup() #ifdef PIONEER_PROFILER FileSystem::userFiles.MakeDirectory("profiler"); #endif + + SDL_Init(SDL_INIT_EVENTS); } void Application::Shutdown() { FileSystem::Uninit(); + SDL_Quit(); } bool Application::StartLifecycle() diff --git a/src/core/Application.h b/src/core/Application.h index 15ad0a21e..063d04fdc 100644 --- a/src/core/Application.h +++ b/src/core/Application.h @@ -40,8 +40,6 @@ public: m_nextLifecycle = l; } - Application *GetApp(); - private: // set to true when you want to accumulate all updates in a lifecycle into a single profile frame bool m_profilerAccumulate = false; diff --git a/src/core/GuiApplication.cpp b/src/core/GuiApplication.cpp index 4e315bf11..ed0f8a452 100644 --- a/src/core/GuiApplication.cpp +++ b/src/core/GuiApplication.cpp @@ -100,6 +100,54 @@ Graphics::RenderTarget *GuiApplication::CreateRenderTarget(const Graphics::Setti return nullptr; } +void GuiApplication::HandleEvents() +{ + PROFILE_SCOPED() + SDL_Event event; + + // FIXME: input state is right before handling updates because + // legacy UI code needs to run before input does. + // When HandleEvents() is moved to BeginFrame / PreUpdate, this call should go with it + m_input->NewFrame(); + + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + RequestQuit(); + } + + m_pigui->ProcessEvent(&event); + + // Input system takes priority over mouse events when capturing the mouse + if (m_pigui->WantCaptureMouse() && !m_input->IsCapturingMouse()) { + // don't process mouse event any further, imgui already handled it + switch (event.type) { + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEWHEEL: + case SDL_MOUSEMOTION: + continue; + default: break; + } + } + if (m_pigui->WantCaptureKeyboard()) { + // don't process keyboard event any further, imgui already handled it + switch (event.type) { + case SDL_KEYDOWN: + case SDL_KEYUP: + case SDL_TEXTINPUT: + continue; + default: break; + } + } + + // TODO: virtual method dispatch for each event isn't great. Let's find a better solution + if (HandleEvent(event)) + continue; + + m_input->HandleSDLEvent(event); + } +} + Graphics::Renderer *GuiApplication::StartupRenderer(const GameConfig *config, bool hidden) { PROFILE_SCOPED() @@ -145,3 +193,28 @@ void GuiApplication::ShutdownRenderer() SDL_QuitSubSystem(SDL_INIT_VIDEO); } + +Input *GuiApplication::StartupInput(const GameConfig *config) +{ + m_input.reset(new Input(config)); + + return m_input.get(); +} + +void GuiApplication::ShutdownInput() +{ + m_input.reset(); +} + +PiGui *GuiApplication::StartupPiGui() +{ + m_pigui.Reset(new PiGui()); + m_pigui->Init(m_renderer->GetSDLWindow()); + return m_pigui.Get(); +} + +void GuiApplication::ShutdownPiGui() +{ + m_pigui->Uninit(); + m_pigui.Reset(); +} diff --git a/src/core/GuiApplication.h b/src/core/GuiApplication.h index 588d31327..07bed2e49 100644 --- a/src/core/GuiApplication.h +++ b/src/core/GuiApplication.h @@ -5,7 +5,10 @@ #include "Application.h" #include "GameConfig.h" +#include "Input.h" #include "RefCounted.h" +#include "SDL_events.h" +#include "pigui/PiGui.h" #include "graphics/RenderState.h" #include "graphics/RenderTarget.h" @@ -17,9 +20,11 @@ public: Application(), m_applicationTitle(title) {} -protected: Graphics::Renderer *GetRenderer() { return m_renderer.get(); } + Input *GetInput() { return m_input.get(); } + PiGui *GetPiGui() { return m_pigui.Get(); } +protected: // Called at the end of the frame automatically, blits the RT onto the application // framebuffer void DrawRenderTarget(); @@ -27,9 +32,21 @@ protected: // Call this from your Startup() method Graphics::Renderer *StartupRenderer(const GameConfig *config, bool hidden = false); + // Call this from your Startup() method + Input *StartupInput(const GameConfig *config); + + // Call this from your Startup() method + PiGui *StartupPiGui(); + // Call this from your Shutdown() method void ShutdownRenderer(); + // Call this from your Shutdown() method + void ShutdownInput(); + + // Call this from your shutdown() method + void ShutdownPiGui(); + // Hook to bind the RT and clear the screen. // If you override BeginFrame, make sure you call this. void BeginFrame() override; @@ -38,9 +55,21 @@ protected: // If you override EndFrame, make sure you call this. void EndFrame() override; + // Consume events from SDL and dispatch to pigui / input + void HandleEvents(); + + // Override point for classes to add custom event handling + virtual bool HandleEvent(SDL_Event &ev) { return false; } + + // Override point to handle an application quit notification + virtual void HandleQuit(SDL_QuitEvent &ev) { RequestQuit(); } + private: Graphics::RenderTarget *CreateRenderTarget(const Graphics::Settings &settings); + RefCountedPtr m_pigui; + std::unique_ptr m_input; + std::string m_applicationTitle; std::unique_ptr m_renderer; diff --git a/src/lua/LuaEngine.cpp b/src/lua/LuaEngine.cpp index e71ee8fe1..5fee1b387 100644 --- a/src/lua/LuaEngine.cpp +++ b/src/lua/LuaEngine.cpp @@ -116,7 +116,7 @@ static int l_engine_attr_ui(lua_State *l) */ static int l_engine_attr_pigui(lua_State *l) { - LuaObject::PushToLua(Pi::pigui.Get()); + LuaObject::PushToLua(Pi::pigui); return 1; } From 7b3a69a38513f2a7c4e7ca634a30efd670f45243 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 31 Mar 2020 02:03:48 -0400 Subject: [PATCH 3/9] Rename PiGui -> PiGui::Instance Make PiGui a namespace for easier method scoping, allows us to be more flexible PiGui::Instance only has the bare minimum of methods needed to run ImGui Move PiGui RegisterClass call to pigui/PiGuiLua - we'll further unify LuaPiGui and PiGuiLua later. --- src/Pi.cpp | 9 +- src/Pi.h | 7 +- src/core/GuiApplication.cpp | 9 +- src/core/GuiApplication.h | 6 +- src/lua/Lua.cpp | 2 - src/lua/LuaEngine.cpp | 2 +- src/lua/LuaPiGui.cpp | 58 +---- src/lua/LuaPiGui.h | 5 - src/pigui/PiGui.cpp | 436 +++++++----------------------------- src/pigui/PiGui.h | 192 +++++++++------- src/pigui/PiGuiLua.cpp | 48 ++++ src/pigui/PiGuiLua.h | 10 +- src/pigui/PiGuiSandbox.cpp | 2 +- src/pigui/Widgets.cpp | 303 +++++++++++++++++++++++++ 14 files changed, 578 insertions(+), 511 deletions(-) create mode 100644 src/pigui/Widgets.cpp diff --git a/src/Pi.cpp b/src/Pi.cpp index f339e686c..2b346127b 100644 --- a/src/Pi.cpp +++ b/src/Pi.cpp @@ -25,6 +25,7 @@ #include "NavLights.h" #include "OS.h" #include "core/GuiApplication.h" +#include "graphics/opengl/RendererGL.h" #include "lua/Lua.h" #include "lua/LuaConsole.h" #include "lua/LuaEvent.h" @@ -134,7 +135,7 @@ float Pi::amountOfBackgroundStarsDisplayed = 1.0f; bool Pi::DrawGUI = true; Graphics::Renderer *Pi::renderer; RefCountedPtr Pi::ui; -PiGui *Pi::pigui = nullptr; +PiGui::Instance *Pi::pigui = nullptr; ModelCache *Pi::modelCache; Intro *Pi::intro; SDLGraphics *Pi::sdl; @@ -441,6 +442,7 @@ void Pi::App::Shutdown() FaceParts::Uninit(); Graphics::Uninit(); + PiGUI::Lua::Uninit(); ShutdownPiGui(); Pi::pigui = nullptr; Pi::ui.Reset(0); @@ -495,9 +497,8 @@ void LoadStep::Start() // TODO: Get the lua state responsible for drawing the init progress up as fast as possible // Investigate using a pigui-only Lua state that we can initialize without depending on // normal init flow, or drawing the init screen in C++ instead? - // Ideally we can initialize the ImGui related parts of pigui as soon as the renderer is online, - // and then load all the lua-related state once Lua's registered and online... - LuaObject::RegisterClass(); + // Loads just the PiGui class and PiGui-related modules + PiGUI::Lua::Init(); // Don't render the first frame, just make sure all of our fonts are loaded Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); diff --git a/src/Pi.h b/src/Pi.h index 89d3313a7..f3966a297 100644 --- a/src/Pi.h +++ b/src/Pi.h @@ -16,6 +16,10 @@ #include #include +namespace PiGui { + class Instance; +} //namespace PiGui + class Game; class GameConfig; @@ -26,7 +30,6 @@ class LuaNameGen; class LuaTimer; class ModelCache; class ObjectViewerView; -class PiGui; class Player; class SystemPath; class TransferPlanner; @@ -170,7 +173,7 @@ public: #endif static RefCountedPtr ui; - static PiGui *pigui; + static PiGui::Instance *pigui; static Random rng; static int statSceneTris; diff --git a/src/core/GuiApplication.cpp b/src/core/GuiApplication.cpp index ed0f8a452..9f4db5547 100644 --- a/src/core/GuiApplication.cpp +++ b/src/core/GuiApplication.cpp @@ -12,6 +12,7 @@ #include "graphics/RenderTarget.h" #include "graphics/Renderer.h" #include "graphics/Texture.h" +#include "pigui/PiGui.h" #include "utils.h" #include "versioningInfo.h" @@ -118,7 +119,7 @@ void GuiApplication::HandleEvents() m_pigui->ProcessEvent(&event); // Input system takes priority over mouse events when capturing the mouse - if (m_pigui->WantCaptureMouse() && !m_input->IsCapturingMouse()) { + if (PiGui::WantCaptureMouse() && !m_input->IsCapturingMouse()) { // don't process mouse event any further, imgui already handled it switch (event.type) { case SDL_MOUSEBUTTONDOWN: @@ -129,7 +130,7 @@ void GuiApplication::HandleEvents() default: break; } } - if (m_pigui->WantCaptureKeyboard()) { + if (PiGui::WantCaptureKeyboard()) { // don't process keyboard event any further, imgui already handled it switch (event.type) { case SDL_KEYDOWN: @@ -206,9 +207,9 @@ void GuiApplication::ShutdownInput() m_input.reset(); } -PiGui *GuiApplication::StartupPiGui() +PiGui::Instance *GuiApplication::StartupPiGui() { - m_pigui.Reset(new PiGui()); + m_pigui.Reset(new PiGui::Instance()); m_pigui->Init(m_renderer->GetSDLWindow()); return m_pigui.Get(); } diff --git a/src/core/GuiApplication.h b/src/core/GuiApplication.h index 07bed2e49..f46c1922c 100644 --- a/src/core/GuiApplication.h +++ b/src/core/GuiApplication.h @@ -22,7 +22,7 @@ public: Graphics::Renderer *GetRenderer() { return m_renderer.get(); } Input *GetInput() { return m_input.get(); } - PiGui *GetPiGui() { return m_pigui.Get(); } + PiGui::Instance *GetPiGui() { return m_pigui.Get(); } protected: // Called at the end of the frame automatically, blits the RT onto the application @@ -36,7 +36,7 @@ protected: Input *StartupInput(const GameConfig *config); // Call this from your Startup() method - PiGui *StartupPiGui(); + PiGui::Instance *StartupPiGui(); // Call this from your Shutdown() method void ShutdownRenderer(); @@ -67,7 +67,7 @@ protected: private: Graphics::RenderTarget *CreateRenderTarget(const Graphics::Settings &settings); - RefCountedPtr m_pigui; + RefCountedPtr m_pigui; std::unique_ptr m_input; std::string m_applicationTitle; diff --git a/src/lua/Lua.cpp b/src/lua/Lua.cpp index 6f370cbef..ba845f4e9 100644 --- a/src/lua/Lua.cpp +++ b/src/lua/Lua.cpp @@ -107,8 +107,6 @@ namespace Lua { GameUI::Lua::Init(); SceneGraph::Lua::Init(); - PiGUI::Lua::Init(); - // XXX load everything. for now, just modules lua_State *l = Lua::manager->GetLuaState(); pi_lua_dofile(l, "libs/autoload.lua"); diff --git a/src/lua/LuaEngine.cpp b/src/lua/LuaEngine.cpp index 5fee1b387..d27b39820 100644 --- a/src/lua/LuaEngine.cpp +++ b/src/lua/LuaEngine.cpp @@ -116,7 +116,7 @@ static int l_engine_attr_ui(lua_State *l) */ static int l_engine_attr_pigui(lua_State *l) { - LuaObject::PushToLua(Pi::pigui); + LuaObject::PushToLua(Pi::pigui); return 1; } diff --git a/src/lua/LuaPiGui.cpp b/src/lua/LuaPiGui.cpp index 269be635d..47583de6d 100644 --- a/src/lua/LuaPiGui.cpp +++ b/src/lua/LuaPiGui.cpp @@ -18,6 +18,7 @@ #include "graphics/Graphics.h" #include "pigui/LuaFlags.h" #include "pigui/PiGui.h" +#include "pigui/PiGuiLua.h" #include "ship/PlayerShipController.h" #include "sound/Sound.h" #include "ui/Context.h" @@ -29,33 +30,6 @@ // undef it, to avoid including yet another header that undefs it #undef RegisterClass -LuaRef m_handlers; -LuaRef PiGUI::GetHandlers() { return m_handlers; } - -LuaRef m_keys; -LuaRef PiGUI::GetKeys() { return m_keys; } - -static std::vector> m_keycodes = { - { "left", SDLK_LEFT }, - { "right", SDLK_RIGHT }, - { "up", SDLK_UP }, - { "down", SDLK_DOWN }, - { "escape", SDLK_ESCAPE }, - { "f1", SDLK_F1 }, - { "f2", SDLK_F2 }, - { "f3", SDLK_F3 }, - { "f4", SDLK_F4 }, - { "f5", SDLK_F5 }, - { "f6", SDLK_F6 }, - { "f7", SDLK_F7 }, - { "f8", SDLK_F8 }, - { "f9", SDLK_F9 }, - { "f10", SDLK_F10 }, - { "f11", SDLK_F11 }, - { "f12", SDLK_F12 }, - { "tab", SDLK_TAB }, -}; - template static Type parse_imgui_flags(lua_State *l, int index, LuaFlags &lookupTable) { @@ -1408,7 +1382,7 @@ static int l_pigui_is_mouse_clicked(lua_State *l) static int l_pigui_push_font(lua_State *l) { PROFILE_SCOPED() - PiGui *pigui = LuaObject::CheckFromLua(1); + PiGui::Instance *pigui = LuaObject::CheckFromLua(1); std::string fontname = LuaPull(l, 2); int size = LuaPull(l, 3); ImFont *font = pigui->GetFont(fontname, size); @@ -1968,7 +1942,7 @@ static int l_pigui_should_show_labels(lua_State *l) static int l_attr_handlers(lua_State *l) { PROFILE_SCOPED() - PiGui *pigui = LuaObject::CheckFromLua(1); + PiGui::Instance *pigui = LuaObject::CheckFromLua(1); PiGUI::GetHandlers().PushCopyToStack(); return 1; } @@ -1976,7 +1950,7 @@ static int l_attr_handlers(lua_State *l) static int l_attr_keys(lua_State *l) { PROFILE_SCOPED() - PiGui *pigui = LuaObject::CheckFromLua(1); + PiGui::Instance *pigui = LuaObject::CheckFromLua(1); PiGUI::GetKeys().PushCopyToStack(); return 1; } @@ -1984,7 +1958,7 @@ static int l_attr_keys(lua_State *l) static int l_attr_screen_width(lua_State *l) { PROFILE_SCOPED() - // PiGui *pigui = LuaObject::CheckFromLua(1); + // PiGui::Instance *pigui = LuaObject::CheckFromLua(1); LuaPush(l, Graphics::GetScreenWidth()); return 1; } @@ -2020,7 +1994,7 @@ static int l_attr_key_alt(lua_State *l) static int l_attr_screen_height(lua_State *l) { PROFILE_SCOPED() - // PiGui *pigui = LuaObject::CheckFromLua(1); + // PiGui::Instance *pigui = LuaObject::CheckFromLua(1); LuaPush(l, Graphics::GetScreenHeight()); return 1; } @@ -2392,11 +2366,11 @@ static int l_pigui_add_convex_poly_filled(lua_State *l) static int l_pigui_load_texture_from_svg(lua_State *l) { PROFILE_SCOPED() - PiGui *pigui = LuaObject::CheckFromLua(1); + PiGui::Instance *pigui = LuaObject::CheckFromLua(1); std::string svg_filename = LuaPull(l, 2); int width = LuaPull(l, 3); int height = LuaPull(l, 4); - ImTextureID id = pigui->RenderSVG(svg_filename, width, height); + ImTextureID id = PiGui::RenderSVG(svg_filename, width, height); // LuaPush(l, id); lua_pushlightuserdata(l, id); return 1; @@ -2497,7 +2471,7 @@ static int l_pigui_push_text_wrap_pos(lua_State *l) void PiGUI::RunHandler(double delta, std::string handler) { PROFILE_SCOPED() - ScopedTable t(m_handlers); + ScopedTable t(GetHandlers()); if (t.Get(handler)) { t.Call(handler, delta); Pi::renderer->CheckRenderErrors(__FUNCTION__, __LINE__); @@ -2505,10 +2479,10 @@ void PiGUI::RunHandler(double delta, std::string handler) } template <> -const char *LuaObject::s_type = "PiGui"; +const char *LuaObject::s_type = "PiGui"; template <> -void LuaObject::RegisterClass() +void LuaObject::RegisterClass() { static const luaL_Reg l_methods[] = { { "Begin", l_pigui_begin }, @@ -2666,14 +2640,4 @@ void LuaObject::RegisterClass() imguiStyleVarTable.Register(l, "ImGuiStyleVar"); imguiWindowFlagsTable.Register(l, "ImGuiWindowFlags"); imguiHoveredFlagsTable.Register(l, "ImGuiHoveredFlags"); - - lua_newtable(l); - m_handlers = LuaRef(l, -1); - - lua_newtable(l); - m_keys = LuaRef(l, -1); - LuaTable keys(l, -1); - for (auto p : m_keycodes) { - keys.Set(p.first, p.second); - } } diff --git a/src/lua/LuaPiGui.h b/src/lua/LuaPiGui.h index e4b436966..c94fc5ca9 100644 --- a/src/lua/LuaPiGui.h +++ b/src/lua/LuaPiGui.h @@ -29,11 +29,6 @@ namespace PiGUI { int pushOnScreenPositionDirection(lua_State *l, vector3d position); TScreenSpace lua_world_space_to_screen_space(const vector3d &pos); - // Get registered PiGui handlers. - LuaRef GetHandlers(); - // Get a table of key name to SDL-keycode mappings - LuaRef GetKeys(); - // Run a lua PiGui handler. void RunHandler(double delta, std::string handler = "GAME"); } // namespace PiGUI diff --git a/src/pigui/PiGui.cpp b/src/pigui/PiGui.cpp index 7e9732df4..af63fb752 100644 --- a/src/pigui/PiGui.cpp +++ b/src/pigui/PiGui.cpp @@ -5,6 +5,8 @@ #include "Input.h" #include "Pi.h" +#include "graphics/Texture.h" +#include "graphics/opengl/RendererGL.h" #include "graphics/opengl/TextureGL.h" // nasty, usage of GL is implementation specific #include "imgui/imgui.h" @@ -12,9 +14,6 @@ #define IMGUI_IMPL_OPENGL_LOADER_GLEW 1 #include "imgui/examples/imgui_impl_opengl3.h" #include "imgui/examples/imgui_impl_sdl.h" -// to get ImVec2 + ImVec2 -#define IMGUI_DEFINE_MATH_OPERATORS true -#include "imgui/imgui_internal.h" #include #include @@ -24,7 +23,37 @@ #define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" -std::vector PiGui::m_svg_textures; +using namespace PiGui; + +std::vector m_svg_textures; + +std::vector &PiGui::GetSVGTextures() +{ + return m_svg_textures; +} + +static void *makeTexture(unsigned char *pixels, int width, int height) +{ + PROFILE_SCOPED() + // this is not very pretty code and uses the Graphics::TextureGL class directly + // Texture descriptor defines the size, type. + // Gone for LINEAR_CLAMP here and RGBA like the original code + const vector2f texSize(1.0f, 1.0f); + const vector3f dataSize(width, height, 0.0f); + const Graphics::TextureDescriptor texDesc(Graphics::TEXTURE_RGBA_8888, + dataSize, texSize, Graphics::LINEAR_CLAMP, + false, false, false, 0, Graphics::TEXTURE_2D); + // Create the texture, calling it via renderer directly avoids the caching call of TextureBuilder + // However interestingly this gets called twice which would have been a WIN for the TextureBuilder :/ + Graphics::Texture *pTex = Pi::renderer->CreateTexture(texDesc); + // Update it with the actual pixels, this is a two step process due to legacy code + pTex->Update(pixels, dataSize, Graphics::TEXTURE_RGBA_8888); + // nasty bit as I invoke the TextureGL + Graphics::OGL::TextureGL *pGLTex = reinterpret_cast(pTex); + Uint32 result = pGLTex->GetTextureID(); + PiGui::GetSVGTextures().push_back(pTex); // store for cleanup later + return reinterpret_cast(result); +} ImTextureID PiGui::RenderSVG(std::string svgFilename, int width, int height) { @@ -85,7 +114,41 @@ ImTextureID PiGui::RenderSVG(std::string svgFilename, int width, int height) return makeTexture(img, W, H); } -ImFont *PiGui::GetFont(const std::string &name, int size) +// +// PiGui::Instance +// + +Instance::Instance() : + m_should_bake_fonts(true) +{ + // TODO: clang-format doesn't like list initializers inside function calls + // clang-format off + PiFont uiheading("orbiteer", { + PiFace("DejaVuSans.ttf", /*18.0/20.0*/ 1.2), + PiFace("wqy-microhei.ttc", 1.0), + PiFace("Orbiteer-Bold.ttf", 1.0) // imgui only supports 0xffff, not 0x10ffff + }); + AddFontDefinition(uiheading); + + PiFont guifont("pionillium", { + PiFace("DejaVuSans.ttf", 13.0 / 14.0), + PiFace("wqy-microhei.ttc", 1.0), + PiFace("PionilliumText22L-Medium.ttf", 1.0) + }); + AddFontDefinition(guifont); + // clang-format on + + // Output("Fonts:\n"); + for (auto entry : m_font_definitions) { + // Output(" entry %s:\n", entry.first.c_str()); + entry.second.describe(); + } + + // ensure the tooltip font exists + GetFont("pionillium", 14); +}; + +ImFont *Instance::GetFont(const std::string &name, int size) { PROFILE_SCOPED() auto iter = m_fonts.find(std::make_pair(name, size)); @@ -97,7 +160,7 @@ ImFont *PiGui::GetFont(const std::string &name, int size) return font; } -void PiGui::AddGlyph(ImFont *font, unsigned short glyph) +void Instance::AddGlyph(ImFont *font, unsigned short glyph) { PROFILE_SCOPED() // range glyph..glyph @@ -122,7 +185,7 @@ void PiGui::AddGlyph(ImFont *font, unsigned short glyph) Error("No face in font %s handles glyph %i\n", pifont.name().c_str(), glyph); } -ImFont *PiGui::AddFont(const std::string &name, int size) +ImFont *Instance::AddFont(const std::string &name, int size) { PROFILE_SCOPED() auto iter = m_font_definitions.find(name); @@ -146,7 +209,7 @@ ImFont *PiGui::AddFont(const std::string &name, int size) return m_fonts[std::make_pair(name, size)]; } -void PiGui::RefreshFontsTexture() +void Instance::RefreshFontsTexture() { PROFILE_SCOPED() // TODO: fix this, do the right thing, don't just re-create *everything* :) @@ -160,7 +223,7 @@ void PiDefaultStyle(ImGuiStyle &style) style.WindowBorderSize = 0.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. } -void PiGui::Init(SDL_Window *window) +void Instance::Init(SDL_Window *window) { PROFILE_SCOPED() @@ -196,146 +259,14 @@ void PiGui::Init(SDL_Window *window) io.IniFilename = ioIniFilename; } -int PiGui::RadialPopupSelectMenu(const ImVec2 ¢er, std::string popup_id, int mouse_button, std::vector tex_ids, std::vector> uvs, unsigned int size, std::vector tooltips) -{ - PROFILE_SCOPED() - // return: - // 0 - n for item selected - // -1 for nothing chosen, but menu open - // -2 for menu closed without an icon chosen - // -3 for menu not open - int ret = -3; - - // FIXME: Missing a call to query if Popup is open so we can move the PushStyleColor inside the BeginPopupBlock (e.g. IsPopupOpen() in imgui.cpp) - // FIXME: Our PathFill function only handle convex polygons, so we can't have items spanning an arc too large else inner concave edge artifact is too visible, hence the ImMax(7,items_count) - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); - if (ImGui::BeginPopup(popup_id.c_str())) { - ret = -1; - const ImVec2 drag_delta = ImVec2(ImGui::GetIO().MousePos.x - center.x, ImGui::GetIO().MousePos.y - center.y); - const float drag_dist2 = drag_delta.x * drag_delta.x + drag_delta.y * drag_delta.y; - - const ImGuiStyle &style = ImGui::GetStyle(); - const float RADIUS_MIN = 20.0f; - const float RADIUS_MAX = 90.0f; - const float RADIUS_INTERACT_MIN = 20.0f; - const int ITEMS_MIN = 4; - const float border_inout = 12.0f; - const float border_thickness = 4.0f; - ImDrawList *draw_list = ImGui::GetWindowDrawList(); - draw_list->PushClipRectFullScreen(); - draw_list->PathArcTo(center, (RADIUS_MIN + RADIUS_MAX) * 0.5f, 0.0f, IM_PI * 2.0f * 0.99f, 64); // FIXME: 0.99f look like full arc with closed thick stroke has a bug now - draw_list->PathStroke(ImColor(18, 44, 67, 210), true, RADIUS_MAX - RADIUS_MIN); - - const float item_arc_span = 2 * IM_PI / ImMax(ITEMS_MIN, tex_ids.size()); - float drag_angle = atan2f(drag_delta.y, drag_delta.x); - if (drag_angle < -0.5f * item_arc_span) - drag_angle += 2.0f * IM_PI; - - int item_hovered = -1; - int item_n = 0; - for (ImTextureID tex_id : tex_ids) { - const char *tooltip = tooltips.at(item_n).c_str(); - const float inner_spacing = style.ItemInnerSpacing.x / RADIUS_MIN / 2; - const float item_inner_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing); - const float item_inner_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing); - const float item_outer_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing * (RADIUS_MIN / RADIUS_MAX)); - const float item_outer_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing * (RADIUS_MIN / RADIUS_MAX)); - - bool hovered = false; - if (drag_dist2 >= RADIUS_INTERACT_MIN * RADIUS_INTERACT_MIN) { - if (drag_angle >= item_inner_ang_min && drag_angle < item_inner_ang_max) - hovered = true; - } - bool selected = false; - - int arc_segments = static_cast((64 * item_arc_span / (2 * IM_PI))) + 1; - draw_list->PathArcTo(center, RADIUS_MAX - border_inout, item_outer_ang_min, item_outer_ang_max, arc_segments); - draw_list->PathArcTo(center, RADIUS_MIN + border_inout, item_inner_ang_max, item_inner_ang_min, arc_segments); - - draw_list->PathFillConvex(hovered ? ImColor(102, 147, 189) : selected ? ImColor(48, 81, 111) : ImColor(48, 81, 111)); - if (hovered) { - // draw outer / inner extra segments - draw_list->PathArcTo(center, RADIUS_MAX - border_thickness, item_outer_ang_min, item_outer_ang_max, arc_segments); - draw_list->PathStroke(ImColor(102, 147, 189), false, border_thickness); - draw_list->PathArcTo(center, RADIUS_MIN + border_thickness, item_outer_ang_min, item_outer_ang_max, arc_segments); - draw_list->PathStroke(ImColor(102, 147, 189), false, border_thickness); - } - ImVec2 text_size = ImVec2(size, size); - ImVec2 text_pos = ImVec2( - center.x + cosf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.x * 0.5f, - center.y + sinf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.y * 0.5f); - draw_list->AddImage(tex_id, text_pos, ImVec2(text_pos.x + size, text_pos.y + size), uvs[item_n].first, uvs[item_n].second); - ImGui::SameLine(); - if (hovered) { - item_hovered = item_n; - ImGui::SetTooltip("%s", tooltip); - } - item_n++; - } - draw_list->PopClipRect(); - - if (ImGui::IsMouseReleased(mouse_button)) { - ImGui::CloseCurrentPopup(); - if (item_hovered == -1) - ret = -2; - else - ret = item_hovered; - } - ImGui::EndPopup(); - } else { - // Output("WARNING: RadialPopupSelectMenu BeginPopup failed: %s\n", popup_id.c_str()); - } - ImGui::PopStyleColor(3); - return ret; -} - -bool PiGui::CircularSlider(const ImVec2 ¢er, float *v, float v_min, float v_max) -{ - PROFILE_SCOPED() - ImDrawList *draw_list = ImGui::GetWindowDrawList(); - ImGuiWindow *window = ImGui::GetCurrentWindow(); - const ImGuiID id = window->GetID("circularslider"); - draw_list->AddCircle(center, 17, ImColor(100, 100, 100), 128, 12.0); - draw_list->PathArcTo(center, 17, 0, M_PI * 2.0 * (*v - v_min) / (v_max - v_min), 64); - draw_list->PathStroke(ImColor(200, 200, 200), false, 12.0); - ImRect grab_bb; - return ImGui::SliderBehavior(ImRect(center.x - 17, center.y - 17, center.x + 17, center.y + 17), - id, ImGuiDataType_Float, v, &v_min, &v_max, "%.4f", 1.0, ImGuiSliderFlags_None, &grab_bb); -} - -bool PiGui::ProcessEvent(SDL_Event *event) +bool Instance::ProcessEvent(SDL_Event *event) { PROFILE_SCOPED() ImGui_ImplSDL2_ProcessEvent(event); return false; } -void *PiGui::makeTexture(unsigned char *pixels, int width, int height) -{ - PROFILE_SCOPED() - // this is not very pretty code and uses the Graphics::TextureGL class directly - // Texture descriptor defines the size, type. - // Gone for LINEAR_CLAMP here and RGBA like the original code - const vector2f texSize(1.0f, 1.0f); - const vector3f dataSize(width, height, 0.0f); - const Graphics::TextureDescriptor texDesc(Graphics::TEXTURE_RGBA_8888, - dataSize, texSize, Graphics::LINEAR_CLAMP, - false, false, false, 0, Graphics::TEXTURE_2D); - // Create the texture, calling it via renderer directly avoids the caching call of TextureBuilder - // However interestingly this gets called twice which would have been a WIN for the TextureBuilder :/ - Graphics::Texture *pTex = Pi::renderer->CreateTexture(texDesc); - // Update it with the actual pixels, this is a two step process due to legacy code - pTex->Update(pixels, dataSize, Graphics::TEXTURE_RGBA_8888); - // nasty bit as I invoke the TextureGL - Graphics::OGL::TextureGL *pGLTex = reinterpret_cast(pTex); - Uint32 result = pGLTex->GetTextureID(); - m_svg_textures.push_back(pTex); // store for cleanup later - return reinterpret_cast(result); -} - -void PiGui::NewFrame(SDL_Window *window) +void Instance::NewFrame(SDL_Window *window) { PROFILE_SCOPED() @@ -361,7 +292,7 @@ void PiGui::NewFrame(SDL_Window *window) ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow); } -void PiGui::EndFrame() +void Instance::EndFrame() { PROFILE_SCOPED() @@ -388,7 +319,7 @@ void PiGui::EndFrame() } } -void PiGui::Render() +void Instance::Render() { PROFILE_SCOPED() EndFrame(); @@ -405,7 +336,7 @@ void PiGui::Render() } } -void PiGui::ClearFonts() +void Instance::ClearFonts() { PROFILE_SCOPED() ImGuiIO &io = ImGui::GetIO(); @@ -415,7 +346,7 @@ void PiGui::ClearFonts() io.Fonts->Clear(); } -void PiGui::BakeFont(PiFont &font) +void Instance::BakeFont(PiFont &font) { PROFILE_SCOPED() ImGuiIO &io = ImGui::GetIO(); @@ -456,7 +387,7 @@ void PiGui::BakeFont(PiFont &font) imfont->MissingGlyphs.clear(); } -void PiGui::BakeFonts() +void Instance::BakeFonts() { PROFILE_SCOPED() // Output("Baking fonts\n"); @@ -483,192 +414,7 @@ void PiGui::BakeFonts() RefreshFontsTexture(); } -static void drawThrust(ImDrawList *draw_list, const ImVec2 ¢er, const ImVec2 &up, float value, const ImColor &fg, const ImColor &bg) -{ - PROFILE_SCOPED() - float factor = 0.1; // how much to offset from center - const ImVec2 step(up.x * 0.5, up.y * 0.5); - const ImVec2 left(-step.y * (1.0 - factor), step.x * (1.0 - factor)); - const ImVec2 u(up.x * (1.0 - factor), up.y * (1.0 - factor)); - const ImVec2 c(center + ImVec2(u.x * factor, u.y * factor)); - const ImVec2 right(-left.x, -left.y); - const ImVec2 leftmiddle = c + step + left; - const ImVec2 rightmiddle = c + step + right; - const ImVec2 bb_lowerright = c + right; - const ImVec2 bb_upperleft = c + left + ImVec2(u.x * value, u.y * value); - const ImVec2 lefttop = c + u + left; - const ImVec2 righttop = c + u + right; - const ImVec2 minimum(fmin(bb_upperleft.x, bb_lowerright.x), fmin(bb_upperleft.y, bb_lowerright.y)); - const ImVec2 maximum(fmax(bb_upperleft.x, bb_lowerright.x), fmax(bb_upperleft.y, bb_lowerright.y)); - ImVec2 points[] = { c, leftmiddle, lefttop, righttop, rightmiddle }; - draw_list->AddConvexPolyFilled(points, 5, bg); - draw_list->PushClipRect(minimum - ImVec2(1, 1), maximum + ImVec2(1, 1)); - draw_list->AddConvexPolyFilled(points, 5, fg); - draw_list->PopClipRect(); -} - -void PiGui::ThrustIndicator(const std::string &id_string, const ImVec2 &size_arg, const ImVec4 &thrust, const ImVec4 &velocity, const ImVec4 &bg_col, int frame_padding, ImColor vel_fg, ImColor vel_bg, ImColor thrust_fg, ImColor thrust_bg) -{ - PROFILE_SCOPED() - ImGuiWindow *window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext &g = *GImGui; - const ImGuiStyle &style = g.Style; - const ImGuiID id = window->GetID(id_string.c_str()); - - ImVec2 pos = window->DC.CursorPos; - // if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) - // pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; - ImVec2 size = ImGui::CalcItemSize(size_arg, style.FramePadding.x * 2.0f, style.FramePadding.y * 2.0f); - - const ImVec2 padding = (frame_padding >= 0) ? ImVec2(static_cast(frame_padding), static_cast(frame_padding)) : style.FramePadding; - const ImRect bb(pos, pos + size + padding * 2); - const ImRect inner_bb(pos + padding, pos + padding + size); - - ImGui::ItemSize(bb, style.FramePadding.y); - if (!ImGui::ItemAdd(bb, id)) - return; - - // Render - const ImU32 col = ImGui::GetColorU32(static_cast(ImGuiCol_Button)); - ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); - ImDrawList *draw_list = ImGui::GetWindowDrawList(); - if (bg_col.w > 0.0f) - draw_list->AddRectFilled(inner_bb.Min, inner_bb.Max, ImGui::GetColorU32(bg_col)); - const ImVec2 leftupper = inner_bb.Min; - const ImVec2 rightlower = inner_bb.Max; - const ImVec2 rightcenter((rightlower.x - leftupper.x) * 0.8 + leftupper.x, (rightlower.y + leftupper.y) / 2); - const ImVec2 leftcenter((rightlower.x - leftupper.x) * 0.35 + leftupper.x, (rightlower.y + leftupper.y) / 2); - const ImVec2 up(0, -std::abs(leftupper.y - rightlower.y) * 0.4); - const ImVec2 left(-up.y, up.x); - float thrust_fwd = fmax(thrust.z, 0); - float thrust_bwd = fmax(-thrust.z, 0); - float thrust_left = fmax(-thrust.x, 0); - float thrust_right = fmax(thrust.x, 0); - float thrust_up = fmax(-thrust.y, 0); - float thrust_down = fmax(thrust.y, 0); - // actual thrust - drawThrust(draw_list, rightcenter, up, thrust_fwd, thrust_fg, thrust_bg); - drawThrust(draw_list, rightcenter, ImVec2(-up.x, -up.y), thrust_bwd, thrust_fg, thrust_bg); - drawThrust(draw_list, leftcenter, up, thrust_up, thrust_fg, thrust_bg); - drawThrust(draw_list, leftcenter, ImVec2(-up.x, -up.y), thrust_down, thrust_fg, thrust_bg); - drawThrust(draw_list, leftcenter, left, thrust_left, thrust_fg, thrust_bg); - drawThrust(draw_list, leftcenter, ImVec2(-left.x, -left.y), thrust_right, thrust_fg, thrust_bg); - // forward/back velocity - draw_list->AddLine(rightcenter + up, rightcenter - up, vel_bg, 3); - draw_list->AddLine(rightcenter, rightcenter - up * velocity.z, vel_fg, 3); - // left/right velocity - draw_list->AddLine(leftcenter + left, leftcenter - left, vel_bg, 3); - draw_list->AddLine(leftcenter, leftcenter + left * velocity.x, vel_fg, 3); - // up/down velocity - draw_list->AddLine(leftcenter + up, leftcenter - up, vel_bg, 3); - draw_list->AddLine(leftcenter, leftcenter + up * velocity.y, vel_fg, 3); - // Automatically close popups - //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) - // CloseCurrentPopup(); -} - -bool PiGui::LowThrustButton(const char *id_string, const ImVec2 &size_arg, int thrust_level, const ImVec4 &bg_col, int frame_padding, ImColor gauge_fg, ImColor gauge_bg) -{ - PROFILE_SCOPED() - std::string label = std::to_string(thrust_level); - ImGuiWindow *window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext &g = *GImGui; - const ImGuiStyle &style = g.Style; - const ImGuiID id = window->GetID(id_string); - const ImVec2 label_size = ImGui::CalcTextSize(label.c_str(), NULL, true); - - ImVec2 pos = window->DC.CursorPos; - // if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) - // pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; - ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); - - const ImVec2 padding = (frame_padding >= 0) ? ImVec2(static_cast(frame_padding), static_cast(frame_padding)) : style.FramePadding; - const ImRect bb(pos, pos + size + padding * 2); - const ImRect inner_bb(pos + padding, pos + padding + size); - - ImGui::ItemSize(bb, style.FramePadding.y); - if (!ImGui::ItemAdd(bb, id)) - return false; - - // if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); // flags - - // Render - const ImU32 col = ImGui::GetColorU32(static_cast((hovered && held) ? ImGuiCol_ButtonActive : (hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button))); - ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); - const ImVec2 center = (inner_bb.Min + inner_bb.Max) / 2; - float radius = (inner_bb.Max.x - inner_bb.Min.x) * 0.4; - float thickness = 4; - ImDrawList *draw_list = ImGui::GetWindowDrawList(); - if (bg_col.w > 0.0f) - draw_list->AddRectFilled(inner_bb.Min, inner_bb.Max, ImGui::GetColorU32(bg_col)); - - draw_list->PathArcTo(center, radius, 0, IM_PI * 2, 16); - draw_list->PathStroke(gauge_bg, false, thickness); - - draw_list->PathArcTo(center, radius, IM_PI, IM_PI + IM_PI * 2 * (thrust_level / 100.0), 16); - draw_list->PathStroke(gauge_fg, false, thickness); - ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label.c_str(), NULL, &label_size, style.ButtonTextAlign, &bb); - - // Automatically close popups - //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) - // CloseCurrentPopup(); - - return pressed; -} - -// frame_padding < 0: uses FramePadding from style (default) -// frame_padding = 0: no framing -// frame_padding > 0: set framing size -// The color used are the button colors. -bool PiGui::ButtonImageSized(ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &imgSize, const ImVec2 &uv0, const ImVec2 &uv1, int frame_padding, const ImVec4 &bg_col, const ImVec4 &tint_col) -{ - ImGuiWindow *window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext &g = *GImGui; - const ImGuiStyle &style = g.Style; - - // Default to using texture ID as ID. User can still push string/integer prefixes. - // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. - ImGui::PushID((void *)user_texture_id); - const ImGuiID id = window->GetID("#image"); - ImGui::PopID(); - - ImVec2 imgPadding = (size - imgSize) / 2; - imgPadding.x = imgPadding.x < 0 || imgSize.x <= 0 ? 0 : imgPadding.x; - imgPadding.y = imgPadding.y < 0 || imgSize.y <= 0 ? 0 : imgPadding.y; - - const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); - const ImRect image_bb(window->DC.CursorPos + padding + imgPadding, window->DC.CursorPos + padding + size - imgPadding); - ImGui::ItemSize(bb); - if (!ImGui::ItemAdd(bb, id)) - return false; - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held); - - // Render - const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - ImGui::RenderNavHighlight(bb, id); - ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); - if (bg_col.w > 0.0f) - window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, ImGui::GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, ImGui::GetColorU32(tint_col)); - - return pressed; -} - -void PiGui::Uninit() +void Instance::Uninit() { PROFILE_SCOPED() for (auto tex : m_svg_textures) { @@ -688,25 +434,9 @@ void PiGui::Uninit() ImGui::DestroyContext(); } -PiGui::PiGui() : - m_should_bake_fonts(true) -{ - PiFont uiheading("orbiteer", { - PiFace("DejaVuSans.ttf", /*18.0/20.0*/ 1.2), PiFace("wqy-microhei.ttc", 1.0), PiFace("Orbiteer-Bold.ttf", 1.0) // imgui only supports 0xffff, not 0x10ffff - }); - PiFont guifont("pionillium", { PiFace("DejaVuSans.ttf", 13.0 / 14.0), PiFace("wqy-microhei.ttc", 1.0), PiFace("PionilliumText22L-Medium.ttf", 1.0) }); - AddFontDefinition(uiheading); - AddFontDefinition(guifont); - - // Output("Fonts:\n"); - for (auto entry : m_font_definitions) { - // Output(" entry %s:\n", entry.first.c_str()); - entry.second.describe(); - } - - // ensure the tooltip font exists - GetFont("pionillium", 14); -}; +// +// PiGui::PiFace +// const bool PiFace::isValidGlyph(unsigned short glyph) const { diff --git a/src/pigui/PiGui.h b/src/pigui/PiGui.h index 21d10d530..1892da92a 100644 --- a/src/pigui/PiGui.h +++ b/src/pigui/PiGui.h @@ -5,126 +5,144 @@ #include "FileSystem.h" #include "RefCounted.h" -#include "graphics/opengl/RendererGL.h" #include "imgui/imgui.h" #include "utils.h" #include -class PiFace { - friend class PiGui; // need acces to some private data - std::string m_ttfname; // only the ttf name, it is automatically sought in data/fonts/ - float m_sizefactor; // the requested pixelsize is multiplied by this factor - std::unordered_set m_invalid_glyphs; - mutable std::vector> m_used_ranges; - ImVector m_imgui_ranges; +namespace Graphics { + class Texture; +} -public: - PiFace(const std::string &ttfname, float sizefactor) : - m_ttfname(ttfname), - m_sizefactor(sizefactor) {} - const std::string &ttfname() const { return m_ttfname; } - const float sizefactor() const { return m_sizefactor; } - //std::unordered_map &invalid_glyphs() const { return m_invalid_glyphs; } - const std::vector> &used_ranges() const { return m_used_ranges; } - const bool isValidGlyph(unsigned short glyph) const; - void addGlyph(unsigned short glyph); - void sortUsedRanges() const; -}; +namespace PiGui { -class PiFont { - std::string m_name; - std::vector m_faces; - int m_pixelsize; + class PiFace { + public: + using UsedRange = std::pair; + PiFace(const std::string &ttfname, float sizefactor) : + m_ttfname(ttfname), + m_sizefactor(sizefactor) {} -public: - PiFont(const std::string &name) : - m_name(name) {} - PiFont(const std::string &name, const std::vector &faces) : - m_name(name), - m_faces(faces) {} - PiFont(const PiFont &other) : - m_name(other.name()), - m_faces(other.faces()) {} - PiFont() : - m_name("unknown") {} - const std::vector &faces() const { return m_faces; } - std::vector &faces() { return m_faces; } - const std::string &name() const { return m_name; } - int pixelsize() const { return m_pixelsize; } - void setPixelsize(int pixelsize) { m_pixelsize = pixelsize; } - void describe() const - { - Output("font %s:\n", name().c_str()); - for (const PiFace &face : faces()) { - Output("- %s %f\n", face.ttfname().c_str(), face.sizefactor()); + const std::string &ttfname() const { return m_ttfname; } + + const float sizefactor() const { return m_sizefactor; } + + //std::unordered_map &invalid_glyphs() const { return m_invalid_glyphs; } + const std::vector &used_ranges() const { return m_used_ranges; } + + const bool isValidGlyph(unsigned short glyph) const; + void addGlyph(unsigned short glyph); + void sortUsedRanges() const; + + private: + friend class Instance; // need access to some private data + + std::string m_ttfname; // only the ttf name, it is automatically sought in data/fonts/ + float m_sizefactor; // the requested pixelsize is multiplied by this factor + + std::unordered_set m_invalid_glyphs; + mutable std::vector m_used_ranges; + + ImVector m_imgui_ranges; + }; + + class PiFont { + public: + PiFont(const std::string &name) : + m_name(name) {} + PiFont(const std::string &name, const std::vector &faces) : + m_name(name), + m_faces(faces) {} + PiFont(const PiFont &other) : + m_name(other.name()), + m_faces(other.faces()) {} + PiFont() : + m_name("unknown") {} + + const std::vector &faces() const { return m_faces; } + std::vector &faces() { return m_faces; } + + const std::string &name() const { return m_name; } + + int pixelsize() const { return m_pixelsize; } + void setPixelsize(int pixelsize) { m_pixelsize = pixelsize; } + + void describe() const + { + Output("font %s:\n", name().c_str()); + for (const PiFace &face : faces()) { + Output("- %s %f\n", face.ttfname().c_str(), face.sizefactor()); + } } - } -}; -/* Class to wrap ImGui. */ -class PiGui : public RefCounted { - std::map, ImFont *> m_fonts; - std::map> m_im_fonts; - std::map, PiFont> m_pi_fonts; - bool m_should_bake_fonts; + private: + std::string m_name; + std::vector m_faces; + int m_pixelsize; + }; - std::map m_font_definitions; + /* Class to wrap ImGui. */ + class Instance : public RefCounted { + public: + Instance(); - void BakeFonts(); - void BakeFont(PiFont &font); - void AddFontDefinition(const PiFont &font) { m_font_definitions[font.name()] = font; } - void ClearFonts(); + // Call at the start of every frame. Calls ImGui::NewFrame() internally. + void NewFrame(SDL_Window *window); -public: - PiGui(); + // Call at the end of a frame that you're not going to render the results of + void EndFrame(); - // Call at the start of every frame. Calls ImGui::NewFrame() internally. - void NewFrame(SDL_Window *window); + // Calls ImGui::EndFrame() internally and does book-keeping before rendering. + void Render(); - // Call at the end of a frame that you're not going to render the results of - void EndFrame(); + void Init(SDL_Window *window); - // Calls ImGui::EndFrame() internally and does book-keeping before rendering. - void Render(); + ImFont *GetFont(const std::string &name, int size); - void Init(SDL_Window *window); + void Uninit(); - ImFont *GetFont(const std::string &name, int size); + ImFont *AddFont(const std::string &name, int size); - void Uninit(); + void AddGlyph(ImFont *font, unsigned short glyph); - ImFont *AddFont(const std::string &name, int size); + bool ProcessEvent(SDL_Event *event); - void AddGlyph(ImFont *font, unsigned short glyph); + void RefreshFontsTexture(); - static ImTextureID RenderSVG(std::string svgFilename, int width, int height); + private: + std::map, ImFont *> m_fonts; + std::map> m_im_fonts; + std::map, PiFont> m_pi_fonts; + bool m_should_bake_fonts; - static bool ProcessEvent(SDL_Event *event); + std::map m_font_definitions; - void RefreshFontsTexture(); + void BakeFonts(); + void BakeFont(PiFont &font); + void AddFontDefinition(const PiFont &font) { m_font_definitions[font.name()] = font; } + void ClearFonts(); + }; - static void *makeTexture(unsigned char *pixels, int width, int height); + int RadialPopupSelectMenu(const ImVec2 ¢er, std::string popup_id, int mouse_button, std::vector tex_ids, std::vector> uvs, unsigned int size, std::vector tooltips); + bool CircularSlider(const ImVec2 ¢er, float *v, float v_min, float v_max); - static bool WantCaptureMouse() + bool LowThrustButton(const char *label, const ImVec2 &size_arg, int thrust_level, const ImVec4 &bg_col, int frame_padding, ImColor gauge_fg, ImColor gauge_bg); + bool ButtonImageSized(ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &imgSize, const ImVec2 &uv0, const ImVec2 &uv1, int frame_padding, const ImVec4 &bg_col, const ImVec4 &tint_col); + + void ThrustIndicator(const std::string &id_string, const ImVec2 &size, const ImVec4 &thrust, const ImVec4 &velocity, const ImVec4 &bg_col, int frame_padding, ImColor vel_fg, ImColor vel_bg, ImColor thrust_fg, ImColor thrust_bg); + + inline bool WantCaptureMouse() { return ImGui::GetIO().WantCaptureMouse; } - static bool WantCaptureKeyboard() + inline bool WantCaptureKeyboard() { return ImGui::GetIO().WantCaptureKeyboard; } - static int RadialPopupSelectMenu(const ImVec2 ¢er, std::string popup_id, int mouse_button, std::vector tex_ids, std::vector> uvs, unsigned int size, std::vector tooltips); - static bool CircularSlider(const ImVec2 ¢er, float *v, float v_min, float v_max); + std::vector &GetSVGTextures(); + ImTextureID RenderSVG(std::string svgFilename, int width, int height); - static bool LowThrustButton(const char *label, const ImVec2 &size_arg, int thrust_level, const ImVec4 &bg_col, int frame_padding, ImColor gauge_fg, ImColor gauge_bg); - static bool ButtonImageSized(ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &imgSize, const ImVec2 &uv0, const ImVec2 &uv1, int frame_padding, const ImVec4 &bg_col, const ImVec4 &tint_col); - - static void ThrustIndicator(const std::string &id_string, const ImVec2 &size, const ImVec4 &thrust, const ImVec4 &velocity, const ImVec4 &bg_col, int frame_padding, ImColor vel_fg, ImColor vel_bg, ImColor thrust_fg, ImColor thrust_bg); - -private: - static std::vector m_svg_textures; -}; +} //namespace PiGui diff --git a/src/pigui/PiGuiLua.cpp b/src/pigui/PiGuiLua.cpp index ad05c3fa9..e089746bc 100644 --- a/src/pigui/PiGuiLua.cpp +++ b/src/pigui/PiGuiLua.cpp @@ -5,17 +5,65 @@ #include "Face.h" #include "Image.h" #include "ModelSpinner.h" +#include "lua/LuaTable.h" + +static std::vector> m_keycodes = { + { "left", SDLK_LEFT }, + { "right", SDLK_RIGHT }, + { "up", SDLK_UP }, + { "down", SDLK_DOWN }, + { "escape", SDLK_ESCAPE }, + { "f1", SDLK_F1 }, + { "f2", SDLK_F2 }, + { "f3", SDLK_F3 }, + { "f4", SDLK_F4 }, + { "f5", SDLK_F5 }, + { "f6", SDLK_F6 }, + { "f7", SDLK_F7 }, + { "f8", SDLK_F8 }, + { "f9", SDLK_F9 }, + { "f10", SDLK_F10 }, + { "f11", SDLK_F11 }, + { "f12", SDLK_F12 }, + { "tab", SDLK_TAB }, +}; + +static LuaRef m_handlers; +static LuaRef m_keys; namespace PiGUI { + namespace Lua { void Init() { + LuaObject::RegisterClass(); + + lua_State *l = ::Lua::manager->GetLuaState(); + lua_newtable(l); + m_handlers = LuaRef(l, -1); + + lua_newtable(l); + m_keys = LuaRef(l, -1); + LuaTable keys(l, -1); + for (auto p : m_keycodes) { + keys.Set(p.first, p.second); + } + LuaObject::RegisterClass(); LuaObject::RegisterClass(); LuaObject::RegisterClass(); RegisterSandbox(); } + void Uninit() + { + m_handlers.Unref(); + m_keys.Unref(); + } + } // namespace Lua + + LuaRef GetHandlers() { return m_handlers; } + LuaRef GetKeys() { return m_keys; } } // namespace PiGUI diff --git a/src/pigui/PiGuiLua.h b/src/pigui/PiGuiLua.h index c40704051..a1ae5ecb5 100644 --- a/src/pigui/PiGuiLua.h +++ b/src/pigui/PiGuiLua.h @@ -7,12 +7,18 @@ #include "lua/LuaObject.h" namespace PiGUI { - void RegisterSandbox(); + + // Get registered PiGui handlers. + LuaRef GetHandlers(); + // Get a table of key name to SDL-keycode mappings + LuaRef GetKeys(); namespace Lua { + void RegisterSandbox(); void Init(); - } + void Uninit(); + } // namespace Lua } // namespace PiGUI #endif diff --git a/src/pigui/PiGuiSandbox.cpp b/src/pigui/PiGuiSandbox.cpp index ba9e8cf25..3fb063b7a 100644 --- a/src/pigui/PiGuiSandbox.cpp +++ b/src/pigui/PiGuiSandbox.cpp @@ -132,7 +132,7 @@ luaL_Reg l_stack_functions[] = { { NULL, NULL } }; -void PiGUI::RegisterSandbox() +void PiGUI::Lua::RegisterSandbox() { lua_State *L = ::Lua::manager->GetLuaState(); LUA_DEBUG_START(L); diff --git a/src/pigui/Widgets.cpp b/src/pigui/Widgets.cpp new file mode 100644 index 000000000..c20390873 --- /dev/null +++ b/src/pigui/Widgets.cpp @@ -0,0 +1,303 @@ +// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "PiGui.h" +#include "imgui/imgui.h" + +// to get ImVec2 + ImVec2 +#define IMGUI_DEFINE_MATH_OPERATORS true +#include "imgui/imgui_internal.h" + +int PiGui::RadialPopupSelectMenu(const ImVec2 ¢er, std::string popup_id, int mouse_button, std::vector tex_ids, std::vector> uvs, unsigned int size, std::vector tooltips) +{ + PROFILE_SCOPED() + // return: + // 0 - n for item selected + // -1 for nothing chosen, but menu open + // -2 for menu closed without an icon chosen + // -3 for menu not open + int ret = -3; + + // FIXME: Missing a call to query if Popup is open so we can move the PushStyleColor inside the BeginPopupBlock (e.g. IsPopupOpen() in imgui.cpp) + // FIXME: Our PathFill function only handle convex polygons, so we can't have items spanning an arc too large else inner concave edge artifact is too visible, hence the ImMax(7,items_count) + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + if (ImGui::BeginPopup(popup_id.c_str())) { + ret = -1; + const ImVec2 drag_delta = ImVec2(ImGui::GetIO().MousePos.x - center.x, ImGui::GetIO().MousePos.y - center.y); + const float drag_dist2 = drag_delta.x * drag_delta.x + drag_delta.y * drag_delta.y; + + const ImGuiStyle &style = ImGui::GetStyle(); + const float RADIUS_MIN = 20.0f; + const float RADIUS_MAX = 90.0f; + const float RADIUS_INTERACT_MIN = 20.0f; + const int ITEMS_MIN = 4; + const float border_inout = 12.0f; + const float border_thickness = 4.0f; + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRectFullScreen(); + draw_list->PathArcTo(center, (RADIUS_MIN + RADIUS_MAX) * 0.5f, 0.0f, IM_PI * 2.0f * 0.99f, 64); // FIXME: 0.99f look like full arc with closed thick stroke has a bug now + draw_list->PathStroke(ImColor(18, 44, 67, 210), true, RADIUS_MAX - RADIUS_MIN); + + const float item_arc_span = 2 * IM_PI / ImMax(ITEMS_MIN, tex_ids.size()); + float drag_angle = atan2f(drag_delta.y, drag_delta.x); + if (drag_angle < -0.5f * item_arc_span) + drag_angle += 2.0f * IM_PI; + + int item_hovered = -1; + int item_n = 0; + for (ImTextureID tex_id : tex_ids) { + const char *tooltip = tooltips.at(item_n).c_str(); + const float inner_spacing = style.ItemInnerSpacing.x / RADIUS_MIN / 2; + const float item_inner_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing); + const float item_inner_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing); + const float item_outer_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing * (RADIUS_MIN / RADIUS_MAX)); + const float item_outer_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing * (RADIUS_MIN / RADIUS_MAX)); + + bool hovered = false; + if (drag_dist2 >= RADIUS_INTERACT_MIN * RADIUS_INTERACT_MIN) { + if (drag_angle >= item_inner_ang_min && drag_angle < item_inner_ang_max) + hovered = true; + } + bool selected = false; + + int arc_segments = static_cast((64 * item_arc_span / (2 * IM_PI))) + 1; + draw_list->PathArcTo(center, RADIUS_MAX - border_inout, item_outer_ang_min, item_outer_ang_max, arc_segments); + draw_list->PathArcTo(center, RADIUS_MIN + border_inout, item_inner_ang_max, item_inner_ang_min, arc_segments); + + draw_list->PathFillConvex(hovered ? ImColor(102, 147, 189) : selected ? ImColor(48, 81, 111) : ImColor(48, 81, 111)); + if (hovered) { + // draw outer / inner extra segments + draw_list->PathArcTo(center, RADIUS_MAX - border_thickness, item_outer_ang_min, item_outer_ang_max, arc_segments); + draw_list->PathStroke(ImColor(102, 147, 189), false, border_thickness); + draw_list->PathArcTo(center, RADIUS_MIN + border_thickness, item_outer_ang_min, item_outer_ang_max, arc_segments); + draw_list->PathStroke(ImColor(102, 147, 189), false, border_thickness); + } + ImVec2 text_size = ImVec2(size, size); + ImVec2 text_pos = ImVec2( + center.x + cosf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.x * 0.5f, + center.y + sinf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.y * 0.5f); + draw_list->AddImage(tex_id, text_pos, ImVec2(text_pos.x + size, text_pos.y + size), uvs[item_n].first, uvs[item_n].second); + ImGui::SameLine(); + if (hovered) { + item_hovered = item_n; + ImGui::SetTooltip("%s", tooltip); + } + item_n++; + } + draw_list->PopClipRect(); + + if (ImGui::IsMouseReleased(mouse_button)) { + ImGui::CloseCurrentPopup(); + if (item_hovered == -1) + ret = -2; + else + ret = item_hovered; + } + ImGui::EndPopup(); + } else { + // Output("WARNING: RadialPopupSelectMenu BeginPopup failed: %s\n", popup_id.c_str()); + } + ImGui::PopStyleColor(3); + return ret; +} + +bool PiGui::CircularSlider(const ImVec2 ¢er, float *v, float v_min, float v_max) +{ + PROFILE_SCOPED() + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + ImGuiWindow *window = ImGui::GetCurrentWindow(); + const ImGuiID id = window->GetID("circularslider"); + draw_list->AddCircle(center, 17, ImColor(100, 100, 100), 128, 12.0); + draw_list->PathArcTo(center, 17, 0, M_PI * 2.0 * (*v - v_min) / (v_max - v_min), 64); + draw_list->PathStroke(ImColor(200, 200, 200), false, 12.0); + ImRect grab_bb; + return ImGui::SliderBehavior(ImRect(center.x - 17, center.y - 17, center.x + 17, center.y + 17), + id, ImGuiDataType_Float, v, &v_min, &v_max, "%.4f", 1.0, ImGuiSliderFlags_None, &grab_bb); +} + +static void drawThrust(ImDrawList *draw_list, const ImVec2 ¢er, const ImVec2 &up, float value, const ImColor &fg, const ImColor &bg) +{ + PROFILE_SCOPED() + float factor = 0.1; // how much to offset from center + const ImVec2 step(up.x * 0.5, up.y * 0.5); + const ImVec2 left(-step.y * (1.0 - factor), step.x * (1.0 - factor)); + const ImVec2 u(up.x * (1.0 - factor), up.y * (1.0 - factor)); + const ImVec2 c(center + ImVec2(u.x * factor, u.y * factor)); + const ImVec2 right(-left.x, -left.y); + const ImVec2 leftmiddle = c + step + left; + const ImVec2 rightmiddle = c + step + right; + const ImVec2 bb_lowerright = c + right; + const ImVec2 bb_upperleft = c + left + ImVec2(u.x * value, u.y * value); + const ImVec2 lefttop = c + u + left; + const ImVec2 righttop = c + u + right; + const ImVec2 minimum(fmin(bb_upperleft.x, bb_lowerright.x), fmin(bb_upperleft.y, bb_lowerright.y)); + const ImVec2 maximum(fmax(bb_upperleft.x, bb_lowerright.x), fmax(bb_upperleft.y, bb_lowerright.y)); + ImVec2 points[] = { c, leftmiddle, lefttop, righttop, rightmiddle }; + draw_list->AddConvexPolyFilled(points, 5, bg); + draw_list->PushClipRect(minimum - ImVec2(1, 1), maximum + ImVec2(1, 1)); + draw_list->AddConvexPolyFilled(points, 5, fg); + draw_list->PopClipRect(); +} + +void PiGui::ThrustIndicator(const std::string &id_string, const ImVec2 &size_arg, const ImVec4 &thrust, const ImVec4 &velocity, const ImVec4 &bg_col, int frame_padding, ImColor vel_fg, ImColor vel_bg, ImColor thrust_fg, ImColor thrust_bg) +{ + PROFILE_SCOPED() + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + const ImGuiID id = window->GetID(id_string.c_str()); + + ImVec2 pos = window->DC.CursorPos; + // if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + // pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = ImGui::CalcItemSize(size_arg, style.FramePadding.x * 2.0f, style.FramePadding.y * 2.0f); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2(static_cast(frame_padding), static_cast(frame_padding)) : style.FramePadding; + const ImRect bb(pos, pos + size + padding * 2); + const ImRect inner_bb(pos + padding, pos + padding + size); + + ImGui::ItemSize(bb, style.FramePadding.y); + if (!ImGui::ItemAdd(bb, id)) + return; + + // Render + const ImU32 col = ImGui::GetColorU32(static_cast(ImGuiCol_Button)); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + if (bg_col.w > 0.0f) + draw_list->AddRectFilled(inner_bb.Min, inner_bb.Max, ImGui::GetColorU32(bg_col)); + const ImVec2 leftupper = inner_bb.Min; + const ImVec2 rightlower = inner_bb.Max; + const ImVec2 rightcenter((rightlower.x - leftupper.x) * 0.8 + leftupper.x, (rightlower.y + leftupper.y) / 2); + const ImVec2 leftcenter((rightlower.x - leftupper.x) * 0.35 + leftupper.x, (rightlower.y + leftupper.y) / 2); + const ImVec2 up(0, -std::abs(leftupper.y - rightlower.y) * 0.4); + const ImVec2 left(-up.y, up.x); + float thrust_fwd = fmax(thrust.z, 0); + float thrust_bwd = fmax(-thrust.z, 0); + float thrust_left = fmax(-thrust.x, 0); + float thrust_right = fmax(thrust.x, 0); + float thrust_up = fmax(-thrust.y, 0); + float thrust_down = fmax(thrust.y, 0); + // actual thrust + drawThrust(draw_list, rightcenter, up, thrust_fwd, thrust_fg, thrust_bg); + drawThrust(draw_list, rightcenter, ImVec2(-up.x, -up.y), thrust_bwd, thrust_fg, thrust_bg); + drawThrust(draw_list, leftcenter, up, thrust_up, thrust_fg, thrust_bg); + drawThrust(draw_list, leftcenter, ImVec2(-up.x, -up.y), thrust_down, thrust_fg, thrust_bg); + drawThrust(draw_list, leftcenter, left, thrust_left, thrust_fg, thrust_bg); + drawThrust(draw_list, leftcenter, ImVec2(-left.x, -left.y), thrust_right, thrust_fg, thrust_bg); + // forward/back velocity + draw_list->AddLine(rightcenter + up, rightcenter - up, vel_bg, 3); + draw_list->AddLine(rightcenter, rightcenter - up * velocity.z, vel_fg, 3); + // left/right velocity + draw_list->AddLine(leftcenter + left, leftcenter - left, vel_bg, 3); + draw_list->AddLine(leftcenter, leftcenter + left * velocity.x, vel_fg, 3); + // up/down velocity + draw_list->AddLine(leftcenter + up, leftcenter - up, vel_bg, 3); + draw_list->AddLine(leftcenter, leftcenter + up * velocity.y, vel_fg, 3); + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); +} + +bool PiGui::LowThrustButton(const char *id_string, const ImVec2 &size_arg, int thrust_level, const ImVec4 &bg_col, int frame_padding, ImColor gauge_fg, ImColor gauge_bg) +{ + PROFILE_SCOPED() + std::string label = std::to_string(thrust_level); + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + const ImGuiID id = window->GetID(id_string); + const ImVec2 label_size = ImGui::CalcTextSize(label.c_str(), NULL, true); + + ImVec2 pos = window->DC.CursorPos; + // if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + // pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2(static_cast(frame_padding), static_cast(frame_padding)) : style.FramePadding; + const ImRect bb(pos, pos + size + padding * 2); + const ImRect inner_bb(pos + padding, pos + padding + size); + + ImGui::ItemSize(bb, style.FramePadding.y); + if (!ImGui::ItemAdd(bb, id)) + return false; + + // if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); // flags + + // Render + const ImU32 col = ImGui::GetColorU32(static_cast((hovered && held) ? ImGuiCol_ButtonActive : (hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button))); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + const ImVec2 center = (inner_bb.Min + inner_bb.Max) / 2; + float radius = (inner_bb.Max.x - inner_bb.Min.x) * 0.4; + float thickness = 4; + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + if (bg_col.w > 0.0f) + draw_list->AddRectFilled(inner_bb.Min, inner_bb.Max, ImGui::GetColorU32(bg_col)); + + draw_list->PathArcTo(center, radius, 0, IM_PI * 2, 16); + draw_list->PathStroke(gauge_bg, false, thickness); + + draw_list->PathArcTo(center, radius, IM_PI, IM_PI + IM_PI * 2 * (thrust_level / 100.0), 16); + draw_list->PathStroke(gauge_fg, false, thickness); + ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label.c_str(), NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + return pressed; +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +// The color used are the button colors. +bool PiGui::ButtonImageSized(ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &imgSize, const ImVec2 &uv0, const ImVec2 &uv1, int frame_padding, const ImVec4 &bg_col, const ImVec4 &tint_col) +{ + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. + ImGui::PushID((void *)user_texture_id); + const ImGuiID id = window->GetID("#image"); + ImGui::PopID(); + + ImVec2 imgPadding = (size - imgSize) / 2; + imgPadding.x = imgPadding.x < 0 || imgSize.x <= 0 ? 0 : imgPadding.x; + imgPadding.y = imgPadding.y < 0 || imgSize.y <= 0 ? 0 : imgPadding.y; + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + const ImRect image_bb(window->DC.CursorPos + padding + imgPadding, window->DC.CursorPos + padding + size - imgPadding); + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, ImGui::GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, ImGui::GetColorU32(tint_col)); + + return pressed; +} From 053ff32ef14a8c224b64803372a4cbd8d684cac9 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 31 Mar 2020 02:50:11 -0400 Subject: [PATCH 4/9] Decouple PiGui from Pi::renderer Explicitly inject the renderer dependency, instead of relying on Pi::renderer existing Made it Pi.cpp's responsibility to hide the mouse cursor. --- src/Pi.cpp | 14 +++++++++---- src/core/GuiApplication.cpp | 2 +- src/lua/LuaPiGui.cpp | 2 +- src/pigui/PiGui.cpp | 42 ++++++++++++++++--------------------- src/pigui/PiGui.h | 19 +++++++++-------- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/Pi.cpp b/src/Pi.cpp index 2b346127b..a00c9ba11 100644 --- a/src/Pi.cpp +++ b/src/Pi.cpp @@ -501,7 +501,7 @@ void LoadStep::Start() PiGUI::Lua::Init(); // Don't render the first frame, just make sure all of our fonts are loaded - Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); + Pi::pigui->NewFrame(); PiGUI::RunHandler(0.01, "INIT"); Pi::pigui->EndFrame(); @@ -614,7 +614,7 @@ void LoadStep::Update(float deltaTime) Output("Loading [%02.f%%]: %s took %.2fms\n", progress * 100., loader.name.c_str(), timer.milliseconds()); - Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); + Pi::pigui->NewFrame(); PiGUI::RunHandler(progress, "INIT"); Pi::pigui->Render(); @@ -653,7 +653,7 @@ void MainMenu::Update(float deltaTime) Pi::intro->Draw(deltaTime); - Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); + Pi::pigui->NewFrame(); PiGUI::RunHandler(deltaTime, "MAINMENU"); Pi::pigui->Render(); @@ -1075,10 +1075,16 @@ void GameLoop::Update(float deltaTime) Pi::ui->Draw(); } + // Ask ImGui to hide OS cursor if we're capturing it for input: + // it will do this if GetMouseCursor == ImGuiMouseCursor_None. + if (Pi::input->IsCapturingMouse()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_None); + } + // TODO: the escape menu depends on HandleEvents() being called before NewFrame() // Move HandleEvents to either the end of the loop or the very start of the loop // The goal is to be able to call imgui functions for debugging inside C++ code - Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); + Pi::pigui->NewFrame(); if (Pi::game && !Pi::player->IsDead()) { // FIXME: Always begin a camera frame because WorldSpaceToScreenSpace diff --git a/src/core/GuiApplication.cpp b/src/core/GuiApplication.cpp index 9f4db5547..65a461dd9 100644 --- a/src/core/GuiApplication.cpp +++ b/src/core/GuiApplication.cpp @@ -210,7 +210,7 @@ void GuiApplication::ShutdownInput() PiGui::Instance *GuiApplication::StartupPiGui() { m_pigui.Reset(new PiGui::Instance()); - m_pigui->Init(m_renderer->GetSDLWindow()); + m_pigui->Init(GetRenderer()); return m_pigui.Get(); } diff --git a/src/lua/LuaPiGui.cpp b/src/lua/LuaPiGui.cpp index 47583de6d..e2c3680e6 100644 --- a/src/lua/LuaPiGui.cpp +++ b/src/lua/LuaPiGui.cpp @@ -2370,7 +2370,7 @@ static int l_pigui_load_texture_from_svg(lua_State *l) std::string svg_filename = LuaPull(l, 2); int width = LuaPull(l, 3); int height = LuaPull(l, 4); - ImTextureID id = PiGui::RenderSVG(svg_filename, width, height); + ImTextureID id = PiGui::RenderSVG(Pi::renderer, svg_filename, width, height); // LuaPush(l, id); lua_pushlightuserdata(l, id); return 1; diff --git a/src/pigui/PiGui.cpp b/src/pigui/PiGui.cpp index af63fb752..daefbf7f8 100644 --- a/src/pigui/PiGui.cpp +++ b/src/pigui/PiGui.cpp @@ -5,6 +5,7 @@ #include "Input.h" #include "Pi.h" +#include "graphics/Graphics.h" #include "graphics/Texture.h" #include "graphics/opengl/RendererGL.h" #include "graphics/opengl/TextureGL.h" // nasty, usage of GL is implementation specific @@ -32,10 +33,10 @@ std::vector &PiGui::GetSVGTextures() return m_svg_textures; } -static void *makeTexture(unsigned char *pixels, int width, int height) +static ImTextureID makeTexture(Graphics::Renderer *renderer, unsigned char *pixels, int width, int height) { PROFILE_SCOPED() - // this is not very pretty code and uses the Graphics::TextureGL class directly + // this is not very pretty code // Texture descriptor defines the size, type. // Gone for LINEAR_CLAMP here and RGBA like the original code const vector2f texSize(1.0f, 1.0f); @@ -45,17 +46,14 @@ static void *makeTexture(unsigned char *pixels, int width, int height) false, false, false, 0, Graphics::TEXTURE_2D); // Create the texture, calling it via renderer directly avoids the caching call of TextureBuilder // However interestingly this gets called twice which would have been a WIN for the TextureBuilder :/ - Graphics::Texture *pTex = Pi::renderer->CreateTexture(texDesc); + Graphics::Texture *pTex = renderer->CreateTexture(texDesc); // Update it with the actual pixels, this is a two step process due to legacy code pTex->Update(pixels, dataSize, Graphics::TEXTURE_RGBA_8888); - // nasty bit as I invoke the TextureGL - Graphics::OGL::TextureGL *pGLTex = reinterpret_cast(pTex); - Uint32 result = pGLTex->GetTextureID(); PiGui::GetSVGTextures().push_back(pTex); // store for cleanup later - return reinterpret_cast(result); + return reinterpret_cast(uintptr_t(pTex->GetTextureID())); } -ImTextureID PiGui::RenderSVG(std::string svgFilename, int width, int height) +ImTextureID PiGui::RenderSVG(Graphics::Renderer *renderer, std::string svgFilename, int width, int height) { PROFILE_SCOPED() Output("nanosvg: %s %dx%d\n", svgFilename.c_str(), width, height); @@ -111,7 +109,7 @@ ImTextureID PiGui::RenderSVG(std::string svgFilename, int width, int height) } nsvgDeleteRasterizer(rast); nsvgDelete(image); - return makeTexture(img, W, H); + return makeTexture(renderer, img, W, H); } // @@ -223,9 +221,11 @@ void PiDefaultStyle(ImGuiStyle &style) style.WindowBorderSize = 0.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. } -void Instance::Init(SDL_Window *window) +// TODO: this isn't very RAII friendly, are we sure we need to call Init() seperately from creating the instance? +void Instance::Init(Graphics::Renderer *renderer) { PROFILE_SCOPED() + m_renderer = renderer; IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -233,8 +233,8 @@ void Instance::Init(SDL_Window *window) // TODO: FIXME before upgrading! The sdl_gl_context parameter is currently // unused, but that is slated to change very soon. // We will need to fill this with a valid pointer to the OpenGL context. - ImGui_ImplSDL2_InitForOpenGL(window, NULL); - switch (Pi::renderer->GetRendererType()) { + ImGui_ImplSDL2_InitForOpenGL(m_renderer->GetSDLWindow(), NULL); + switch (m_renderer->GetRendererType()) { default: case Graphics::RENDERER_DUMMY: Error("RENDERER_DUMMY is not a valid renderer, aborting."); @@ -266,17 +266,11 @@ bool Instance::ProcessEvent(SDL_Event *event) return false; } -void Instance::NewFrame(SDL_Window *window) +void Instance::NewFrame() { PROFILE_SCOPED() - // Ask ImGui to hide OS cursor if we're capturing it for input: - // it will do this if GetMouseCursor == ImGuiMouseCursor_None. - if (Pi::input->IsCapturingMouse()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_None); - } - - switch (Pi::renderer->GetRendererType()) { + switch (m_renderer->GetRendererType()) { default: case Graphics::RENDERER_DUMMY: Error("RENDERER_DUMMY is not a valid renderer, aborting."); @@ -285,10 +279,10 @@ void Instance::NewFrame(SDL_Window *window) ImGui_ImplOpenGL3_NewFrame(); break; } - ImGui_ImplSDL2_NewFrame(window); + ImGui_ImplSDL2_NewFrame(m_renderer->GetSDLWindow()); ImGui::NewFrame(); - Pi::renderer->CheckRenderErrors(__FUNCTION__, __LINE__); + m_renderer->CheckRenderErrors(__FUNCTION__, __LINE__); ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow); } @@ -326,7 +320,7 @@ void Instance::Render() ImGui::Render(); - switch (Pi::renderer->GetRendererType()) { + switch (m_renderer->GetRendererType()) { default: case Graphics::RENDERER_DUMMY: return; @@ -421,7 +415,7 @@ void Instance::Uninit() delete tex; } - switch (Pi::renderer->GetRendererType()) { + switch (m_renderer->GetRendererType()) { default: case Graphics::RENDERER_DUMMY: return; diff --git a/src/pigui/PiGui.h b/src/pigui/PiGui.h index 1892da92a..9a53d0bd5 100644 --- a/src/pigui/PiGui.h +++ b/src/pigui/PiGui.h @@ -13,7 +13,8 @@ namespace Graphics { class Texture; -} + class Renderer; +} // namespace Graphics namespace PiGui { @@ -87,8 +88,11 @@ namespace PiGui { public: Instance(); + void Init(Graphics::Renderer *renderer); + void Uninit(); + // Call at the start of every frame. Calls ImGui::NewFrame() internally. - void NewFrame(SDL_Window *window); + void NewFrame(); // Call at the end of a frame that you're not going to render the results of void EndFrame(); @@ -96,13 +100,8 @@ namespace PiGui { // Calls ImGui::EndFrame() internally and does book-keeping before rendering. void Render(); - void Init(SDL_Window *window); - - ImFont *GetFont(const std::string &name, int size); - - void Uninit(); - ImFont *AddFont(const std::string &name, int size); + ImFont *GetFont(const std::string &name, int size); void AddGlyph(ImFont *font, unsigned short glyph); @@ -111,6 +110,8 @@ namespace PiGui { void RefreshFontsTexture(); private: + Graphics::Renderer *m_renderer; + std::map, ImFont *> m_fonts; std::map> m_im_fonts; std::map, PiFont> m_pi_fonts; @@ -143,6 +144,6 @@ namespace PiGui { } std::vector &GetSVGTextures(); - ImTextureID RenderSVG(std::string svgFilename, int width, int height); + ImTextureID RenderSVG(Graphics::Renderer *renderer, std::string svgFilename, int width, int height); } //namespace PiGui From ca3fde811fb5f9ce75017038582d00738c710c31 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 1 Apr 2020 17:02:12 -0400 Subject: [PATCH 5/9] Decouple Input from Pi, add key state tracking Added IsKeyPressed / Released methods to track per-frame key state Added GetMouseWheel so users don't have to track mouse wheel events themselves Input keeps a pointer to a config instance, completely decoupling it from Pi --- src/Input.cpp | 32 +++++++++++++++++++++++++------- src/Input.h | 26 +++++++++++++++++++------- src/core/GuiApplication.cpp | 9 +++++---- src/core/GuiApplication.h | 8 +++++--- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/Input.cpp b/src/Input.cpp index 2c8fee352..fb8ded619 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -9,7 +9,8 @@ #include -Input::Input(const GameConfig *config) : +Input::Input(IniConfig *config) : + m_config(config), m_capturingMouse(false), mouseYInvert(false), joystickEnabled(true), @@ -17,8 +18,8 @@ Input::Input(const GameConfig *config) : mouseButton(), mouseMotion() { - joystickEnabled = (config->Int("EnableJoystick")) ? true : false; - mouseYInvert = (config->Int("InvertMouseY")) ? true : false; + joystickEnabled = (m_config->Int("EnableJoystick")) ? true : false; + mouseYInvert = (m_config->Int("InvertMouseY")) ? true : false; InitJoysticks(); } @@ -41,6 +42,20 @@ void Input::InitGame() void Input::NewFrame() { mouseMotion.fill(0); + mouseWheel = 0; + for (auto &k : keyState) { + auto &val = keyState[k.first]; + switch (k.second) { + case 1: // if we were just pressed last frame, migrate to held state + val = 2; + break; + case 4: // if we were just released last frame, migrate to empty state + val = 0; + break; + default: // otherwise, no need to do anything + break; + } + } } InputResponse Input::InputFrame::ProcessSDLEvent(SDL_Event &event) @@ -106,7 +121,7 @@ KeyBindings::ActionBinding *Input::AddActionBinding(std::string id, BindingGroup group->bindings[id] = BindingGroup::ENTRY_ACTION; // Load from the config - std::string config_str = Pi::config->String(id.c_str()); + std::string config_str = m_config->String(id.c_str()); if (config_str.length() > 0) binding.SetFromString(config_str); return &(actionBindings[id] = binding); @@ -121,7 +136,7 @@ KeyBindings::AxisBinding *Input::AddAxisBinding(std::string id, BindingGroup *gr group->bindings[id] = BindingGroup::ENTRY_AXIS; // Load from the config - std::string config_str = Pi::config->String(id.c_str()); + std::string config_str = m_config->String(id.c_str()); if (config_str.length() > 0) binding.SetFromString(config_str); return &(axisBindings[id] = binding); @@ -131,12 +146,14 @@ void Input::HandleSDLEvent(SDL_Event &event) { switch (event.type) { case SDL_KEYDOWN: - keyState[event.key.keysym.sym] = true; + // Set key state to "just pressed" + keyState[event.key.keysym.sym] = 1; keyModState = event.key.keysym.mod; onKeyPress.emit(&event.key.keysym); break; case SDL_KEYUP: - keyState[event.key.keysym.sym] = false; + // Set key state to "just released" + keyState[event.key.keysym.sym] = 4; keyModState = event.key.keysym.mod; onKeyRelease.emit(&event.key.keysym); break; @@ -155,6 +172,7 @@ void Input::HandleSDLEvent(SDL_Event &event) } break; case SDL_MOUSEWHEEL: + mouseWheel = event.wheel.y; onMouseWheel.emit(event.wheel.y > 0); // true = up break; case SDL_MOUSEMOTION: diff --git a/src/Input.h b/src/Input.h index e9dfc0e35..b5e88bb9f 100644 --- a/src/Input.h +++ b/src/Input.h @@ -9,14 +9,11 @@ #include -class GameConfig; +class IniConfig; class Input { - // TODO: better decouple these two classes. - friend class Pi; - public: - Input(const GameConfig *config); + Input(IniConfig *config); void InitGame(); void HandleSDLEvent(SDL_Event &ev); void NewFrame(); @@ -94,7 +91,17 @@ public: return axisBindings.count(id) ? &axisBindings[id] : nullptr; } - bool KeyState(SDL_Keycode k) { return keyState[k]; } + bool KeyState(SDL_Keycode k) { return IsKeyDown(k); } + + // returns true if key K is currently pressed + bool IsKeyDown(SDL_Keycode k) { return keyState[k] & 0x3; } + + // returns true if key K was pressed this frame + bool IsKeyPressed(SDL_Keycode k) { return keyState[k] == 1; } + + // returns true if key K was released this frame + bool IsKeyReleased(SDL_Keycode k) { return keyState[k] == 4; } + int KeyModState() { return keyModState; } int JoystickButtonState(int joystick, int button); @@ -135,6 +142,8 @@ public: std::copy_n(mouseMotion.data(), mouseMotion.size(), motion); } + int GetMouseWheel() { return mouseWheel; } + // Capturing the mouse hides the cursor, puts the mouse into relative mode, // and passes all mouse inputs to the input system, regardless of whether // ImGui is using them or not. @@ -154,10 +163,13 @@ public: private: void InitJoysticks(); - std::map keyState; + IniConfig *m_config; + + std::map keyState; int keyModState; std::array mouseButton; std::array mouseMotion; + int mouseWheel; bool m_capturingMouse; bool joystickEnabled; diff --git a/src/core/GuiApplication.cpp b/src/core/GuiApplication.cpp index 65a461dd9..8b6bdca52 100644 --- a/src/core/GuiApplication.cpp +++ b/src/core/GuiApplication.cpp @@ -32,7 +32,8 @@ void GuiApplication::BeginFrame() void GuiApplication::DrawRenderTarget() { #if RTT - m_renderer->BeginFrame(); + m_renderer->SetRenderTarget(nullptr); + m_renderer->ClearScreen(); m_renderer->SetViewport(0, 0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight()); m_renderer->SetTransform(matrix4x4f::Identity()); @@ -61,9 +62,9 @@ void GuiApplication::DrawRenderTarget() void GuiApplication::EndFrame() { #if RTT - m_renderer->SetRenderTarget(nullptr); DrawRenderTarget(); #endif + m_renderer->EndFrame(); m_renderer->SwapBuffers(); } @@ -149,7 +150,7 @@ void GuiApplication::HandleEvents() } } -Graphics::Renderer *GuiApplication::StartupRenderer(const GameConfig *config, bool hidden) +Graphics::Renderer *GuiApplication::StartupRenderer(IniConfig *config, bool hidden) { PROFILE_SCOPED() // Initialize SDL @@ -195,7 +196,7 @@ void GuiApplication::ShutdownRenderer() SDL_QuitSubSystem(SDL_INIT_VIDEO); } -Input *GuiApplication::StartupInput(const GameConfig *config) +Input *GuiApplication::StartupInput(IniConfig *config) { m_input.reset(new Input(config)); diff --git a/src/core/GuiApplication.h b/src/core/GuiApplication.h index f46c1922c..bccd1b6ab 100644 --- a/src/core/GuiApplication.h +++ b/src/core/GuiApplication.h @@ -4,7 +4,6 @@ #pragma once #include "Application.h" -#include "GameConfig.h" #include "Input.h" #include "RefCounted.h" #include "SDL_events.h" @@ -14,6 +13,8 @@ #include "graphics/RenderTarget.h" #include "graphics/Renderer.h" +class IniConfig; + class GuiApplication : public Application { public: GuiApplication(std::string title) : @@ -29,11 +30,12 @@ protected: // framebuffer void DrawRenderTarget(); + // TODO: unify config handling, possibly make the config an Application member // Call this from your Startup() method - Graphics::Renderer *StartupRenderer(const GameConfig *config, bool hidden = false); + Graphics::Renderer *StartupRenderer(IniConfig *config, bool hidden = false); // Call this from your Startup() method - Input *StartupInput(const GameConfig *config); + Input *StartupInput(IniConfig *config); // Call this from your Startup() method PiGui::Instance *StartupPiGui(); From 2ea32811f7f65797470543d9930eed2f032b475f Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 1 Apr 2020 17:02:51 -0400 Subject: [PATCH 6/9] Always clean up the active lifecycle --- src/core/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 6ffdd45af..64df05b71 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -141,7 +141,7 @@ void Application::Run() EndFrame(); - if (m_activeLifecycle->m_endLifecycle) { + if (m_activeLifecycle->m_endLifecycle || !m_applicationRunning) { EndLifecycle(); } From b754aeed2a575a241d0d2abadff15056cadb993b Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 1 Apr 2020 17:04:52 -0400 Subject: [PATCH 7/9] Rewrite ModelViewer to use GuiApplication and PiGui "Snap to Direction" keybinds don't work, due to Input not dispatching their event handlers. Will be resolved in a later sprint on the Input system. --- src/ModelViewer.cpp | 1397 ++++++++++++++++++++----------------------- src/ModelViewer.h | 210 ++++--- src/main.cpp | 4 +- 3 files changed, 785 insertions(+), 826 deletions(-) diff --git a/src/ModelViewer.cpp b/src/ModelViewer.cpp index 228391809..af640269a 100644 --- a/src/ModelViewer.cpp +++ b/src/ModelViewer.cpp @@ -5,8 +5,10 @@ #include "FileSystem.h" #include "GameConfig.h" #include "GameSaveError.h" +#include "KeyBindings.h" #include "ModManager.h" #include "OS.h" +#include "SDL_keycode.h" #include "StringF.h" #include "graphics/Drawables.h" #include "graphics/Graphics.h" @@ -19,7 +21,12 @@ #include "scenegraph/DumpVisitor.h" #include "scenegraph/FindNodeVisitor.h" #include "scenegraph/ModelSkin.h" -#include + +#include "imgui/imgui.h" + +#include + +#include "Pi.h" //default options ModelViewer::Options::Options() : @@ -53,79 +60,138 @@ namespace { return v; } - //extract color from RGB sliders - Color get_slider_color(UI::Slider *r, UI::Slider *g, UI::Slider *b) - { - return Color(r->GetValue() * 255.f, g->GetValue() * 255.f, b->GetValue() * 255.f); - } - - float get_thrust(const UI::Slider *s) - { - return 1.f - (2.f * s->GetValue()); - } - - //add a horizontal button/label pair to a box - void add_pair(UI::Context *c, UI::Box *box, UI::Widget *widget, const std::string &label) - { - box->PackEnd(c->HBox(5)->PackEnd(UI::WidgetSet(widget, c->Label(label)))); - } - - void collect_decals(std::vector &list) - { - const std::string basepath("textures/decals"); - FileSystem::FileSource &fileSource = FileSystem::gameDataFiles; - for (FileSystem::FileEnumerator files(fileSource, basepath); !files.Finished(); files.Next()) { - const FileSystem::FileInfo &info = files.Current(); - const std::string &fpath = info.GetPath(); - - //check it's the expected type - if (info.IsFile() && ends_with_ci(fpath, ".dds")) { - list.push_back(info.GetName().substr(0, info.GetName().size() - 4)); - } - } - } - float zoom_distance(const float base_distance, const float zoom) { return base_distance * powf(2.0f, zoom); } } // namespace -ModelViewer::ModelViewer(Graphics::Renderer *r, LuaManager *lm) : - m_done(false), +// An adaptor for automagic reverse range-for iteration of containers +// One might be able to specialize this for raw arrays, but that's beyond the +// point of its use-case. +// One might also point out that this is surely more work to code than simply +// writing an explicit iterator loop, to which I say: bah humbug! +template +struct reverse_container_t { + using iterator = std::reverse_iterator; + using const_iterator = std::reverse_iterator; + + using value_type = typename std::remove_reference::type; + + reverse_container_t(value_type &ref) : + ref(ref) {} + + iterator begin() { return iterator(ref.end()); } + const_iterator begin() const { return const_iterator(ref.cend()); } + + iterator end() { return iterator(ref.begin()); } + const_iterator end() const { return const_iterator(ref.cbegin()); } + +private: + value_type &ref; +}; + +// Use this function for automatic template parameter deduction +template +reverse_container_t reverse_container(T &ref) { return reverse_container_t(ref); } + +namespace ImGui { + bool ColorEdit3(const char *label, Color &color) + { + Color4f _c = color.ToColor4f(); + bool changed = ColorEdit3(label, &_c[0]); + color = Color(_c); + return changed; + } +} // namespace ImGui + +void ModelViewerApp::Startup() +{ + Application::Startup(); + OS::RedirectStdio(); + + std::unique_ptr config(new GameConfig); + + Lua::Init(); + + ModManager::Init(); + + Graphics::RendererOGL::RegisterRenderer(); + + auto *renderer = StartupRenderer(config.get()); + + // FIXME MAJOR FIXME: Action / Axis bindings depend on Pi::input to get their data. + // This is OBVIOUSLY suboptimal, and *must* be redesigned. + // Either make Input a singleton (lots of function overhead when polling axes) + // or cache input state on the binding itself (probably the best option) + + Pi::input = StartupInput(config.get()); + StartupPiGui(); + + NavLights::Init(renderer); + Shields::Init(renderer); + + //run main loop until quit + m_modelViewer = std::make_shared(this, Lua::manager); + if (!m_modelName.empty()) + m_modelViewer->SetModel(m_modelName); + + m_modelViewer->ResetCamera(); + + QueueLifecycle(m_modelViewer); +} + +void ModelViewerApp::Shutdown() +{ + //uninit components + m_modelViewer.reset(); + Lua::Uninit(); + Shields::Uninit(); + NavLights::Uninit(); + Graphics::Uninit(); + + ShutdownRenderer(); + Application::Shutdown(); +} + +void ModelViewerApp::PreUpdate() +{ + HandleEvents(); + GetPiGui()->NewFrame(); +} + +void ModelViewerApp::PostUpdate() +{ + GetRenderer()->ClearDepthBuffer(); + GetPiGui()->Render(); +} + +ModelViewer::ModelViewer(ModelViewerApp *app, LuaManager *lm) : + m_app(app), + m_input(app->GetInput()), + m_pigui(app->GetPiGui()), + m_renderer(app->GetRenderer()), m_screenshotQueued(false), m_shieldIsHit(false), m_settingColourSliders(false), m_shieldHitPan(-1.48f), - m_frameTime(0.0), - m_renderer(r), m_decalTexture(0), m_rotX(0), m_rotY(0), m_zoom(0), m_baseDistance(100.0f), m_rng(time(0)), - m_currentAnimation(0), - m_model(0), - m_modelName("") + m_modelIsShip(false), + m_colors({ Color(255, 0, 0), + Color(0, 255, 0), + Color(0, 0, 255) }), + m_modelName(""), + m_requestedModelName(), + m_logWindowSize(350.0f, 500.0f), + m_animWindowSize(0.0f, 150.0f) { - OS::RedirectStdio(); - m_ui.Reset(new UI::Context(lm, r, Graphics::GetScreenWidth(), Graphics::GetScreenHeight())); - m_ui->SetMousePointer("icons/cursors/mouse_cursor_2.png", UI::Point(15, 8)); - - m_log = m_ui->MultiLineText(""); - m_log->SetFont(UI::Widget::FONT_SMALLEST); - - m_logScroller.Reset(m_ui->Scroller()); - m_logScroller->SetInnerWidget(m_ui->ColorBackground(Color(0x0, 0x0, 0x0, 0x40))->SetInnerWidget(m_log)); - - std::fill(m_mouseButton, m_mouseButton + COUNTOF(m_mouseButton), false); - std::fill(m_mouseMotion, m_mouseMotion + 2, 0); - - //some widgets - animSlider = 0; - - onModelChanged.connect(sigc::mem_fun(*this, &ModelViewer::SetupUI)); + onModelChanged.connect(sigc::mem_fun(*this, &ModelViewer::OnModelChanged)); + SetupAxes(); //for grid, background Graphics::RenderStateDesc rsd; @@ -134,105 +200,39 @@ ModelViewer::ModelViewer(Graphics::Renderer *r, LuaManager *lm) : m_bgState = m_renderer->CreateRenderState(rsd); } -ModelViewer::~ModelViewer() +void ModelViewer::Start() +{ + UpdateModelList(); + UpdateDecalList(); +} + +void ModelViewer::End() { ClearModel(); } -void ModelViewer::Run(const std::string &modelName) -{ - std::unique_ptr config(new GameConfig); - - //init components - FileSystem::Init(); - FileSystem::userFiles.MakeDirectory(""); // ensure the config directory exists - if (SDL_Init(SDL_INIT_VIDEO) < 0) - Error("SDL initialization failed: %s\n", SDL_GetError()); - - Lua::Init(); - - ModManager::Init(); - - Graphics::RendererOGL::RegisterRenderer(); - - // determine what renderer we should use, default to Opengl 3.x - const std::string rendererName = config->String("RendererName", Graphics::RendererNameFromType(Graphics::RENDERER_OPENGL_3x)); - Graphics::RendererType rType = Graphics::RENDERER_OPENGL_3x; - //if(rendererName == Graphics::RendererNameFromType(Graphics::RENDERER_OPENGL_3x)) - //{ - // rType = Graphics::RENDERER_OPENGL_3x; - //} - - //video - Graphics::Settings videoSettings = {}; - videoSettings.rendererType = rType; - videoSettings.width = config->Int("ScrWidth"); - videoSettings.height = config->Int("ScrHeight"); - videoSettings.fullscreen = (config->Int("StartFullscreen") != 0); - videoSettings.hidden = false; - videoSettings.requestedSamples = config->Int("AntiAliasingMode"); - videoSettings.vsync = (config->Int("VSync") != 0); - videoSettings.useTextureCompression = (config->Int("UseTextureCompression") != 0); - videoSettings.useAnisotropicFiltering = (config->Int("UseAnisotropicFiltering") != 0); - videoSettings.iconFile = OS::GetIconFilename(); - videoSettings.title = "Model viewer"; - Graphics::Renderer *renderer = Graphics::Init(videoSettings); - - NavLights::Init(renderer); - Shields::Init(renderer); - - //run main loop until quit - ModelViewer *viewer = new ModelViewer(renderer, Lua::manager); - viewer->SetModel(modelName); - viewer->ResetCamera(); - viewer->MainLoop(); - - //uninit components - delete viewer; - Lua::Uninit(); - delete renderer; - Shields::Uninit(); - NavLights::Uninit(); - Graphics::Uninit(); - FileSystem::Uninit(); - SDL_Quit(); -} - -bool ModelViewer::OnPickModel(UI::List *list) -{ - m_requestedModelName = list->GetSelectedOption(); - return true; -} - -bool ModelViewer::OnQuit() -{ - m_done = true; - return true; -} - -bool ModelViewer::OnReloadModel(UI::Widget *w) +void ModelViewer::ReloadModel() { + AddLog(stringf("Reloading model %0", m_modelName)); //camera is not reset, it would be annoying when //tweaking materials SetModel(m_modelName); - return true; + m_resetLogScroll = true; } -bool ModelViewer::OnToggleCollMesh(UI::CheckBox *w) +void ModelViewer::ToggleCollMesh() { m_options.showDockingLocators = !m_options.showDockingLocators; m_options.showCollMesh = !m_options.showCollMesh; m_options.showAabb = m_options.showCollMesh; - return m_options.showCollMesh; } -bool ModelViewer::OnToggleShowShields(UI::CheckBox *w) +void ModelViewer::ToggleShowShields() { m_options.showShields = !m_options.showShields; - return m_options.showShields; } -bool ModelViewer::OnToggleGrid(UI::Widget *) +void ModelViewer::ToggleGrid() { if (!m_options.showGrid) { m_options.showGrid = true; @@ -245,10 +245,9 @@ bool ModelViewer::OnToggleGrid(UI::Widget *) } } AddLog(m_options.showGrid ? stringf("Grid: %0{d}", int(m_options.gridInterval)) : "Grid: off"); - return m_options.showGrid; } -bool ModelViewer::OnToggleGuns(UI::CheckBox *w) +void ModelViewer::ToggleGuns() { if (!m_gunModel) { CreateTestResources(); @@ -256,7 +255,7 @@ bool ModelViewer::OnToggleGuns(UI::CheckBox *w) if (!m_gunModel) { AddLog("test_gun.model not available"); - return false; + return; } m_options.attachGuns = !m_options.attachGuns; @@ -264,7 +263,7 @@ bool ModelViewer::OnToggleGuns(UI::CheckBox *w) m_model->FindTagsByStartOfName("tag_gun_", tags); if (tags.empty()) { AddLog("Missing tags \"tag_gun_XXX\" in model"); - return false; + return; } if (m_options.attachGuns) { for (auto tag : tags) { @@ -276,28 +275,18 @@ bool ModelViewer::OnToggleGuns(UI::CheckBox *w) tag->RemoveChildAt(0); } } - return true; + return; } -bool ModelViewer::OnRandomColor(UI::Widget *) +bool ModelViewer::SetRandomColor() { if (!m_model || !m_model->SupportsPatterns()) return false; SceneGraph::ModelSkin skin; skin.SetRandomColors(m_rng); - skin.Apply(m_model); + skin.Apply(m_model.get()); - // We need this flag setting so that we don't override what we're changing in OnModelColorsChanged - m_settingColourSliders = true; - const std::vector &colors = skin.GetColors(); - for (unsigned int i = 0; i < 3; i++) { - for (unsigned int j = 0; j < 3; j++) { - // use ToColor4f to get the colours in 0..1 range required - if (colorSliders[(i * 3) + j]) - colorSliders[(i * 3) + j]->SetValue(colors[i].ToColor4f()[j]); - } - } - m_settingColourSliders = false; + m_colors = skin.GetColors(); return true; } @@ -313,13 +302,7 @@ void ModelViewer::UpdateShield() } } -bool ModelViewer::OnHitIt(UI::Widget *) -{ - HitImpl(); - return true; -} - -void ModelViewer::HitImpl() +void ModelViewer::HitIt() { if (m_model) { assert(m_shields.get()); @@ -345,98 +328,74 @@ void ModelViewer::HitImpl() void ModelViewer::AddLog(const std::string &line) { - m_log->AppendText(line + "\n"); - m_logScroller->SetScrollPosition(1.0f); + m_log.push_back(line); Output("%s\n", line.c_str()); } -void ModelViewer::ChangeCameraPreset(SDL_Keycode key, SDL_Keymod mod) +void ModelViewer::ChangeCameraPreset(CameraPreset preset) { if (!m_model) return; - // Like Blender, but a bit different because we like that - // 1 - front (+ctrl back) - // 7 - top (+ctrl bottom) - // 3 - left (+ctrl right) - // 2,4,6,8 incrementally rotate + switch (preset) { + case CameraPreset::Bottom: + m_rotX = -90.0f; + m_rotY = 0.0f; + break; + case CameraPreset::Top: + m_rotX = 90.0f; + m_rotY = 0.0f; + break; - const bool invert = mod & KMOD_CTRL; + case CameraPreset::Left: + m_rotX = 0.f; + m_rotY = 90.0f; + break; + case CameraPreset::Right: + m_rotX = 0.f; + m_rotY = -90.0f; + break; - switch (key) { - case SDLK_KP_7: - case SDLK_u: - m_rotX = invert ? -90.f : 90.f; - m_rotY = 0.f; - AddLog(invert ? "Bottom view" : "Top view"); - break; - case SDLK_KP_3: - case SDLK_PERIOD: + case CameraPreset::Front: m_rotX = 0.f; - m_rotY = invert ? -90.f : 90.f; - AddLog(invert ? "Right view" : "Left view"); + m_rotY = 180.0f; break; - case SDLK_KP_1: - case SDLK_m: + case CameraPreset::Back: m_rotX = 0.f; - m_rotY = invert ? 0.f : 180.f; - AddLog(invert ? "Rear view" : "Front view"); + m_rotY = 0.0f; break; - case SDLK_KP_4: - case SDLK_j: - m_rotY += 15.f; - break; - case SDLK_KP_6: - case SDLK_l: - m_rotY -= 15.f; - break; - case SDLK_KP_2: - case SDLK_COMMA: - m_rotX += 15.f; - break; - case SDLK_KP_8: - case SDLK_i: - m_rotX -= 15.f; - break; - default: - break; - //no others yet } } void ModelViewer::ToggleViewControlMode() { m_options.mouselookEnabled = !m_options.mouselookEnabled; - // FIXME: update modelviewer to use Input::SetCaptureMouse instead - SDL_SetWindowGrab(m_renderer->GetSDLWindow(), SDL_bool(m_options.mouselookEnabled)); - SDL_SetRelativeMouseMode(SDL_bool(m_options.mouselookEnabled)); + m_input->SetCapturingMouse(m_options.mouselookEnabled); if (m_options.mouselookEnabled) { m_viewRot = matrix3x3f::RotateY(DEG2RAD(m_rotY)) * matrix3x3f::RotateX(DEG2RAD(Clamp(m_rotX, -90.0f, 90.0f))); m_viewPos = zoom_distance(m_baseDistance, m_zoom) * m_viewRot.VectorZ(); } else { - // XXX re-initialise the turntable style view position from the current mouselook view + // TODO: re-initialise the turntable style view position from the current mouselook view ResetCamera(); } } -void ModelViewer::ClearLog() -{ - m_log->SetText(""); -} - void ModelViewer::ClearModel() { - delete m_model; - m_model = 0; + m_model.reset(); + + m_animations.clear(); + m_currentAnimation = nullptr; + m_patterns.clear(); + m_currentPattern = 0; + m_currentDecal = 0; + m_gunModel.reset(); m_scaleModel.reset(); m_options.mouselookEnabled = false; - // FIXME: update modelviewer to use Input::SetCaptureMouse instead - SDL_SetWindowGrab(m_renderer->GetSDLWindow(), SDL_bool(m_options.mouselookEnabled)); - SDL_SetRelativeMouseMode(SDL_bool(m_options.mouselookEnabled)); + m_input->SetCapturingMouse(false); m_viewPos = vector3f(0.0f, 0.0f, 10.0f); - ResetCamera(); } void ModelViewer::CreateTestResources() @@ -544,134 +503,96 @@ void ModelViewer::DrawModel(const matrix4x4f &mv) m_navLights->Render(m_renderer); } -void ModelViewer::MainLoop() +void ModelViewer::Update(float deltaTime) { - double lastTime = SDL_GetTicks() * 0.001; - while (!m_done) { - const double ticks = SDL_GetTicks() * 0.001; - m_frameTime = (ticks - lastTime); - lastTime = ticks; + HandleInput(); - // logic update - PollEvents(); + UpdateLights(); + UpdateCamera(deltaTime); + UpdateShield(); - m_renderer->ClearScreen(); - UpdateLights(); - UpdateCamera(); - UpdateShield(); + // render the gradient backdrop + DrawBackground(); - // render the gradient backdrop - DrawBackground(); + //update animations, draw model etc. + if (m_model) { + m_navLights->Update(deltaTime); + m_shields->SetEnabled(m_options.showShields || m_shieldIsHit); - //update animations, draw model etc. - if (m_model) { - m_navLights->Update(m_frameTime); - m_shields->SetEnabled(m_options.showShields || m_shieldIsHit); + //Calculate the impact's radius dependant on time + const float dif1 = 0.34 - (-1.48f); + const float dif2 = m_shieldHitPan - (-1.48f); + //Range from start (0.0) to end (1.0) + const float dif = dif2 / (dif1 * 1.0f); - //Calculate the impact's radius dependant on time - const float dif1 = 0.34 - (-1.48f); - const float dif2 = m_shieldHitPan - (-1.48f); - //Range from start (0.0) to end (1.0) - const float dif = dif2 / (dif1 * 1.0f); + m_shields->Update(m_options.showShields ? 1.0f : (1.0f - dif), 1.0f); - m_shields->Update(m_options.showShields ? 1.0f : (1.0f - dif), 1.0f); - - // setup rendering - if (!m_options.orthoView) { - m_renderer->SetPerspectiveProjection(85, Graphics::GetScreenWidth() / float(Graphics::GetScreenHeight()), 0.1f, 100000.f); - } else { - /* TODO: Zoom in ortho mode seems don't work as in perspective mode, - / I change "screen dimensions" to avoid the problem. - / However the zoom needs more care - */ - if (m_zoom <= 0.0) m_zoom = 0.01; - float screenW = Graphics::GetScreenWidth() * m_zoom / 10; - float screenH = Graphics::GetScreenHeight() * m_zoom / 10; - matrix4x4f orthoMat = matrix4x4f::OrthoFrustum(-screenW, screenW, -screenH, screenH, 0.1f, 100000.0f); - orthoMat.ClearToRotOnly(); - m_renderer->SetProjection(orthoMat); - } - - m_renderer->SetTransform(matrix4x4f::Identity()); - - // calc camera info - matrix4x4f mv; - float zd = 0; - if (m_options.mouselookEnabled) { - mv = m_viewRot.Transpose() * matrix4x4f::Translation(-m_viewPos); - } else { - m_rotX = Clamp(m_rotX, -90.0f, 90.0f); - matrix4x4f rot = matrix4x4f::Identity(); - rot.RotateX(DEG2RAD(-m_rotX)); - rot.RotateY(DEG2RAD(-m_rotY)); - if (m_options.orthoView) - zd = -m_baseDistance; - else - zd = -zoom_distance(m_baseDistance, m_zoom); - mv = matrix4x4f::Translation(0.0f, 0.0f, zd) * rot; - } - - // draw the model itself - DrawModel(mv); - - // helper rendering - if (m_options.showLandingPad) { - if (!m_scaleModel) CreateTestResources(); - m_scaleModel->Render(mv * matrix4x4f::Translation(0.f, m_landingMinOffset, 0.f)); - } - - if (m_options.showGrid) { - DrawGrid(mv, m_model->GetDrawClipRadius()); - } + // setup rendering + if (!m_options.orthoView) { + m_renderer->SetPerspectiveProjection(85, Graphics::GetScreenWidth() / float(Graphics::GetScreenHeight()), 0.1f, 100000.f); + } else { + /* TODO: Zoom in ortho mode seems don't work as in perspective mode, + / I change "screen dimensions" to avoid the problem. + / However the zoom needs more care + */ + if (m_zoom <= 0.0) m_zoom = 0.01; + float screenW = Graphics::GetScreenWidth() * m_zoom / 10; + float screenH = Graphics::GetScreenHeight() * m_zoom / 10; + matrix4x4f orthoMat = matrix4x4f::OrthoFrustum(-screenW, screenW, -screenH, screenH, 0.1f, 100000.0f); + orthoMat.ClearToRotOnly(); + m_renderer->SetProjection(orthoMat); } - m_ui->Update(); - if (m_options.showUI && !m_screenshotQueued) { - m_ui->Draw(); - } - if (m_screenshotQueued) { - m_screenshotQueued = false; - Screenshot(); + m_renderer->SetTransform(matrix4x4f::Identity()); + + // calc camera info + matrix4x4f mv; + float zd = 0; + if (m_options.mouselookEnabled) { + mv = m_viewRot.Transpose() * matrix4x4f::Translation(-m_viewPos); + } else { + m_rotX = Clamp(m_rotX, -90.0f, 90.0f); + matrix4x4f rot = matrix4x4f::Identity(); + rot.RotateX(DEG2RAD(-m_rotX)); + rot.RotateY(DEG2RAD(-m_rotY)); + if (m_options.orthoView) + zd = -m_baseDistance; + else + zd = -zoom_distance(m_baseDistance, m_zoom); + mv = matrix4x4f::Translation(0.0f, 0.0f, zd) * rot; } - // end scene - m_renderer->SwapBuffers(); + // draw the model itself + DrawModel(mv); - // if we've requested a different model then switch too it - if (!m_requestedModelName.empty()) { - SetModel(m_requestedModelName); - m_requestedModelName.clear(); - ResetCamera(); + // helper rendering + if (m_options.showLandingPad) { + if (!m_scaleModel) CreateTestResources(); + m_scaleModel->Render(mv * matrix4x4f::Translation(0.f, m_landingMinOffset, 0.f)); } + + if (m_options.showGrid) { + DrawGrid(mv, m_model->GetDrawClipRadius()); + } + } + + if (m_options.showUI && !m_screenshotQueued) { + DrawPiGui(); + } + if (m_screenshotQueued) { + m_screenshotQueued = false; + Screenshot(); + } + + // if we've requested a different model then switch too it + if (!m_requestedModelName.empty()) { + SetModel(m_requestedModelName); + ResetCamera(); + m_requestedModelName.clear(); } } -void ModelViewer::OnAnimChanged(unsigned int, const std::string &name) -{ - m_currentAnimation = 0; - // Find the animation matching the name (could also store the anims in a map - // when the animationSelector is filled) - if (!name.empty()) { - const std::vector &anims = m_model->GetAnimations(); - for (std::vector::const_iterator anim = anims.begin(); anim != anims.end(); ++anim) { - if ((*anim)->GetName() == name) - m_currentAnimation = (*anim); - } - } - if (m_currentAnimation) - animSlider->SetValue(m_currentAnimation->GetProgress()); - else - animSlider->SetValue(0.0); -} - -void ModelViewer::OnAnimSliderChanged(float value) -{ - if (m_currentAnimation) - m_currentAnimation->SetProgress(value); - animValue->SetText(stringf("%0{f.2}", value)); -} - -void ModelViewer::OnDecalChanged(unsigned int index, const std::string &texname) +void ModelViewer::SetDecals(const std::string &texname) { if (!m_model) return; @@ -683,49 +604,49 @@ void ModelViewer::OnDecalChanged(unsigned int index, const std::string &texname) m_model->SetDecalTexture(m_decalTexture, 3); } -void ModelViewer::OnLightPresetChanged(unsigned int index, const std::string &) +void ModelViewer::SetupAxes() { - m_options.lightPreset = std::min(index, 3); + auto *page = m_input->GetBindingPage("ModelViewer"); + auto *group = page->GetBindingGroup("View"); + +#define AXIS(name, axis, positive, negative) m_input->AddAxisBinding(name, group, KeyBindings::AxisBinding(axis, positive, negative)) +#define ACTION(name, b1, b2) m_input->AddActionBinding(name, group, KeyBindings::ActionBinding(b1, b2)) + + m_zoomAxis = AXIS("BindZoomAxis", {}, SDLK_EQUALS, SDLK_MINUS); + + m_moveForward = AXIS("BindMoveForward", {}, SDLK_w, SDLK_s); + m_moveLeft = AXIS("BindMoveLeft", {}, SDLK_a, SDLK_d); + m_moveUp = AXIS("BindMoveUp", {}, SDLK_q, SDLK_e); + + // Like Blender, but a bit different because we like that + // 1 - front (+ctrl back) + // 7 - top (+ctrl bottom) + // 3 - left (+ctrl right) + // 2,4,6,8 incrementally rotate + + m_viewFront = ACTION("BindViewFront", SDLK_KP_1, SDLK_m); + m_viewFront->onPress.connect([=]() { + this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Back : CameraPreset::Front); + }); + + m_viewLeft = ACTION("BindViewLeft", SDLK_KP_3, SDLK_PERIOD); + m_viewLeft->onPress.connect([=]() { + this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Right : CameraPreset::Left); + }); + + m_viewTop = ACTION("BindViewTop", SDLK_KP_7, SDLK_u); + m_viewTop->onPress.connect([=]() { + this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Bottom : CameraPreset::Top); + }); + + m_rotateViewLeft = AXIS("BindRotateViewLeft", {}, SDLK_KP_6, SDLK_KP_4); + m_rotateViewUp = AXIS("BindRotateViewUp", {}, SDLK_KP_8, SDLK_KP_2); } -void ModelViewer::OnModelColorsChanged(float) +void ModelViewer::HandleInput() { - if (!m_model || m_settingColourSliders) return; + // FIXME: better handle dispatching input to Action/Axis bindings - //don't care about the float. Fetch values from all sliders. - std::vector colors; - colors.push_back(get_slider_color(colorSliders[0], colorSliders[1], colorSliders[2])); - colors.push_back(get_slider_color(colorSliders[3], colorSliders[4], colorSliders[5])); - colors.push_back(get_slider_color(colorSliders[6], colorSliders[7], colorSliders[8])); - m_model->SetColors(colors); -} - -void ModelViewer::OnPatternChanged(unsigned int index, const std::string &value) -{ - if (!m_model) return; - assert(index < m_model->GetPatterns().size()); - m_model->SetPattern(index); -} - -void ModelViewer::OnThrustChanged(float) -{ - vector3f linthrust; - vector3f angthrust; - - linthrust.x = get_thrust(thrustSliders[0]); - linthrust.y = get_thrust(thrustSliders[1]); - linthrust.z = get_thrust(thrustSliders[2]); - - // angthrusts are negated in ship.cpp for some reason - angthrust.x = -get_thrust(thrustSliders[3]); - angthrust.y = -get_thrust(thrustSliders[4]); - angthrust.z = -get_thrust(thrustSliders[5]); - - m_model->SetThrust(linthrust, angthrust); -} - -void ModelViewer::PollEvents() -{ /* * Special butans * @@ -739,118 +660,64 @@ void ModelViewer::PollEvents() * o - switch orthographic<->perspective * */ - m_mouseMotion[0] = m_mouseMotion[1] = 0; - m_mouseWheelUp = m_mouseWheelDown = false; - SDL_Event event; - while (SDL_PollEvent(&event)) { - //ui gets all events - if (m_options.showUI && m_ui->DispatchSDLEvent(event)) - continue; - - switch (event.type) { - case SDL_QUIT: - m_done = true; - break; - case SDL_MOUSEMOTION: - m_mouseMotion[0] += event.motion.xrel; - m_mouseMotion[1] += event.motion.yrel; - break; - case SDL_MOUSEBUTTONDOWN: - m_mouseButton[event.button.button] = true; - break; - case SDL_MOUSEBUTTONUP: - m_mouseButton[event.button.button] = false; - break; - case SDL_MOUSEWHEEL: - if (event.wheel.y > 0) m_mouseWheelUp = true; - if (event.wheel.y < 0) m_mouseWheelDown = true; - break; - case SDL_KEYDOWN: - switch (event.key.keysym.sym) { - case SDLK_ESCAPE: - if (m_model) { - ClearModel(); - onModelChanged.emit(); - PopulateFilePicker(); - } else { - m_done = true; - } - break; - case SDLK_SPACE: - ResetCamera(); - ResetThrusters(); - break; - case SDLK_TAB: - m_options.showUI = !m_options.showUI; - break; - case SDLK_t: - m_options.showTags = !m_options.showTags; - break; - case SDLK_PRINTSCREEN: - m_screenshotQueued = true; - break; - case SDLK_g: - OnToggleGrid(0); - break; - case SDLK_o: - m_options.orthoView = !m_options.orthoView; - break; - case SDLK_z: - m_options.wireframe = !m_options.wireframe; - break; - case SDLK_f: - ToggleViewControlMode(); - break; - case SDLK_F6: - SaveModelToBinary(); - break; - case SDLK_F11: - if (event.key.keysym.mod & KMOD_SHIFT) - m_renderer->ReloadShaders(); - break; - case SDLK_KP_1: - case SDLK_m: - case SDLK_KP_2: - case SDLK_COMMA: - case SDLK_KP_3: - case SDLK_PERIOD: - case SDLK_KP_4: - case SDLK_j: - case SDLK_KP_6: - case SDLK_l: - case SDLK_KP_7: - case SDLK_u: - case SDLK_KP_8: - case SDLK_i: - ChangeCameraPreset(event.key.keysym.sym, SDL_Keymod(event.key.keysym.mod)); - break; - case SDLK_p: //landing pad test - m_options.showLandingPad = !m_options.showLandingPad; - AddLog(stringf("Scale/landing pad test %0", m_options.showLandingPad ? "on" : "off")); - break; - case SDLK_r: //random colors, eastereggish - for (unsigned int i = 0; i < 3 * 3; i++) { - if (colorSliders[i]) - colorSliders[i]->SetValue(m_rng.Double()); - } - break; - default: - break; //shuts up -Wswitch - } //keysym switch - m_keyStates[event.key.keysym.sym] = true; - break; - case SDL_KEYUP: - m_keyStates[event.key.keysym.sym] = false; - break; - default: - break; + if (m_input->IsKeyPressed(SDLK_ESCAPE)) { + if (m_model) { + ClearModel(); + UpdateModelList(); + UpdateDecalList(); + } else { + RequestEndLifecycle(); } } + + if (m_input->IsKeyPressed(SDLK_SPACE)) { + ResetCamera(); + ResetThrusters(); + } + + if (m_input->IsKeyPressed(SDLK_TAB)) + m_options.showUI = !m_options.showUI; + + if (m_input->IsKeyPressed(SDLK_t)) + m_options.showTags = !m_options.showTags; + + if (m_input->IsKeyPressed(SDLK_PRINTSCREEN)) + m_screenshotQueued = true; + + if (m_input->IsKeyPressed(SDLK_g)) + ToggleGrid(); + + if (m_input->IsKeyPressed(SDLK_o)) + m_options.orthoView = !m_options.orthoView; + + if (m_input->IsKeyPressed(SDLK_z)) + m_options.wireframe = !m_options.wireframe; + + if (m_input->IsKeyPressed(SDLK_f)) + ToggleViewControlMode(); + + if (m_input->IsKeyPressed(SDLK_F6)) + SaveModelToBinary(); + + if (m_input->IsKeyPressed(SDLK_F11) && m_input->KeyModState() & KMOD_SHIFT) + m_renderer->ReloadShaders(); + + //landing pad test + if (m_input->IsKeyPressed(SDLK_p)) { + m_options.showLandingPad = !m_options.showLandingPad; + AddLog(stringf("Scale/landing pad test %0", m_options.showLandingPad ? "on" : "off")); + } + + // random colors, eastereggish + if (m_input->IsKeyPressed(SDLK_r)) + SetRandomColor(); } -static void collect_models(std::vector &list) +void ModelViewer::UpdateModelList() { + m_fileNames.clear(); + const std::string basepath("models"); FileSystem::FileSource &fileSource = FileSystem::gameDataFiles; for (FileSystem::FileEnumerator files(fileSource, basepath, FileSystem::FileEnumerator::Recurse); !files.Finished(); files.Next()) { @@ -860,22 +727,29 @@ static void collect_models(std::vector &list) //check it's the expected type if (info.IsFile()) { if (ends_with_ci(fpath, ".model")) - list.push_back(info.GetName().substr(0, info.GetName().size() - 6)); + m_fileNames.push_back(info.GetName().substr(0, info.GetName().size() - 6)); else if (ends_with_ci(fpath, ".sgm")) - list.push_back(info.GetName()); + m_fileNames.push_back(info.GetName()); } } } -void ModelViewer::PopulateFilePicker() +void ModelViewer::UpdateDecalList() { - m_fileList->Clear(); + m_decals.clear(); + m_currentDecal = 0; - std::vector models; - collect_models(models); + const std::string basepath("textures/decals"); + FileSystem::FileSource &fileSource = FileSystem::gameDataFiles; + for (FileSystem::FileEnumerator files(fileSource, basepath); !files.Finished(); files.Next()) { + const FileSystem::FileInfo &info = files.Current(); + const std::string &fpath = info.GetPath(); - for (const auto &it : models) - m_fileList->AddOption(it); + //check it's the expected type + if (info.IsFile() && ends_with_ci(fpath, ".dds")) { + m_decals.push_back(info.GetName().substr(0, info.GetName().size() - 4)); + } + } } void ModelViewer::ResetCamera() @@ -887,11 +761,8 @@ void ModelViewer::ResetCamera() void ModelViewer::ResetThrusters() { - if (thrustSliders[0] == 0) return; - - for (unsigned int i = 0; i < 6; i++) { - thrustSliders[i]->SetValue(0.5f); - } + m_angularThrust = vector3f{}; + m_linearThrust = vector3f{}; } void ModelViewer::Screenshot() @@ -935,6 +806,11 @@ void ModelViewer::SaveModelToBinary() } } +void ModelViewer::SetAnimation(SceneGraph::Animation *anim) +{ + m_currentAnimation = anim; +} + void ModelViewer::SetModel(const std::string &filename) { AddLog(stringf("Loading model %0...", filename)); @@ -949,11 +825,11 @@ void ModelViewer::SetModel(const std::string &filename) //binary loader expects extension-less name. Might want to change this. m_modelName = filename.substr(0, filename.size() - 4); SceneGraph::BinaryConverter bc(m_renderer); - m_model = bc.Load(m_modelName); + m_model.reset(bc.Load(m_modelName)); } else { m_modelName = filename; SceneGraph::Loader loader(m_renderer, true); - m_model = loader.LoadModel(filename); + m_model.reset(loader.LoadModel(filename)); //dump warnings for (std::vector::const_iterator it = loader.GetLogMessages().begin(); @@ -963,14 +839,14 @@ void ModelViewer::SetModel(const std::string &filename) } } - Shields::ReparentShieldNodes(m_model); + Shields::ReparentShieldNodes(m_model.get()); //set decal textures, max 4 supported. //Identical texture at the moment - OnDecalChanged(0, "pioneer"); + SetDecals("pioneer"); Output("\n\n"); - SceneGraph::DumpVisitor d(m_model); + SceneGraph::DumpVisitor d(m_model.get()); m_model->GetRoot()->Accept(d); AddLog(d.GetModelStatistics()); @@ -984,275 +860,300 @@ void ModelViewer::SetModel(const std::string &filename) m_landingMinOffset = 0.0f; //note: stations won't demonstrate full docking light logic in MV - m_navLights.reset(new NavLights(m_model)); + m_navLights.reset(new NavLights(m_model.get())); m_navLights->SetEnabled(true); - m_shields.reset(new Shields(m_model)); + m_shields.reset(new Shields(m_model.get())); } catch (SceneGraph::LoadingError &err) { // report the error and show model picker. - m_model = 0; + m_model.reset(); AddLog(stringf("Could not load model %0: %1", filename, err.what())); } - onModelChanged.emit(); + if (m_model) + onModelChanged.emit(); } -void ModelViewer::SetupFilePicker() +void ModelViewer::OnModelChanged() { - UI::Context *c = m_ui.Get(); + ResetThrusters(); + m_model->SetColors(m_colors); - m_fileList = c->List(); - UI::Button *quitButton = c->Button(); - UI::Button *loadButton = c->Button(); - quitButton->SetInnerWidget(c->Label("Quit")); - loadButton->SetInnerWidget(c->Label("Load")); + SceneGraph::FindNodeVisitor visitor(SceneGraph::FindNodeVisitor::MATCH_NAME_STARTSWITH, "thruster_"); + m_model->GetRoot()->Accept(visitor); + m_modelIsShip = !visitor.GetResults().empty(); + m_modelSupportsDecals = m_model->SupportsDecals(); - PopulateFilePicker(); + m_modelHasShields = m_shields.get() && m_shields->GetFirstShieldMesh(); - UI::Widget *fp = - c->Grid(UI::CellSpec(1, 3, 1), UI::CellSpec(1, 3, 1)) - ->SetCell(1, 1, - c->VBox(10) - ->PackEnd(c->Label("Select a model")) - ->PackEnd(c->Expand(UI::Expand::BOTH)->SetInnerWidget(c->Scroller()->SetInnerWidget(m_fileList))) - ->PackEnd(c->Grid(2, 1)->SetRow(0, UI::WidgetSet(c->Align(UI::Align::LEFT)->SetInnerWidget(loadButton), c->Align(UI::Align::RIGHT)->SetInnerWidget(quitButton))))); + m_animations = m_model->GetAnimations(); + m_currentAnimation = nullptr; - m_logScroller->Layout(); //issues without this - c->GetTopLayer()->SetInnerWidget(c->Grid(2, 1) - ->SetRow(0, UI::WidgetSet(fp, m_logScroller.Get()))); - - c->Layout(); - m_logScroller->SetScrollPosition(1.f); - - loadButton->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnPickModel), m_fileList)); - quitButton->onClick.connect(sigc::mem_fun(*this, &ModelViewer::OnQuit)); + m_patterns.clear(); + m_currentPattern = 0; + m_modelSupportsPatterns = m_model->SupportsPatterns(); + if (m_modelSupportsPatterns) { + for (const auto &pattern : m_model->GetPatterns()) { + m_patterns.push_back(pattern.name); + } + } } -void ModelViewer::SetupUI() +void ModelViewer::DrawModelSelector() { - UI::Context *c = m_ui.Get(); - c->SetFont(UI::Widget::FONT_XSMALL); + vector2f selectorSize = m_windowSize * vector2f(0.4, 0.8); + ImGui::SetNextWindowSize({ selectorSize.x, selectorSize.y }, ImGuiCond_Always); + vector2f selectorPos = m_windowSize * 0.5 - selectorSize * 0.5; + ImGui::SetNextWindowPos({ selectorPos.x, selectorPos.y }, ImGuiCond_Always); - for (unsigned int i = 0; i < 9; i++) - colorSliders[i] = 0; - for (unsigned int i = 0; i < 6; i++) - thrustSliders[i] = 0; - - animSlider = 0; - animValue = 0; - - if (!m_model) - return SetupFilePicker(); - - const int spacing = 5; - - UI::SmallButton *reloadButton = nullptr; - UI::SmallButton *toggleGridButton = nullptr; - UI::SmallButton *hitItButton = nullptr; - UI::SmallButton *randomColours = nullptr; - UI::CheckBox *collMeshCheck = nullptr; - UI::CheckBox *showShieldsCheck = nullptr; - UI::CheckBox *gunsCheck = nullptr; - - UI::VBox *outerBox = c->VBox(); - - UI::VBox *mainBox = c->VBox(5); - UI::VBox *bottomBox = c->VBox(5); - - UI::HBox *sliderBox = c->HBox(); - bottomBox->PackEnd(sliderBox); - - outerBox->PackEnd(UI::WidgetSet( - c->Expand()->SetInnerWidget(c->Grid(UI::CellSpec(0.30f, 0.8f, 0.35f), 1) - ->SetColumn(0, mainBox) - ->SetColumn(2, m_logScroller.Get())), - bottomBox)); - - c->GetTopLayer()->SetInnerWidget(c->Margin(spacing)->SetInnerWidget(outerBox)); - - //model name + reload button: visible even if loading failed - mainBox->PackEnd(nameLabel = c->Label(m_modelName)); - nameLabel->SetFont(UI::Widget::FONT_NORMAL); - add_pair(c, mainBox, reloadButton = c->SmallButton(), "Reload model"); - reloadButton->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnReloadModel), reloadButton)); - - if (m_model == 0) { - c->Layout(); - return; - } - - add_pair(c, mainBox, toggleGridButton = c->SmallButton(), "Grid mode"); - add_pair(c, mainBox, collMeshCheck = c->CheckBox(), "Collision mesh"); - // not everything has a shield - if (m_shields.get() && m_shields->GetFirstShieldMesh()) { - add_pair(c, mainBox, showShieldsCheck = c->CheckBox(), "Show Shields"); - add_pair(c, mainBox, hitItButton = c->SmallButton(), "Hit it!"); - hitItButton->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnHitIt), hitItButton)); - } - - add_pair(c, mainBox, randomColours = c->SmallButton(), "Random Colours"); - randomColours->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnRandomColor), randomColours)); - - //pattern selector - if (m_model->SupportsPatterns()) { - mainBox->PackEnd(c->Label("Pattern:")); - mainBox->PackEnd(patternSelector = c->DropDown()->AddOption("Default")); - - sliderBox->PackEnd( - c->Grid(3, 4) - ->SetColumn(0, UI::WidgetSet(c->Label("Color 1"), c->HBox(spacing)->PackEnd(c->Label("R"))->PackEnd(colorSliders[0] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("G"))->PackEnd(colorSliders[1] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("B"))->PackEnd(colorSliders[2] = c->HSlider()))) - ->SetColumn(1, UI::WidgetSet(c->Label("Color 2"), c->HBox(spacing)->PackEnd(c->Label("R"))->PackEnd(colorSliders[3] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("G"))->PackEnd(colorSliders[4] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("B"))->PackEnd(colorSliders[5] = c->HSlider()))) - ->SetColumn(2, UI::WidgetSet(c->Label("Color 3"), c->HBox(spacing)->PackEnd(c->Label("R"))->PackEnd(colorSliders[6] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("G"))->PackEnd(colorSliders[7] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("B"))->PackEnd(colorSliders[8] = c->HSlider())))); - - //connect slider signals, set initial values (RGB) - const float values[] = { - 1.f, 0.f, 0.f, - 0.f, 1.f, 0.f, - 0.f, 0.f, 1.f - }; - for (unsigned int i = 0; i < 3 * 3; i++) { - colorSliders[i]->SetValue(values[i]); - colorSliders[i]->onValueChanged.connect(sigc::mem_fun(*this, &ModelViewer::OnModelColorsChanged)); + auto flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + bool b_open = true; // Use the window close button to quit the modelviewer + if (ImGui::Begin("Select Model", &b_open, flags)) { + if (ImGui::BeginChild("FileList", ImVec2(0.0, -ImGui::GetFrameHeightWithSpacing()))) { + for (const auto &name : m_fileNames) { + if (ImGui::Selectable(name.c_str())) { + m_requestedModelName = name; + } + } } - //// slidems end - - patternSelector->onOptionSelected.connect(sigc::mem_fun(*this, &ModelViewer::OnPatternChanged)); - - UpdatePatternList(); + ImGui::EndChild(); } + ImGui::End(); - //decal selector - //models support up to 4 but 1 is enough here - if (m_model->SupportsDecals()) { - mainBox->PackEnd(c->Label("Decal:")); - mainBox->PackEnd(decalSelector = c->DropDown()); - - decalSelector->onOptionSelected.connect(sigc::mem_fun(*this, &ModelViewer::OnDecalChanged)); - - std::vector decals; - collect_decals(decals); - - for (std::vector::const_iterator it = decals.begin(); it != decals.end(); ++it) { - decalSelector->AddOption(*it); - } - if (decals.size() > 0) - decalSelector->SetSelectedOption("pioneer"); + if (!b_open || ImGui::IsKeyPressed(ImGuiKey_Escape)) { + RequestEndLifecycle(); } - - //light dropdown - UI::DropDown *lightSelector; - mainBox->PackEnd(c->Label("Lights:")); - mainBox->PackEnd( - lightSelector = c->DropDown() - ->AddOption("1 Front white") - ->AddOption("2 Two-point") - ->AddOption("3 Backlight") - //->AddOption("4 Nuts") - ); - lightSelector->SetSelectedOption("1 Front white"); - m_options.lightPreset = 0; - - add_pair(c, mainBox, gunsCheck = c->CheckBox(), "Attach guns"); - - //Animation controls - if (!m_model->GetAnimations().empty()) { - //UI::Button *playBtn; - //UI::Button *revBtn; - //UI::Button *stopBtn; - UI::Box *animBox; - mainBox->PackEnd(animBox = c->VBox(spacing)); - animBox->PackEnd(m_ui->Label("Animation:")); - animBox->PackEnd(animSelector = m_ui->DropDown()->AddOption("None")); - //add_pair(m_ui, animBox, playBtn = m_ui->Button(), "Play/Pause"); - //add_pair(m_ui, animBox, revBtn = m_ui->Button(), "Play reverse"); - //add_pair(m_ui, animBox, stopBtn = m_ui->Button(), "Stop"); - - bottomBox->PackStart(c->HBox(10)->PackEnd(UI::WidgetSet(c->Label("Animation:"), animSlider = c->HSlider(), animValue = c->Label("0.0")))); - animValue->SetFont(UI::Widget::FONT_NORMAL); - - //playBtn->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnAnimPlay), playBtn, false)); - //revBtn->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnAnimPlay), revBtn, true)); - //stopBtn->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnAnimStop), stopBtn)); - animSlider->onValueChanged.connect(sigc::mem_fun(*this, &ModelViewer::OnAnimSliderChanged)); - animSelector->onOptionSelected.connect(sigc::mem_fun(*this, &ModelViewer::OnAnimChanged)); - - //update anims from model - UpdateAnimList(); - } - - //// Thrust sliders - bool supportsThrusters = false; - { - SceneGraph::FindNodeVisitor fivi(SceneGraph::FindNodeVisitor::MATCH_NAME_STARTSWITH, "thruster_"); - m_model->GetRoot()->Accept(fivi); - supportsThrusters = !fivi.GetResults().empty(); - } - if (supportsThrusters) { - sliderBox->PackStart( - c->Grid(2, 4) - ->SetColumn(0, UI::WidgetSet( - // Column 1, Linear thrust sliders - c->Label("Linear"), c->HBox(spacing)->PackEnd(c->Label("X"))->PackEnd(thrustSliders[0] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("Y"))->PackEnd(thrustSliders[1] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("Z"))->PackEnd(thrustSliders[2] = c->HSlider()))) - ->SetColumn(1, UI::WidgetSet( - //Column 2, Angular thrust sliders - c->Label("Angular"), c->HBox(spacing)->PackEnd(c->Label("Pitch"))->PackEnd(thrustSliders[3] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("Yaw"))->PackEnd(thrustSliders[4] = c->HSlider()), c->HBox(spacing)->PackEnd(c->Label("Roll"))->PackEnd(thrustSliders[5] = c->HSlider())))); - for (unsigned int i = 0; i < 2 * 3; i++) { - thrustSliders[i]->SetValue(0.5f); - thrustSliders[i]->onValueChanged.connect(sigc::mem_fun(*this, &ModelViewer::OnThrustChanged)); - } - ////thruster sliders end - } - - c->Layout(); - - //event handlers - collMeshCheck->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnToggleCollMesh), collMeshCheck)); - if (m_shields.get() && showShieldsCheck) { - showShieldsCheck->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnToggleShowShields), showShieldsCheck)); - } - gunsCheck->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnToggleGuns), gunsCheck)); - lightSelector->onOptionSelected.connect(sigc::mem_fun(*this, &ModelViewer::OnLightPresetChanged)); - toggleGridButton->onClick.connect(sigc::bind(sigc::mem_fun(*this, &ModelViewer::OnToggleGrid), toggleGridButton)); } -void ModelViewer::UpdateAnimList() +void ModelViewer::DrawShipControls() { - animSelector->Clear(); + if (m_modelIsShip) { + ImGui::Columns(3, nullptr, false); + ImGui::TextUnformatted("Linear Thrust"); + ImGui::Spacing(); + + bool valuesChanged = false; + valuesChanged |= ImGui::SliderFloat("X", &m_linearThrust.x, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Y", &m_linearThrust.y, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Z", &m_linearThrust.z, -1.0, 1.0); + + ImGui::NextColumn(); + ImGui::TextUnformatted("Angular Thrust"); + ImGui::Spacing(); + + valuesChanged |= ImGui::SliderFloat("Pitch", &m_angularThrust.x, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Yaw", &m_angularThrust.y, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Roll", &m_angularThrust.z, -1.0, 1.0); + + if (valuesChanged) + m_model->SetThrust(m_linearThrust, m_angularThrust); + + ImGui::NextColumn(); + ImGui::TextUnformatted("Pattern Colors"); + ImGui::Spacing(); + + valuesChanged = false; + valuesChanged |= ImGui::ColorEdit3("Color 1", m_colors[0]); + valuesChanged |= ImGui::ColorEdit3("Color 2", m_colors[1]); + valuesChanged |= ImGui::ColorEdit3("Color 3", m_colors[2]); + + if (valuesChanged) + m_model->SetColors(m_colors); + + ImGui::Columns(1); + } + + if (m_currentAnimation) { + ImGui::Spacing(); + float progress = m_currentAnimation->GetProgress(); + bool changed = ImGui::SliderFloat("Animation Progress", &progress, 0.0, m_currentAnimation->GetDuration()); + if (changed) { + m_currentAnimation->SetProgress(progress); + } + } +} + +void ModelViewer::DrawModelOptions() +{ + ImGui::TextUnformatted(m_modelName.c_str()); + + if (ImGui::Button("Reload Model")) + ReloadModel(); + + ImGui::NewLine(); + + if (ImGui::Button("Show Collision Mesh")) + ToggleCollMesh(); + + if (ImGui::Button("Toggle Grid Mode")) + ToggleGrid(); + + if (ImGui::Button("Set Random Colors")) + SetRandomColor(); + + if (m_modelHasShields) { + ImGui::NewLine(); + + if (ImGui::Button("Show Shields")) + ToggleShowShields(); + + if (ImGui::Button("Test Shield Hit")) + HitIt(); + } + + if (m_modelIsShip) { + if (ImGui::Button("Attach Test Guns")) + ToggleGuns(); + } + + ImGui::NewLine(); + + if (m_modelSupportsPatterns) { + const char *preview_name = m_patterns[m_currentPattern].c_str(); + if (ImGui::BeginCombo("Pattern", preview_name)) { + for (size_t idx = 0; idx < m_patterns.size(); idx++) { + const bool selected = m_currentPattern == idx; + if (ImGui::Selectable(m_patterns[idx].c_str(), selected) && !selected) { + m_currentPattern = idx; + m_model->SetPattern(idx); + } + } + + ImGui::EndCombo(); + } + } + + if (m_modelSupportsDecals) { + const char *preview_name = m_decals[m_currentDecal].c_str(); + if (ImGui::BeginCombo("Decals", preview_name)) { + for (size_t idx = 0; idx < m_decals.size(); idx++) { + const bool selected = m_currentDecal == idx; + if (ImGui::Selectable(m_decals[idx].c_str(), selected) && !selected) { + m_currentDecal = idx; + SetDecals(m_decals[idx]); + } + } + + ImGui::EndCombo(); + } + } + + const char *anim_name = m_currentAnimation ? m_currentAnimation->GetName().c_str() : "None"; + if (ImGui::BeginCombo("Animation", anim_name)) { + for (const auto anim : m_animations) { + const bool selected = m_currentAnimation == anim; + if (ImGui::Selectable(anim->GetName().c_str(), selected) && !selected) { + // selected a new animation entry + SetAnimation(anim); + } + } + + if (ImGui::Selectable("None", !m_currentAnimation) && m_currentAnimation) { + // Changed to no animation + SetAnimation(nullptr); + } + + ImGui::EndCombo(); + } + + static std::vector lightSetups = { + "Front Light", "Two-point", "Backlight" + }; + + uint32_t ¤tLights = m_options.lightPreset; + if (ImGui::BeginCombo("Lights", lightSetups[currentLights].c_str())) { + for (size_t idx = 0; idx < lightSetups.size(); idx++) { + const bool selected = currentLights == idx; + if (ImGui::Selectable(lightSetups[idx].c_str(), selected) && !selected) { + currentLights = idx; + } + } + + ImGui::EndCombo(); + } +} + +void ModelViewer::DrawLog() +{ + ImGui::SetNextWindowPos(ImVec2(m_windowSize.x - m_logWindowSize.x, m_windowSize.y - m_logWindowSize.y)); + ImGui::SetNextWindowSize(ImVec2(m_logWindowSize.x, m_logWindowSize.y)); + const auto flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings; + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0); + if (ImGui::Begin("LogWindow", nullptr, flags)) { + if (ImGui::BeginChild("ScrollArea")) { + for (const auto &message : m_log) { + ImGui::TextUnformatted(message.c_str()); + } + + if (m_resetLogScroll || ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + m_resetLogScroll = false; + } + } + ImGui::EndChild(); + } + ImGui::End(); + ImGui::PopStyleVar(1); +} + +void ModelViewer::DrawPiGui() +{ + m_windowSize = vector2f(Graphics::GetScreenWidth(), Graphics::GetScreenHeight()); + if (m_model) { - const std::vector &anims = m_model->GetAnimations(); - for (unsigned int i = 0; i < anims.size(); i++) { - animSelector->AddOption(anims[i]->GetName()); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0); + { + const auto flags = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + ImGui::SetNextWindowPos({ 0, 0 }); + ImGui::SetNextWindowSize({ m_windowSize.x / 4.0f, m_windowSize.y - m_animWindowSize.y }); + if (ImGui::Begin("Model Options", nullptr, flags)) { + DrawModelOptions(); + } + ImGui::End(); } - if (anims.size()) - animSelector->SetSelectedOption(anims[0]->GetName()); + + if (m_modelIsShip) { + const auto flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + ImGui::SetNextWindowPos({ 0, m_windowSize.y - m_animWindowSize.y }); + ImGui::SetNextWindowSize({ m_windowSize.x - m_logWindowSize.x, m_animWindowSize.y }); + if (ImGui::Begin("Model Controls", nullptr, flags)) { + DrawShipControls(); + } + ImGui::End(); + } + ImGui::PopStyleVar(1); + + } else { + DrawModelSelector(); } - animSelector->Layout(); - OnAnimChanged(0, animSelector->GetSelectedOption()); + + DrawLog(); } -void ModelViewer::UpdateCamera() +void ModelViewer::UpdateCamera(float deltaTime) { static const float BASE_ZOOM_RATE = 1.0f / 12.0f; - float zoomRate = (BASE_ZOOM_RATE * 8.0f) * m_frameTime; - float rotateRate = 25.f * m_frameTime; - float moveRate = 10.0f * m_frameTime; + float zoomRate = (BASE_ZOOM_RATE * 8.0f) * deltaTime; + float rotateRate = 25.f * deltaTime; + float moveRate = 10.0f * deltaTime; - if (m_keyStates[SDLK_LSHIFT]) { + bool isShiftPressed = m_input->KeyState(SDLK_LSHIFT); + + if (isShiftPressed) { zoomRate *= 8.0f; moveRate *= 4.0f; rotateRate *= 4.0f; - } else if (m_keyStates[SDLK_RSHIFT]) { - zoomRate *= 3.0f; - moveRate *= 2.0f; - rotateRate *= 2.0f; } + std::array mouseMotion; + m_input->GetMouseMotion(mouseMotion.data()); + bool rightMouseDown = m_input->MouseButtonState(SDL_BUTTON_RIGHT); if (m_options.mouselookEnabled) { const float degrees_per_pixel = 0.2f; - if (!m_mouseButton[SDL_BUTTON_RIGHT]) { + if (!rightMouseDown) { // yaw and pitch - const float rot_y = degrees_per_pixel * m_mouseMotion[0]; - const float rot_x = degrees_per_pixel * m_mouseMotion[1]; + const float rot_y = degrees_per_pixel * mouseMotion[0]; + const float rot_x = degrees_per_pixel * mouseMotion[1]; const matrix3x3f rot = matrix3x3f::RotateX(DEG2RAD(rot_x)) * matrix3x3f::RotateY(DEG2RAD(rot_y)); @@ -1260,39 +1161,39 @@ void ModelViewer::UpdateCamera() m_viewRot = m_viewRot * rot; } else { // roll - m_viewRot = m_viewRot * matrix3x3f::RotateZ(DEG2RAD(degrees_per_pixel * m_mouseMotion[0])); + m_viewRot = m_viewRot * matrix3x3f::RotateZ(DEG2RAD(degrees_per_pixel * mouseMotion[0])); } - vector3f motion(0.0f); - if (m_keyStates[SDLK_w]) motion.z -= moveRate; - if (m_keyStates[SDLK_s]) motion.z += moveRate; - if (m_keyStates[SDLK_a]) motion.x -= moveRate; - if (m_keyStates[SDLK_d]) motion.x += moveRate; - if (m_keyStates[SDLK_q]) motion.y -= moveRate; - if (m_keyStates[SDLK_e]) motion.y += moveRate; + vector3f motion( + m_moveLeft->GetValue(), + m_moveUp->GetValue(), + m_moveForward->GetValue()); m_viewPos += m_viewRot * motion; } else { //zoom - if (m_keyStates[SDLK_EQUALS] || m_keyStates[SDLK_KP_PLUS]) m_zoom -= zoomRate; - if (m_keyStates[SDLK_MINUS] || m_keyStates[SDLK_KP_MINUS]) m_zoom += zoomRate; + m_zoom += m_zoomAxis->GetValue() * BASE_ZOOM_RATE; //zoom with mouse wheel - if (m_mouseWheelUp) m_zoom -= BASE_ZOOM_RATE; - if (m_mouseWheelDown) m_zoom += BASE_ZOOM_RATE; + int mouseWheel = m_input->GetMouseWheel(); + if (mouseWheel) m_zoom += mouseWheel > 0 ? -BASE_ZOOM_RATE : BASE_ZOOM_RATE; m_zoom = Clamp(m_zoom, -10.0f, 10.0f); // distance range: [baseDistance * 1/1024, baseDistance * 1024] //rotate - if (m_keyStates[SDLK_UP]) m_rotX += rotateRate; - if (m_keyStates[SDLK_DOWN]) m_rotX -= rotateRate; - if (m_keyStates[SDLK_LEFT]) m_rotY += rotateRate; - if (m_keyStates[SDLK_RIGHT]) m_rotY -= rotateRate; + + if (m_input->IsKeyDown(SDLK_UP)) m_rotX += rotateRate; + if (m_input->IsKeyDown(SDLK_DOWN)) m_rotX -= rotateRate; + if (m_input->IsKeyDown(SDLK_LEFT)) m_rotY += rotateRate; + if (m_input->IsKeyDown(SDLK_RIGHT)) m_rotY -= rotateRate; + + m_rotX += rotateRate * m_rotateViewLeft->GetValue(); + m_rotY += rotateRate * -m_rotateViewUp->GetValue(); //mouse rotate when right button held - if (m_mouseButton[SDL_BUTTON_RIGHT]) { - m_rotY += 0.2f * m_mouseMotion[0]; - m_rotX += 0.2f * m_mouseMotion[1]; + if (rightMouseDown) { + m_rotY += 0.2f * mouseMotion[0]; + m_rotX += 0.2f * mouseMotion[1]; } } } @@ -1329,19 +1230,3 @@ void ModelViewer::UpdateLights() m_renderer->SetLights(int(lights.size()), &lights[0]); } - -void ModelViewer::UpdatePatternList() -{ - patternSelector->Clear(); - - if (m_model) { - const SceneGraph::PatternContainer &pats = m_model->GetPatterns(); - for (unsigned int i = 0; i < pats.size(); i++) { - patternSelector->AddOption(pats[i].name); - } - if (pats.size() > 0) - patternSelector->SetSelectedOption(pats[0].name); - } - - m_ui->Layout(); -} diff --git a/src/ModelViewer.h b/src/ModelViewer.h index f05132af7..7cc707d1a 100644 --- a/src/ModelViewer.h +++ b/src/ModelViewer.h @@ -3,66 +3,114 @@ #ifndef MODELVIEWER_H #define MODELVIEWER_H + +#include "Input.h" #include "NavLights.h" #include "Shields.h" +#include "core/GuiApplication.h" #include "graphics/Drawables.h" #include "graphics/Renderer.h" #include "graphics/Texture.h" #include "libs.h" #include "lua/LuaManager.h" +#include "pigui/PiGui.h" #include "scenegraph/SceneGraph.h" #include "ui/Context.h" -class ModelViewer { -public: - ModelViewer(Graphics::Renderer *r, LuaManager *l); - ~ModelViewer(); +#include - static void Run(const std::string &modelName); +class Input; +class ModelViewer; + +class ModelViewerApp : public GuiApplication { +public: + ModelViewerApp() : + GuiApplication("Model Viewer") + {} + + void SetInitialModel(std::string &modelName) { m_modelName = modelName; } + std::string &GetModelName() { return m_modelName; } + +protected: + void Startup() override; + void Shutdown() override; + + void PreUpdate() override; + void PostUpdate() override; + + friend class ModelViewer; + +private: + std::string m_modelName; + std::unique_ptr m_input; + std::shared_ptr m_modelViewer; +}; + +class ModelViewer : public Application::Lifecycle { +public: + enum class CameraPreset : uint8_t { + Front, + Back, + Left, + Right, + Top, + Bottom + }; + + ModelViewer(ModelViewerApp *app, LuaManager *l); + + void SetModel(const std::string &modelName); + bool SetRandomColor(); + void ResetCamera(); + void ChangeCameraPreset(CameraPreset preset); + +protected: + void Start() override; + void Update(float deltaTime) override; + void End() override; + void SetupAxes(); + void HandleInput(); private: - bool OnPickModel(UI::List *); - bool OnQuit(); - bool OnReloadModel(UI::Widget *); - bool OnToggleCollMesh(UI::CheckBox *); - bool OnToggleShowShields(UI::CheckBox *); - bool OnToggleGrid(UI::Widget *); - bool OnToggleGuns(UI::CheckBox *); - bool OnRandomColor(UI::Widget *); - void UpdateShield(); - bool OnHitIt(UI::Widget *); - void HitImpl(); void AddLog(const std::string &line); - void ChangeCameraPreset(SDL_Keycode, SDL_Keymod); + + void UpdateModelList(); + void UpdateDecalList(); + void UpdateShield(); + + void UpdateCamera(float deltaTime); + void UpdateLights(); + + void ReloadModel(); + void SetAnimation(SceneGraph::Animation *anim); + void SetDecals(const std::string &file); + + void OnModelChanged(); + + void ToggleCollMesh(); + void ToggleShowShields(); + void ToggleGrid(); + void ToggleGuns(); + void HitIt(); + void ToggleViewControlMode(); - void ClearLog(); void ClearModel(); void CreateTestResources(); void DrawBackground(); void DrawGrid(const matrix4x4f &trans, float radius); void DrawModel(const matrix4x4f &mv); - void MainLoop(); - void OnAnimChanged(unsigned int, const std::string &); - void OnAnimSliderChanged(float); - void OnDecalChanged(unsigned int, const std::string &); - void OnLightPresetChanged(unsigned int index, const std::string &); - void OnModelColorsChanged(float); - void OnPatternChanged(unsigned int, const std::string &); - void OnThrustChanged(float); - void PollEvents(); - void PopulateFilePicker(); - void ResetCamera(); + void ResetThrusters(); void Screenshot(); void SaveModelToBinary(); - void SetModel(const std::string &name); - void SetupFilePicker(); - void SetupUI(); - void UpdateAnimList(); - void UpdateCamera(); - void UpdateLights(); - void UpdatePatternList(); + void DrawModelSelector(); + void DrawModelOptions(); + void DrawShipControls(); + void DrawLog(); + void DrawPiGui(); + +private: //toggleable options struct Options { bool attachGuns; @@ -77,17 +125,69 @@ private: bool wireframe; bool mouselookEnabled; float gridInterval; - int lightPreset; + uint32_t lightPreset; bool orthoView; Options(); }; - bool m_done; + +private: + ModelViewerApp *m_app; + Input *m_input; + PiGui::Instance *m_pigui; + + KeyBindings::AxisBinding *m_moveForward; + KeyBindings::AxisBinding *m_moveLeft; + KeyBindings::AxisBinding *m_moveUp; + KeyBindings::AxisBinding *m_zoomAxis; + + KeyBindings::AxisBinding *m_rotateViewLeft; + KeyBindings::AxisBinding *m_rotateViewUp; + + KeyBindings::ActionBinding *m_viewTop; + KeyBindings::ActionBinding *m_viewLeft; + KeyBindings::ActionBinding *m_viewFront; + + vector2f m_windowSize; + vector2f m_logWindowSize; + vector2f m_animWindowSize; + std::vector m_log; + bool m_resetLogScroll = false; + + vector3f m_linearThrust = {}; + vector3f m_angularThrust = {}; + + // Model pattern colors + std::vector m_colors; + + std::vector m_fileNames; + std::string m_modelName; + std::string m_requestedModelName; + + std::unique_ptr m_model; + bool m_modelIsShip = false; + + std::vector m_animations; + SceneGraph::Animation *m_currentAnimation = nullptr; + + bool m_modelSupportsPatterns = false; + std::vector m_patterns; + uint32_t m_currentPattern = 0; + + bool m_modelSupportsDecals = false; + std::vector m_decals; + uint32_t m_currentDecal = 0; + + bool m_modelHasShields = false; + std::unique_ptr m_shields; + std::unique_ptr m_navLights; + std::unique_ptr m_gunModel; + std::unique_ptr m_scaleModel; + bool m_screenshotQueued; bool m_shieldIsHit; bool m_settingColourSliders; float m_shieldHitPan; - double m_frameTime; Graphics::Renderer *m_renderer; Graphics::Texture *m_decalTexture; vector3f m_viewPos; @@ -95,41 +195,13 @@ private: float m_rotX, m_rotY, m_zoom; float m_baseDistance; Random m_rng; - SceneGraph::Animation *m_currentAnimation; - SceneGraph::Model *m_model; + Options m_options; float m_landingMinOffset; - std::unique_ptr m_navLights; - std::unique_ptr m_shields; - std::unique_ptr m_gunModel; - std::unique_ptr m_scaleModel; - std::string m_modelName; - std::string m_requestedModelName; - RefCountedPtr m_ui; + Graphics::RenderState *m_bgState; RefCountedPtr m_bgBuffer; - //undecided on this input stuff - //updating the states of all inputs during PollEvents - std::map m_keyStates; - bool m_mouseButton[SDL_BUTTON_RIGHT + 1]; //buttons start at 1 - int m_mouseMotion[2]; - bool m_mouseWheelUp, m_mouseWheelDown; - - //interface stuff that needs to be accessed later (unorganized) - UI::MultiLineText *m_log; - RefCountedPtr m_logScroller; - - UI::List *m_fileList; - UI::DropDown *animSelector; - UI::DropDown *patternSelector; - UI::DropDown *decalSelector; - UI::Label *nameLabel; - UI::Slider *animSlider; - UI::Label *animValue; - UI::Slider *colorSliders[9]; - UI::Slider *thrustSliders[2 * 3]; //thruster sliders 2*xyz (linear & angular) - sigc::signal onModelChanged; Graphics::Drawables::Lines m_gridLines; diff --git a/src/main.cpp b/src/main.cpp index c461e0c73..d408874da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -205,7 +205,9 @@ start: std::string modelName; if (argc > 2) modelName = argv[2]; - ModelViewer::Run(modelName); + auto modelViewer = ModelViewerApp(); + modelViewer.SetInitialModel(modelName); + modelViewer.Run(); break; } From f6ebcbe966a0841c83d57f55c209c8bd446d5cfc Mon Sep 17 00:00:00 2001 From: Andrew Copland Date: Sat, 4 Apr 2020 20:33:18 +0100 Subject: [PATCH 8/9] Need this include as the array header doesn't appear to be included always --- src/Input.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Input.h b/src/Input.h index b5e88bb9f..424ee8fb8 100644 --- a/src/Input.h +++ b/src/Input.h @@ -8,6 +8,7 @@ #include "utils.h" #include +#include class IniConfig; From 2310827da059b66b87a592e44eb35605f4be2c0f Mon Sep 17 00:00:00 2001 From: Andrew Copland Date: Sat, 4 Apr 2020 20:33:39 +0100 Subject: [PATCH 9/9] Added a few missing files to the VS build --- win32/vs2019/pioneer.vcxproj | 3 +++ win32/vs2019/pioneer.vcxproj.filters | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/win32/vs2019/pioneer.vcxproj b/win32/vs2019/pioneer.vcxproj index 7d218a3ab..ae208a9f2 100644 --- a/win32/vs2019/pioneer.vcxproj +++ b/win32/vs2019/pioneer.vcxproj @@ -488,6 +488,7 @@ + @@ -679,10 +680,12 @@ + + diff --git a/win32/vs2019/pioneer.vcxproj.filters b/win32/vs2019/pioneer.vcxproj.filters index 2e5b01e31..a1b1cc8af 100644 --- a/win32/vs2019/pioneer.vcxproj.filters +++ b/win32/vs2019/pioneer.vcxproj.filters @@ -561,6 +561,9 @@ src\core + + src\pigui + @@ -1106,6 +1109,12 @@ src\core + + src\pigui + + + src\pigui +