diff --git a/src/Input.cpp b/src/Input.cpp index 6895c3b98..fb8ded619 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -9,10 +9,17 @@ #include -void Input::Init(GameConfig *config) +Input::Input(IniConfig *config) : + m_config(config), + m_capturingMouse(false), + mouseYInvert(false), + joystickEnabled(true), + keyModState(0), + 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(); } @@ -22,8 +29,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 +39,25 @@ 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) { bool matched = false; @@ -95,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); @@ -110,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); @@ -120,30 +146,33 @@ 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; 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); } 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 157a7abea..424ee8fb8 100644 --- a/src/Input.h +++ b/src/Input.h @@ -8,18 +8,16 @@ #include "utils.h" #include +#include -class GameConfig; +class IniConfig; class Input { - // TODO: better decouple these two classes. - friend class Pi; - public: - Input() = default; - void Init(GameConfig *config); + Input(IniConfig *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. @@ -94,7 +92,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); @@ -132,9 +140,11 @@ public: void GetMouseMotion(int motion[2]) { - memcpy(motion, mouseMotion, sizeof(int) * 2); + 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 +164,13 @@ public: private: void InitJoysticks(); - std::map keyState; + IniConfig *m_config; + + std::map keyState; int keyModState; - char mouseButton[6]; - int mouseMotion[2]; + std::array mouseButton; + std::array mouseMotion; + int mouseWheel; bool m_capturingMouse; bool joystickEnabled; 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/Pi.cpp b/src/Pi.cpp index 22e95b0be..a00c9ba11 100644 --- a/src/Pi.cpp +++ b/src/Pi.cpp @@ -25,9 +25,11 @@ #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" +#include "lua/LuaPiGui.h" #include "lua/LuaTimer.h" #include "profiler/Profiler.h" #include "sound/AmbientSounds.h" @@ -133,7 +135,7 @@ float Pi::amountOfBackgroundStarsDisplayed = 1.0f; bool Pi::DrawGUI = true; Graphics::Renderer *Pi::renderer; RefCountedPtr Pi::ui; -RefCountedPtr Pi::pigui; +PiGui::Instance *Pi::pigui = nullptr; ModelCache *Pi::modelCache; Intro *Pi::intro; SDLGraphics *Pi::sdl; @@ -361,8 +363,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. @@ -371,6 +372,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; @@ -438,9 +441,11 @@ void Pi::App::Shutdown() BaseSphere::Uninit(); FaceParts::Uninit(); Graphics::Uninit(); - Pi::pigui->Uninit(); + + PiGUI::Lua::Uninit(); + ShutdownPiGui(); + Pi::pigui = nullptr; Pi::ui.Reset(0); - Pi::pigui.Reset(0); Lua::UninitModules(); Lua::Uninit(); Gui::Uninit(); @@ -452,6 +457,9 @@ void Pi::App::Shutdown() ShutdownRenderer(); Pi::renderer = nullptr; + ShutdownInput(); + Pi::input = nullptr; + delete Pi::config; delete Pi::planner; asyncJobQueue.reset(); @@ -489,14 +497,12 @@ 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... - Pi::pigui.Reset(new PiGui); - Pi::pigui->Init(Pi::renderer->GetSDLWindow()); + // 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()); - Pi::pigui->RunHandler(0.01, "INIT"); + Pi::pigui->NewFrame(); + PiGUI::RunHandler(0.01, "INIT"); Pi::pigui->EndFrame(); AddStep("UI::AddContext", []() { @@ -608,8 +614,8 @@ 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->RunHandler(progress, "INIT"); + Pi::pigui->NewFrame(); + PiGUI::RunHandler(progress, "INIT"); Pi::pigui->Render(); } else { @@ -643,43 +649,12 @@ 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); - Pi::pigui->NewFrame(Pi::renderer->GetSDLWindow()); - Pi::pigui->RunHandler(deltaTime, "MAINMENU"); + Pi::pigui->NewFrame(); + PiGUI::RunHandler(deltaTime, "MAINMENU"); Pi::pigui->Render(); @@ -847,12 +822,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 @@ -860,64 +836,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() @@ -1126,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 @@ -1137,7 +1092,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/Pi.h b/src/Pi.h index 08f13202c..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; @@ -101,7 +104,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 +173,7 @@ public: #endif static RefCountedPtr ui; - static RefCountedPtr pigui; + static PiGui::Instance *pigui; static Random rng; static int statSceneTris; diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 7b682efaf..3e620f85f 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() @@ -138,7 +142,7 @@ void Application::Run() EndFrame(); - if (m_activeLifecycle->m_endLifecycle) { + if (m_activeLifecycle->m_endLifecycle || !m_applicationRunning) { EndLifecycle(); } 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..8b6bdca52 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" @@ -31,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()); @@ -60,9 +62,9 @@ void GuiApplication::DrawRenderTarget() void GuiApplication::EndFrame() { #if RTT - m_renderer->SetRenderTarget(nullptr); DrawRenderTarget(); #endif + m_renderer->EndFrame(); m_renderer->SwapBuffers(); } @@ -100,7 +102,55 @@ Graphics::RenderTarget *GuiApplication::CreateRenderTarget(const Graphics::Setti return nullptr; } -Graphics::Renderer *GuiApplication::StartupRenderer(const GameConfig *config, bool hidden) +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 (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 (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(IniConfig *config, bool hidden) { PROFILE_SCOPED() // Initialize SDL @@ -145,3 +195,28 @@ void GuiApplication::ShutdownRenderer() SDL_QuitSubSystem(SDL_INIT_VIDEO); } + +Input *GuiApplication::StartupInput(IniConfig *config) +{ + m_input.reset(new Input(config)); + + return m_input.get(); +} + +void GuiApplication::ShutdownInput() +{ + m_input.reset(); +} + +PiGui::Instance *GuiApplication::StartupPiGui() +{ + m_pigui.Reset(new PiGui::Instance()); + m_pigui->Init(GetRenderer()); + 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..bccd1b6ab 100644 --- a/src/core/GuiApplication.h +++ b/src/core/GuiApplication.h @@ -4,32 +4,51 @@ #pragma once #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" #include "graphics/Renderer.h" +class IniConfig; + class GuiApplication : public Application { public: GuiApplication(std::string title) : Application(), m_applicationTitle(title) {} -protected: Graphics::Renderer *GetRenderer() { return m_renderer.get(); } + Input *GetInput() { return m_input.get(); } + PiGui::Instance *GetPiGui() { return m_pigui.Get(); } +protected: // Called at the end of the frame automatically, blits the RT onto the application // 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(IniConfig *config); + + // Call this from your Startup() method + PiGui::Instance *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 +57,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/Lua.cpp b/src/lua/Lua.cpp index 8e88cbf4e..553145acc 100644 --- a/src/lua/Lua.cpp +++ b/src/lua/Lua.cpp @@ -109,9 +109,6 @@ namespace Lua { GameUI::Lua::Init(); SceneGraph::Lua::Init(); - LuaObject::RegisterClass(); - 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/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 3b4ccb60d..93f3ecb41 100644 --- a/src/lua/LuaEngine.cpp +++ b/src/lua/LuaEngine.cpp @@ -117,7 +117,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; } @@ -794,7 +794,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); diff --git a/src/lua/LuaPiGui.cpp b/src/lua/LuaPiGui.cpp index ef9d92f5d..e2c3680e6 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" @@ -77,7 +78,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(); @@ -1381,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); @@ -1560,7 +1561,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 +1570,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 +1585,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 +1721,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 +1729,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 +1758,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 +1774,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 +1798,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 +1827,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); @@ -1941,23 +1942,23 @@ 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->GetHandlers().PushCopyToStack(); + PiGui::Instance *pigui = LuaObject::CheckFromLua(1); + PiGUI::GetHandlers().PushCopyToStack(); return 1; } static int l_attr_keys(lua_State *l) { PROFILE_SCOPED() - PiGui *pigui = LuaObject::CheckFromLua(1); - pigui->GetKeys().PushCopyToStack(); + PiGui::Instance *pigui = LuaObject::CheckFromLua(1); + PiGUI::GetKeys().PushCopyToStack(); return 1; } 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; } @@ -1993,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; } @@ -2365,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(Pi::renderer, svg_filename, width, height); // LuaPush(l, id); lua_pushlightuserdata(l, id); return 1; @@ -2467,11 +2468,21 @@ static int l_pigui_push_text_wrap_pos(lua_State *l) return 0; } -template <> -const char *LuaObject::s_type = "PiGui"; +void PiGUI::RunHandler(double delta, std::string handler) +{ + PROFILE_SCOPED() + ScopedTable t(GetHandlers()); + if (t.Get(handler)) { + t.Call(handler, delta); + Pi::renderer->CheckRenderErrors(__FUNCTION__, __LINE__); + } +} template <> -void LuaObject::RegisterClass() +const char *LuaObject::s_type = "PiGui"; + +template <> +void LuaObject::RegisterClass() { static const luaL_Reg l_methods[] = { { "Begin", l_pigui_begin }, diff --git a/src/lua/LuaPiGui.h b/src/lua/LuaPiGui.h index a1734080e..c94fc5ca9 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,25 @@ 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); + + // 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/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; } diff --git a/src/pigui/PiGui.cpp b/src/pigui/PiGui.cpp index 132d58ad2..1b945dd35 100644 --- a/src/pigui/PiGui.cpp +++ b/src/pigui/PiGui.cpp @@ -5,6 +5,9 @@ #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 #include "imgui/imgui.h" @@ -12,9 +15,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,38 +24,36 @@ #define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" -std::vector PiGui::m_svg_textures; +using namespace PiGui; -static int to_keycode(int key) +std::vector m_svg_textures; + +std::vector &PiGui::GetSVGTextures() { - /*if(key & SDLK_SCANCODE_MASK) { - return (key & ~SDLK_SCANCODE_MASK) | 0x100; - }*/ - return key; + return m_svg_textures; } -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) }, -}; +static ImTextureID makeTexture(Graphics::Renderer *renderer, unsigned char *pixels, int width, int height) +{ + PROFILE_SCOPED() + // 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); + 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 = 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); + PiGui::GetSVGTextures().push_back(pTex); // store for cleanup later + 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,10 +109,44 @@ 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); } -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)); @@ -126,7 +158,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 @@ -151,7 +183,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); @@ -175,7 +207,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* :) @@ -189,21 +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 PiGui::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_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); - } + m_renderer = renderer; IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -211,8 +233,8 @@ void PiGui::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."); @@ -241,156 +263,18 @@ 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() { 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."); @@ -399,24 +283,14 @@ void PiGui::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); } -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() +void Instance::EndFrame() { PROFILE_SCOPED() @@ -443,14 +317,14 @@ void PiGui::EndFrame() } } -void PiGui::Render() +void Instance::Render() { PROFILE_SCOPED() EndFrame(); ImGui::Render(); - switch (Pi::renderer->GetRendererType()) { + switch (m_renderer->GetRendererType()) { default: case Graphics::RENDERER_DUMMY: return; @@ -460,7 +334,7 @@ void PiGui::Render() } } -void PiGui::ClearFonts() +void Instance::ClearFonts() { PROFILE_SCOPED() ImGuiIO &io = ImGui::GetIO(); @@ -470,7 +344,7 @@ void PiGui::ClearFonts() io.Fonts->Clear(); } -void PiGui::BakeFont(PiFont &font) +void Instance::BakeFont(PiFont &font) { PROFILE_SCOPED() ImGuiIO &io = ImGui::GetIO(); @@ -511,7 +385,7 @@ void PiGui::BakeFont(PiFont &font) imfont->MissingGlyphs.clear(); } -void PiGui::BakeFonts() +void Instance::BakeFonts() { PROFILE_SCOPED() // Output("Baking fonts\n"); @@ -538,199 +412,14 @@ 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::Cleanup() +void Instance::Uninit() { PROFILE_SCOPED() for (auto tex : m_svg_textures) { delete tex; } - switch (Pi::renderer->GetRendererType()) { + switch (m_renderer->GetRendererType()) { default: case Graphics::RENDERER_DUMMY: return; @@ -743,25 +432,9 @@ void PiGui::Cleanup() 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 8ce5b4ac2..9a53d0bd5 100644 --- a/src/pigui/PiGui.h +++ b/src/pigui/PiGui.h @@ -1,140 +1,149 @@ // 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 { - 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; + class Renderer; +} // namespace Graphics -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(); + void Init(Graphics::Renderer *renderer); + void Uninit(); -public: - PiGui(); + // Call at the start of every frame. Calls ImGui::NewFrame() internally. + void NewFrame(); - LuaRef GetHandlers() const { return m_handlers; } + // Call at the end of a frame that you're not going to render the results of + void EndFrame(); - LuaRef GetKeys() const { return m_keys; } + // Calls ImGui::EndFrame() internally and does book-keeping before rendering. + void Render(); - void RunHandler(double delta, std::string handler = "GAME"); + ImFont *AddFont(const std::string &name, int size); + ImFont *GetFont(const std::string &name, int size); - // Call at the start of every frame. Calls ImGui::NewFrame() internally. - void NewFrame(SDL_Window *window); + void AddGlyph(ImFont *font, unsigned short glyph); - // Call at the end of a frame that you're not going to render the results of - void EndFrame(); + bool ProcessEvent(SDL_Event *event); - // Calls ImGui::EndFrame() internally and does book-keeping before rendering. - void Render(); + void RefreshFontsTexture(); - void Init(SDL_Window *window); + private: + Graphics::Renderer *m_renderer; - ImFont *GetFont(const std::string &name, int size); + std::map, ImFont *> m_fonts; + std::map> m_im_fonts; + std::map, PiFont> m_pi_fonts; + bool m_should_bake_fonts; - void Uninit() - { - Cleanup(); - m_handlers.Unref(); - m_keys.Unref(); - } - ImFont *AddFont(const std::string &name, int size); + std::map m_font_definitions; - void AddGlyph(ImFont *font, unsigned short glyph); + void BakeFonts(); + void BakeFont(PiFont &font); + void AddFontDefinition(const PiFont &font) { m_font_definitions[font.name()] = font; } + void ClearFonts(); + }; - static ImTextureID RenderSVG(std::string svgFilename, 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 ProcessEvent(SDL_Event *event); + 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 RefreshFontsTexture(); + 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); - static void *makeTexture(unsigned char *pixels, int width, int height); - - static bool WantCaptureMouse() + 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); - 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); + std::vector &GetSVGTextures(); + ImTextureID RenderSVG(Graphics::Renderer *renderer, std::string svgFilename, int width, int height); - 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; -}; +} //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; +} 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 +