Merge pull request #4849 from Web-eWorks/modelviewer-refactor

Refactor the ModelViewer to use PiGui
master
Webster Sheets 2020-04-06 15:51:48 -04:00 committed by GitHub
commit 08386499a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1673 additions and 1539 deletions

View File

@ -9,10 +9,17 @@
#include <array>
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<SDL_JoystickID, JoystickState>::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:

View File

@ -8,18 +8,16 @@
#include "utils.h"
#include <algorithm>
#include <array>
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<SDL_Keycode, bool> keyState;
IniConfig *m_config;
std::map<SDL_Keycode, uint8_t> keyState;
int keyModState;
char mouseButton[6];
int mouseMotion[2];
std::array<char, 6> mouseButton;
std::array<int, 2> mouseMotion;
int mouseWheel;
bool m_capturingMouse;
bool joystickEnabled;

File diff suppressed because it is too large Load Diff

View File

@ -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 <memory>
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<Input> m_input;
std::shared_ptr<ModelViewer> 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<std::string> m_log;
bool m_resetLogScroll = false;
vector3f m_linearThrust = {};
vector3f m_angularThrust = {};
// Model pattern colors
std::vector<Color> m_colors;
std::vector<std::string> m_fileNames;
std::string m_modelName;
std::string m_requestedModelName;
std::unique_ptr<SceneGraph::Model> m_model;
bool m_modelIsShip = false;
std::vector<SceneGraph::Animation *> m_animations;
SceneGraph::Animation *m_currentAnimation = nullptr;
bool m_modelSupportsPatterns = false;
std::vector<std::string> m_patterns;
uint32_t m_currentPattern = 0;
bool m_modelSupportsDecals = false;
std::vector<std::string> m_decals;
uint32_t m_currentDecal = 0;
bool m_modelHasShields = false;
std::unique_ptr<Shields> m_shields;
std::unique_ptr<NavLights> m_navLights;
std::unique_ptr<SceneGraph::Model> m_gunModel;
std::unique_ptr<SceneGraph::Model> 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<NavLights> m_navLights;
std::unique_ptr<Shields> m_shields;
std::unique_ptr<SceneGraph::Model> m_gunModel;
std::unique_ptr<SceneGraph::Model> m_scaleModel;
std::string m_modelName;
std::string m_requestedModelName;
RefCountedPtr<UI::Context> m_ui;
Graphics::RenderState *m_bgState;
RefCountedPtr<Graphics::VertexBuffer> m_bgBuffer;
//undecided on this input stuff
//updating the states of all inputs during PollEvents
std::map<SDL_Keycode, bool> 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<UI::Scroller> 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<void> onModelChanged;
Graphics::Drawables::Lines m_gridLines;

View File

@ -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<UI::Context> Pi::ui;
RefCountedPtr<PiGui> 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();
}

View File

@ -16,6 +16,10 @@
#include <string>
#include <vector>
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::Context> ui;
static RefCountedPtr<PiGui> pigui;
static PiGui::Instance *pigui;
static Random rng;
static int statSceneTris;

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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<PiGui::Instance> m_pigui;
std::unique_ptr<Input> m_input;
std::string m_applicationTitle;
std::unique_ptr<Graphics::Renderer> m_renderer;

View File

@ -109,9 +109,6 @@ namespace Lua {
GameUI::Lua::Init();
SceneGraph::Lua::Init();
LuaObject<PiGui>::RegisterClass();
PiGUI::Lua::Init();
// XXX load everything. for now, just modules
lua_State *l = Lua::manager->GetLuaState();
pi_lua_dofile(l, "libs/autoload.lua");

View File

@ -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<bool>(l, false);
return 1;
}
LuaPush<bool>(l, first_body_is_more_important_than(body, other));
LuaPush<bool>(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<Body>::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<Body>::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)

View File

@ -117,7 +117,7 @@ static int l_engine_attr_ui(lua_State *l)
*/
static int l_engine_attr_pigui(lua_State *l)
{
LuaObject<PiGui>::PushToLua(Pi::pigui.Get());
LuaObject<PiGui::Instance>::PushToLua(Pi::pigui);
return 1;
}
@ -794,7 +794,7 @@ static int l_engine_world_space_to_screen_space(lua_State *l)
{
vector3d pos = LuaPull<vector3d>(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<bool>(l, res._onScreen);
LuaPush<vector2d>(l, res._screenPosition);

View File

@ -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<PiGui>::CheckFromLua(1);
PiGui::Instance *pigui = LuaObject<PiGui::Instance>::CheckFromLua(1);
std::string fontname = LuaPull<std::string>(l, 2);
int size = LuaPull<int>(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<double>(l, 1);
const double ship_max_distance = LuaPull<double>(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<PiGui>::CheckFromLua(1);
pigui->GetHandlers().PushCopyToStack();
PiGui::Instance *pigui = LuaObject<PiGui::Instance>::CheckFromLua(1);
PiGUI::GetHandlers().PushCopyToStack();
return 1;
}
static int l_attr_keys(lua_State *l)
{
PROFILE_SCOPED()
PiGui *pigui = LuaObject<PiGui>::CheckFromLua(1);
pigui->GetKeys().PushCopyToStack();
PiGui::Instance *pigui = LuaObject<PiGui::Instance>::CheckFromLua(1);
PiGUI::GetKeys().PushCopyToStack();
return 1;
}
static int l_attr_screen_width(lua_State *l)
{
PROFILE_SCOPED()
// PiGui *pigui = LuaObject<PiGui>::CheckFromLua(1);
// PiGui::Instance *pigui = LuaObject<PiGui::Instance>::CheckFromLua(1);
LuaPush<int>(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<PiGui>::CheckFromLua(1);
// PiGui::Instance *pigui = LuaObject<PiGui::Instance>::CheckFromLua(1);
LuaPush<int>(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<PiGui>::CheckFromLua(1);
PiGui::Instance *pigui = LuaObject<PiGui::Instance>::CheckFromLua(1);
std::string svg_filename = LuaPull<std::string>(l, 2);
int width = LuaPull<int>(l, 3);
int height = LuaPull<int>(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<PiGui>::s_type = "PiGui";
void PiGUI::RunHandler(double delta, std::string handler)
{
PROFILE_SCOPED()
ScopedTable t(GetHandlers());
if (t.Get<bool>(handler)) {
t.Call<bool>(handler, delta);
Pi::renderer->CheckRenderErrors(__FUNCTION__, __LINE__);
}
}
template <>
void LuaObject<PiGui>::RegisterClass()
const char *LuaObject<PiGui::Instance>::s_type = "PiGui";
template <>
void LuaObject<PiGui::Instance>::RegisterClass()
{
static const luaL_Reg l_methods[] = {
{ "Begin", l_pigui_begin },

View File

@ -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<TScreenSpace> TSS_vector;
typedef std::vector<TScreenSpace> 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

View File

@ -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;
}

View File

@ -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 <float.h>
#include <stdio.h>
@ -24,38 +24,36 @@
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
std::vector<Graphics::Texture *> PiGui::m_svg_textures;
using namespace PiGui;
static int to_keycode(int key)
std::vector<Graphics::Texture *> m_svg_textures;
std::vector<Graphics::Texture *> &PiGui::GetSVGTextures()
{
/*if(key & SDLK_SCANCODE_MASK) {
return (key & ~SDLK_SCANCODE_MASK) | 0x100;
}*/
return key;
return m_svg_textures;
}
static std::vector<std::pair<std::string, int>> 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<ImTextureID>(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 &center, std::string popup_id, int mouse_button, std::vector<ImTextureID> tex_ids, std::vector<std::pair<ImVec2, ImVec2>> uvs, unsigned int size, std::vector<std::string> 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<int>(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<int>((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 &center, 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<Graphics::OGL::TextureGL *>(pTex);
Uint32 result = pGLTex->GetTextureID();
m_svg_textures.push_back(pTex); // store for cleanup later
return reinterpret_cast<void *>(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<bool>(handler)) {
t.Call<bool>(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 &center, 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<float>(frame_padding), static_cast<float>(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>(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<float>(frame_padding), static_cast<float>(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<ImGuiCol>((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
{

View File

@ -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 <unordered_set>
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<unsigned short> m_invalid_glyphs;
mutable std::vector<std::pair<unsigned short, unsigned short>> m_used_ranges;
ImVector<ImWchar> 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<unsigned short, unsigned short> &invalid_glyphs() const { return m_invalid_glyphs; }
const std::vector<std::pair<unsigned short, unsigned short>> &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<PiFace> m_faces;
int m_pixelsize;
class PiFace {
public:
using UsedRange = std::pair<uint16_t, uint16_t>;
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<PiFace> &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<PiFace> &faces() const { return m_faces; }
std::vector<PiFace> &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<unsigned short, unsigned short> &invalid_glyphs() const { return m_invalid_glyphs; }
const std::vector<UsedRange> &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<unsigned short> m_invalid_glyphs;
mutable std::vector<UsedRange> m_used_ranges;
ImVector<ImWchar> m_imgui_ranges;
};
class PiFont {
public:
PiFont(const std::string &name) :
m_name(name) {}
PiFont(const std::string &name, const std::vector<PiFace> &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<PiFace> &faces() const { return m_faces; }
std::vector<PiFace> &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<std::pair<std::string, int>, ImFont *> m_fonts;
std::map<ImFont *, std::pair<std::string, int>> m_im_fonts;
std::map<std::pair<std::string, int>, PiFont> m_pi_fonts;
bool m_should_bake_fonts;
private:
std::string m_name;
std::vector<PiFace> m_faces;
int m_pixelsize;
};
std::map<std::string, PiFont> 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<std::pair<std::string, int>, ImFont *> m_fonts;
std::map<ImFont *, std::pair<std::string, int>> m_im_fonts;
std::map<std::pair<std::string, int>, 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<std::string, PiFont> 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 &center, std::string popup_id, int mouse_button, std::vector<ImTextureID> tex_ids, std::vector<std::pair<ImVec2, ImVec2>> uvs, unsigned int size, std::vector<std::string> tooltips);
bool CircularSlider(const ImVec2 &center, 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 &center, std::string popup_id, int mouse_button, std::vector<ImTextureID> tex_ids, std::vector<std::pair<ImVec2, ImVec2>> uvs, unsigned int size, std::vector<std::string> tooltips);
static bool CircularSlider(const ImVec2 &center, 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<Graphics::Texture *> &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<Graphics::Texture *> m_svg_textures;
};
} //namespace PiGui

View File

@ -5,17 +5,65 @@
#include "Face.h"
#include "Image.h"
#include "ModelSpinner.h"
#include "lua/LuaTable.h"
static std::vector<std::pair<std::string, int>> 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<PiGui::Instance>::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<PiGUI::Image>::RegisterClass();
LuaObject<PiGUI::Face>::RegisterClass();
LuaObject<PiGUI::ModelSpinner>::RegisterClass();
RegisterSandbox();
}
void Uninit()
{
m_handlers.Unref();
m_keys.Unref();
}
} // namespace Lua
LuaRef GetHandlers() { return m_handlers; }
LuaRef GetKeys() { return m_keys; }
} // namespace PiGUI

View File

@ -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

View File

@ -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);

303
src/pigui/Widgets.cpp Normal file
View File

@ -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 &center, std::string popup_id, int mouse_button, std::vector<ImTextureID> tex_ids, std::vector<std::pair<ImVec2, ImVec2>> uvs, unsigned int size, std::vector<std::string> 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<int>(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<int>((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 &center, 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 &center, 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<float>(frame_padding), static_cast<float>(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>(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<float>(frame_padding), static_cast<float>(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<ImGuiCol>((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;
}

View File

@ -488,6 +488,7 @@
<ClCompile Include="..\..\src\pigui\PiGuiLua.cpp" />
<ClCompile Include="..\..\src\pigui\LuaFace.cpp" />
<ClCompile Include="..\..\src\pigui\PiGuiSandbox.cpp" />
<ClCompile Include="..\..\src\pigui\Widgets.cpp" />
<ClCompile Include="..\..\src\Plane.cpp" />
<ClCompile Include="..\..\src\Planet.cpp" />
<ClCompile Include="..\..\src\Player.cpp" />
@ -679,10 +680,12 @@
<ClInclude Include="..\..\src\Pi.h" />
<ClInclude Include="..\..\src\pigui\Face.h" />
<ClInclude Include="..\..\src\pigui\Image.h" />
<ClInclude Include="..\..\src\pigui\LuaFlags.h" />
<ClInclude Include="..\..\src\pigui\ModelSpinner.h" />
<ClInclude Include="..\..\src\pigui\PerfInfo.h" />
<ClInclude Include="..\..\src\pigui\PiGui.h" />
<ClInclude Include="..\..\src\pigui\PiGuiLua.h" />
<ClInclude Include="..\..\src\pigui\View.h" />
<ClInclude Include="..\..\src\Plane.h" />
<ClInclude Include="..\..\src\Planet.h" />
<ClInclude Include="..\..\src\Player.h" />

View File

@ -561,6 +561,9 @@
<ClCompile Include="..\..\src\core\Application.cpp">
<Filter>src\core</Filter>
</ClCompile>
<ClCompile Include="..\..\src\pigui\Widgets.cpp">
<Filter>src\pigui</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\Aabb.h">
@ -1106,6 +1109,12 @@
<ClInclude Include="..\..\src\core\GuiApplication.h">
<Filter>src\core</Filter>
</ClInclude>
<ClInclude Include="..\..\src\pigui\View.h">
<Filter>src\pigui</Filter>
</ClInclude>
<ClInclude Include="..\..\src\pigui\LuaFlags.h">
<Filter>src\pigui</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\src\win32\pioneer.rc">