This commit is contained in:
andrewchilicki
2025-02-08 15:12:24 -05:00
parent 902993bde1
commit 71bd81f2b9
60 changed files with 99637 additions and 12 deletions

121
src/UI/UIManager.cpp Normal file
View File

@ -0,0 +1,121 @@
#include "UIManager.h"
#include "Image/ImageDisplay.h"
#include "Widgets/IdleScreen.h"
#include "Widgets/NumbersPanel.h"
#include "imgui.h"
#include "backends/imgui_impl_glfw.h"
#include "backends/imgui_impl_opengl3.h"
#include <GLFW/glfw3.h>
namespace ColorValues
{
ImColor lumonBlue = ImColor(101,213,235,255);
}
class UIManagerImpl : public UIManager
{
public:
UIManagerImpl()
{
imageDisplay = createImageDisplay("./assets/");
numbersPanel = createNumbersPanel(imageDisplay);
idleScreen = createIdleScreen(imageDisplay);
}
void init() final {
// Initialize ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
// Initialize ImGui backends
ImGui_ImplGlfw_InitForOpenGL(glfwGetCurrentContext(), true);
ImGui_ImplOpenGL3_Init("#version 130");
numbersPanel->init();
}
void update() final
{
if (ImGui::IsKeyPressed(ImGuiKey_Tab))
{
debugMode = !debugMode;
}
if (ImGui::IsKeyPressed(ImGuiKey_I))
{
idleMode = !idleMode;
}
if (idleMode)
{
idleScreen->update();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
idleMode = false;
numbersPanel->triggerLoadAnimation();
}
} else
{
numbersPanel->update();
}
}
void draw() final
{
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImVec2 viewportSize = viewport->WorkSize;
ImVec2 viewportPos = viewport->WorkPos;
static float settingsWidthRatio = 0.4f;
// Draw widgets
ImGui::SetNextWindowPos(ImVec2(viewportPos.x, viewportPos.y));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x, viewportSize.y));
if (ImGui::Begin("Main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
{
if (idleMode)
{
idleScreen->drawIdleScreen();
} else
{
numbersPanel->drawNumbersPanel();
}
}
ImGui::End();
if (debugMode)
{
ImGui::SetNextWindowPos(viewportPos);
ImGui::SetNextWindowSize(ImVec2(viewportSize.x * settingsWidthRatio, viewportSize.y));
if (ImGui::Begin("Settings"))
{
numbersPanel->drawSettings();
}
ImGui::End();
}
}
void cleanup() final {
// Cleanup ImGui
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
private:
std::shared_ptr<ImageDisplay> imageDisplay;
std::shared_ptr<NumbersPanel> numbersPanel;
std::shared_ptr<IdleScreen> idleScreen;
bool debugMode = false;
bool idleMode = false;
};
std::shared_ptr<UIManager> createUIManager()
{
return std::make_shared<UIManagerImpl>();
}

25
src/UI/UIManager.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <imgui.h>
#include <memory>
#ifndef GLOBALS_H
#define GLOBALS_H
namespace ColorValues
{
extern ImColor lumonBlue;
}
#endif
class UIManager {
public:
virtual void init() = 0;
virtual void draw() = 0;
virtual void update() = 0;
virtual void cleanup() = 0;
virtual ~UIManager() = default;
};
std::shared_ptr<UIManager> createUIManager();

View File

@ -0,0 +1,89 @@
#include "IdleScreen.h"
#include "ImageDisplay.h"
#include <imgui.h>
#include <iostream>
#include <ostream>
#include "../UIManager.h"
class IdleScreenImpl : public IdleScreen
{
public:
explicit IdleScreenImpl(std::shared_ptr<ImageDisplay> imageDisplay_) : imageDisplay(std::move(imageDisplay_))
{
auto [width, height] = imageDisplay->getImageSize(logoPath);
logoSize = ImVec2(width, height);
}
void update() final
{
// Adjust logo scale and speed based on viewport size so it is consistent
auto viewportSize = ImGui::GetMainViewport()->Size;
if (lastViewportSize.x != viewportSize.x || lastViewportSize.y != viewportSize.y)
{
float displaySizeScalePrev = lastViewportSize.x/1280.f;
float displaySizeScale = viewportSize.x/1280.f;
float adjustedScale = displaySizeScale / displaySizeScalePrev;
scale = adjustedScale * 0.5f;
speed = adjustedScale * 0.5f;
}
}
void drawIdleScreen() final
{
setLogoPosition();
ImGui::SetCursorPos(currentLogoPosition);
imageDisplay->drawImGuiImage(logoPath, scale, ColorValues::lumonBlue);
}
void setLogoPosition()
{
ImVec2 windowSize = ImGui::GetWindowSize();
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 minPosition = ImVec2(windowPos.x, windowPos.y);
ImVec2 maxPosition = ImVec2(windowPos.x + windowSize.x - scale*logoSize.x, windowPos.y + windowSize.y - scale*logoSize.y);
if (currentLogoPosition.x >= maxPosition.x)
{
nextOffset.x = -1;
} else if (currentLogoPosition.x < minPosition.x)
{
nextOffset.x = 1;
}
if (currentLogoPosition.y >= maxPosition.y)
{
nextOffset.y = -1;
} else if (currentLogoPosition.y < minPosition.y)
{
nextOffset.y = 1;
}
currentLogoPosition = ImVec2(currentLogoPosition.x + speed*nextOffset.x, currentLogoPosition.y + speed*nextOffset.y);
}
private:
std::shared_ptr<ImageDisplay> imageDisplay;
float speed = 0.5f;
float scale = 0.5f;
ImVec2 windowSize;
ImVec2 windowPos;
std::string logoPath = "lumon-logo.png";
ImVec2 logoSize;
ImVec2 lastViewportSize = ImVec2(1280, 720);
ImVec2 currentLogoPosition;
ImVec2 nextOffset = ImVec2(1,1);
};
std::shared_ptr<IdleScreen> createIdleScreen(const std::shared_ptr<ImageDisplay>& imageDisplay)
{
return std::make_shared<IdleScreenImpl>(imageDisplay);
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <memory>
class ImageDisplay;
class IdleScreen {
public:
virtual void update() = 0;
virtual void drawIdleScreen() = 0;
virtual ~IdleScreen() = default;
};
std::shared_ptr<IdleScreen> createIdleScreen(const std::shared_ptr<ImageDisplay>& imageDisplay);

View File

@ -0,0 +1,472 @@
#include "NumbersPanel.h"
#include "Numbers/NumberGrid.h"
#include "ImageDisplay.h"
#include "Settings.h"
#include "../UIManager.h"
#include <cmath>
#include <imgui.h>
#include <imgui_internal.h>
#include <iostream>
#include <random>
#include <utility>
#include <json.hpp>
#include "PerlinNoise.hpp"
class NumbersPanelImpl : public NumbersPanel
{
public:
explicit NumbersPanelImpl(std::shared_ptr<ImageDisplay> imageDisplay) : imageDisplay(std::move(imageDisplay))
{
numberGrid = createNumberGrid(gridSize);
// Update max bad groups for each bin
auto badGroups = numberGrid->getBadGroups();
for (auto &[id, group] : badGroups)
{
bins[group->binIdx].maxBadGroups++;
}
// Load settings
if (auto loadedSettings = loadSettings(settingsSavePath))
{
displaySettings = loadedSettings->displaySettings;
controlSettings = loadedSettings->controlSettings;
std::cout << "Successfully loaded settings from disk." << std::endl;
}
}
void init() final
{
// Load custom font
ImGuiIO& io = ImGui::GetIO();
font = io.Fonts->AddFontFromFileTTF("./assets/Montserrat-Bold.ttf", 50.f);
io.Fonts->Build();
if (font == nullptr)
{
font = ImGui::GetDefaultFont();
std::cerr << "Failed to load 'Montserrat-Bold' font." << std::endl;
}
}
void update() final
{
numberGrid->update();
}
void drawNumbersPanel() final
{
bool displaySizeChanged = updateDisplaySettings(displayPresets, displaySettings.globalScale);
ImVec2 mousePos = ImGui::GetIO().MousePos;
ImVec2 windowSize = ImGui::GetWindowSize();
ImVec2 windowPos = ImGui::GetWindowPos();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// Update viewport
bool viewportChanged = updateViewport(windowSize);
// Draw Overlays
drawGraphicOverlays(windowPos, windowSize, draw_list);
// Draw Grid
auto numberRefiningToBin = drawNumbersGrid(windowPos, windowSize, mousePos, viewportChanged || displaySizeChanged);
// Draw Bins
drawBins(windowPos, windowSize, draw_list, numberRefiningToBin);
}
void triggerLoadAnimation() final
{
// Reset 'regenerate scale' on all numbers
for (auto &[x, gridY] : numberGrid->getGrid())
{
for (auto &[y, gridNumber] : gridY)
{
gridNumber->regenerateScale = 0.f;
}
}
}
private:
std::optional<int> drawNumbersGrid(const ImVec2& windowPos, const ImVec2& windowSize, const ImVec2& mousePos, bool updateDisplayInfos)
{
std::optional<int> refiningToBin = std::nullopt;
for (auto &[x, gridY] : numberGrid->getGrid())
{
for (auto &[y, gridNumber] : gridY)
{
std::string numberToDraw = "numbers/" + std::to_string(gridNumber->num) + ".png";
auto [width, height] = imageDisplay->getImageSize(numberToDraw);
double badScale = gridNumber->badGroup ? gridNumber->badGroup->scale : 0.0;
if (updateDisplayInfos)
{
ImVec2 localNumberPos = ImVec2((x * displaySettings.gridSpacing + panelOffset.x)*panelScale, (y * displaySettings.gridSpacing + panelOffset.y)*panelScale);
gridNumber->displayInfos.centerX = localNumberPos.x + windowPos.x;
gridNumber->displayInfos.centerY = localNumberPos.y + windowPos.y;
double baseNumberScale = gridNumber->regenerateScale*displaySettings.imageScale*panelScale;
double widthOffset = (baseNumberScale*width/2.f);
double heightOffset = (baseNumberScale*height/2.f);
gridNumber->displayInfos.isVisible = gridNumber->displayInfos.centerX + widthOffset < windowPos.x + windowSize.x && gridNumber->displayInfos.centerX - widthOffset > windowPos.x &&
gridNumber->displayInfos.centerY + heightOffset < windowPos.y + windowSize.y - displayPresets.numberWindowBufferBottom && gridNumber->displayInfos.centerY - heightOffset > windowPos.y + displayPresets.numberWindowBufferTop;
}
if (!gridNumber->displayInfos.isVisible)
{
continue;
}
auto centerPos = ImVec2(gridNumber->displayInfos.centerX, gridNumber->displayInfos.centerY);
if (gridNumber->badGroup && gridNumber->badGroup->refined)
{
float startX = gridNumber->displayInfos.centerX;
float startY = gridNumber->displayInfos.centerY;
if (gridNumber->displayInfos.refinedX == -1)
{
gridNumber->displayInfos.refinedX = startX;
gridNumber->displayInfos.refinedY = startY;
}
auto binIdx = gridNumber->badGroup->binIdx;
if (binIdx > 4)
{
std::cout << "Error: Bin index greater than expected. Setting to max." << std::endl;
binIdx = 4;
}
float distX = bins[binIdx].pos.x - gridNumber->displayInfos.refinedX;
float distY = bins[binIdx].pos.y - gridNumber->displayInfos.refinedY;
float distance = sqrt(distX * distX + distY * distY);
const float moveSpeed = 3.0f;
if (distance > moveSpeed)
{
float dirX = distX / distance;
float dirY = distY / distance;
gridNumber->displayInfos.refinedX += dirX * moveSpeed;
gridNumber->displayInfos.refinedY += dirY * moveSpeed;
centerPos = ImVec2(gridNumber->displayInfos.refinedX, gridNumber->displayInfos.refinedY);
refiningToBin = gridNumber->badGroup->binIdx;
}
else
{
gridNumber->badGroup.reset(); // No longer a bad number
gridNumber->num = numberGrid->randomNumber(0,9);
gridNumber->regenerateScale = 0.f;
}
}
float numberAlpha = 255;
if (gridNumber->regenerateScale < 1.f)
{
gridNumber->regenerateScale += numberGrid->randomNumber(0,10)*0.001f;
numberAlpha = static_cast<int>(std::clamp(gridNumber->regenerateScale*2.f*255.f, 0.f, 255.f));
}
// Offset from noise scale
double noiseScale = perlin.noise3D((x * displaySettings.noiseScale), (y * displaySettings.noiseScale), t*displaySettings.noiseSpeed);
if (gridNumber->displayInfos.horizontalOffset)
{
centerPos.x += noiseScale*displaySettings.noiseScaleOffset;
} else
{
centerPos.y += noiseScale*displaySettings.noiseScaleOffset;
}
// Colour
auto col = gridNumber->badGroup ? ImVec4(255,0,0,255) : ImVec4(255,255,255,numberAlpha);
if (gridNumber->badGroup && gridNumber->badGroup->isActive)
{
col = ImVec4(255,255,0,numberAlpha);
}
if (!revealMap)
{
col = ImVec4(255,255,255,numberAlpha);
}
// Scale from mouse hovering
auto numberScale = getScaleFromCursor(centerPos, mousePos);
if (gridNumber->badGroup && gridNumber->badGroup->isActive)
{
if (numberScale > 1.0f)
{
gridNumber->badGroup->superActive = true;
}
if (numberScale >= (0.5f + displaySettings.mouseScaleMultiplier) && ImGui::IsKeyDown(ImGuiKey_MouseLeft))
{
gridNumber->badGroup->refined = true;
bins[gridNumber->badGroup->binIdx].badGroupsRefined++;
}
}
if (gridNumber->badGroup && gridNumber->badGroup->superActive)
{
centerPos.x += numberGrid->randomNumber(-10, 10)*badScale;
centerPos.y += numberGrid->randomNumber(-10, 10)*badScale;
}
float combinedScale = gridNumber->regenerateScale*displaySettings.imageScale*numberScale*panelScale + badScale;
ImGui::SetCursorPos(ImVec2(centerPos.x - ImGui::GetWindowPos().x - ((width*combinedScale)/2.f), centerPos.y - ImGui::GetWindowPos().y - ((height*combinedScale)/2.f)));
imageDisplay->drawImGuiImage(numberToDraw, combinedScale, col);
}
}
t += 1;
return refiningToBin;
}
void drawBins(const ImVec2& windowPos, const ImVec2& windowSize, ImDrawList* drawList, std::optional<int> numberRefiningToBin)
{
std::string binPercentPath = "bins/bin-percent.png";
auto [widthP, heightP] = imageDisplay->getImageSize(binPercentPath);
for (auto &b : bins)
{
auto pos = b.updatePos(windowSize, windowPos, displayPresets.numberWindowBufferBottom - displayPresets.binOffset);
std::string binPath = "bins/bin0" + std::to_string(b.id) + ".png";
auto [width, height] = imageDisplay->getImageSize(binPath);
ImGui::SetCursorPos(ImVec2(pos.x - windowPos.x - (width*displayPresets.binImageScale/2.f), pos.y - windowPos.y - (height*displayPresets.binImageScale/2.f)));
imageDisplay->drawImGuiImage(binPath, displayPresets.binImageScale, ColorValues::lumonBlue);
auto percentPos = ImVec2(pos.x, pos.y + displayPresets.binPercentBarOffset);
ImGui::SetCursorPos(ImVec2(percentPos.x - windowPos.x - (widthP*displayPresets.binImageScale/2.f), percentPos.y - windowPos.y - (heightP*displayPresets.binImageScale/2.f)));
imageDisplay->drawImGuiImage(binPercentPath, displayPresets.binImageScale, ColorValues::lumonBlue);
ImVec2 trCorner = ImVec2(percentPos.x - (widthP*displayPresets.binImageScale/2.f), percentPos.y - (heightP*displayPresets.binImageScale/2.f));
ImVec2 brCorner = ImVec2(percentPos.x + (widthP*displayPresets.binImageScale/2.f), percentPos.y + (heightP*displayPresets.binImageScale/2.f));
double percentD = double(b.badGroupsRefined) / double(b.maxBadGroups);
int percentInt = lround(percentD * 100.f);
std::string percentString = std::to_string(percentInt) + "%";
drawList->AddText(font, displayPresets.fontSize, ImVec2(trCorner.x + 5.f, (trCorner.y + brCorner.y)/2.f - displayPresets.fontSize/2.f), ColorValues::lumonBlue, percentString.c_str());
drawList->AddRectFilled(trCorner, ImVec2(trCorner.x + ((brCorner.x - trCorner.x)* percentD), brCorner.y), ImColor(ColorValues::lumonBlue.Value.x, ColorValues::lumonBlue.Value.y, ColorValues::lumonBlue.Value.z, 0.3f));
// Bin open
if (numberRefiningToBin && *numberRefiningToBin == b.id - 1)
{
std::string binOpenPath = "bins/bin-open.png";
auto [widthO, heightO] = imageDisplay->getImageSize(binOpenPath);
ImGui::SetCursorPos(ImVec2(pos.x - windowPos.x - (widthO*displayPresets.binImageScale/2.f), pos.y - windowPos.y - (heightO*displayPresets.binImageScale/2.f) - (height*displayPresets.binImageScale)));
imageDisplay->drawImGuiImage(binOpenPath, displayPresets.binImageScale, ColorValues::lumonBlue);
}
}
}
void drawGraphicOverlays(const ImVec2& windowPos, const ImVec2& windowSize, ImDrawList* drawList)
{
// Header box
ImVec2 headerBoxMin = ImVec2(windowPos.x + displayPresets.headerBoxBufferX, windowPos.y + displayPresets.headerBoxBufferY);
ImVec2 headerBoxMax = ImVec2(windowPos.x + windowSize.x - displayPresets.headerBoxBufferX - displayPresets.headerImageOffsetX, windowPos.y + displayPresets.numberWindowBufferTop - displayPresets.lineGraphicsSpacing - displayPresets.headerBoxBufferY);
drawList->AddRect(headerBoxMin, headerBoxMax, ColorValues::lumonBlue);
ImVec2 headerTextPos = ImVec2(headerBoxMin.x + 25.f, (headerBoxMin.y+headerBoxMax.y)/2.f - displayPresets.fontSize/2.f);
drawList->AddText(font, displayPresets.fontSize, headerTextPos, ColorValues::lumonBlue, "@andrewchilicki");
// Lumon logo
std::string logoPath = "lumon-logo.png";
auto [widthH, heightH] = imageDisplay->getImageSize(logoPath);
ImGui::SetCursorPos(ImVec2(headerBoxMax.x - (widthH*displayPresets.headerImageScale)/2.f, (headerBoxMax.y + headerBoxMin.y)/2.f - (heightH*displayPresets.headerImageScale)/2.f));
imageDisplay->drawImGuiImage(logoPath, displayPresets.headerImageScale, ColorValues::lumonBlue);
// Horizontal lines
auto drawLine = [&](const float y)
{
drawList->AddLine(ImVec2(windowPos.x, y), ImVec2(windowPos.x + windowSize.x, y), ColorValues::lumonBlue, displayPresets.lineThickness);
};
float topLineY = windowPos.y + displayPresets.numberWindowBufferTop;
drawLine(topLineY);
drawLine(topLineY - displayPresets.lineGraphicsSpacing);
float bottomLineY = windowPos.y + windowSize.y - displayPresets.numberWindowBufferBottom;
drawLine(bottomLineY);
drawLine(bottomLineY + displayPresets.lineGraphicsSpacing);
}
bool updateViewport(const ImVec2& windowSize)
{
static bool viewportInit = false;
bool viewportChanged = !viewportInit;
// Handle arrow key input
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
{
panelOffset.x += controlSettings.arrowSensitivity;
viewportChanged = true;
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow))
{
panelOffset.x -= controlSettings.arrowSensitivity;
viewportChanged = true;
}
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
{
panelOffset.y += controlSettings.arrowSensitivity;
viewportChanged = true;
} else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
{
panelOffset.y -= controlSettings.arrowSensitivity;
viewportChanged = true;
}
// Handle zoom
if (ImGui::IsKeyPressed(ImGuiKey_Comma))
{
panelScale -= controlSettings.zoomSensitivity;
viewportChanged = true;
} else if (ImGui::IsKeyPressed(ImGuiKey_Period))
{
panelScale += controlSettings.zoomSensitivity;
viewportChanged = true;
}
// panelScale = std::clamp(panelScale, 0.8f, 1.8f);
if (viewportChanged)
{
// Clamp movement to within grid boundaries
float gridWidthScaled = gridSize * displaySettings.gridSpacing * panelScale;
float gridHeightScaled = gridSize * displaySettings.gridSpacing * panelScale;
float minOffsetX = -gridWidthScaled + windowSize.x;
float maxOffsetX = 0;
float minOffsetY = -gridHeightScaled + windowSize.y - displayPresets.numberWindowBufferTop;
float maxOffsetY = displayPresets.numberWindowBufferTop;
if (!viewportInit)
{
// Start at center of grid
panelOffset.x = (windowSize.x - gridWidthScaled) / 2.0f / panelScale;
panelOffset.y = (windowSize.y - gridHeightScaled) / 2.0f / panelScale;
}
panelOffset.x = std::clamp(panelOffset.x, minOffsetX / panelScale, maxOffsetX / panelScale);
panelOffset.y = std::clamp(panelOffset.y, minOffsetY / panelScale, maxOffsetY / panelScale);
}
viewportInit = true;
return viewportChanged;
}
void drawSettings() final
{
ImGui::SetWindowFontScale(displayPresets.settingsFontScale);
if (ImGui::Button("Save Settings"))
{
saveSettings(Settings{displaySettings, controlSettings}, settingsSavePath);
}
ImGui::Separator();
ImGui::Text("Display:");
ImGui::InputFloat("Global Scale", &displaySettings.globalScale);
ImGui::InputFloat("Numbers Image Scale", &displaySettings.imageScale);
ImGui::InputFloat("Numbers Grid Spacing", &displaySettings.gridSpacing);
ImGui::InputFloat("Mouse Scale Radius", &displaySettings.mouseScaleRadius);
ImGui::InputFloat("Mouse Scale Multiplier", &displaySettings.mouseScaleMultiplier);
ImGui::Text("Noise:");
ImGui::InputFloat("Noise Speed", &displaySettings.noiseSpeed);
ImGui::InputFloat("Noise Scale", &displaySettings.noiseScale);
ImGui::InputFloat("Noise Scale Offset", &displaySettings.noiseScaleOffset);
ImGui::Text("Controls:");
ImGui::InputFloat("Arrow Sensitivity", &controlSettings.arrowSensitivity);
ImGui::InputFloat("Zoom Sensitivity", &controlSettings.zoomSensitivity);
ImGui::Separator();
ImGui::Text("Debug:");
ImGui::Checkbox("revealMap", &revealMap);
}
bool updateDisplaySettings(PresetDisplaySettings &settings, float globalScale)
{
auto viewportSize = ImGui::GetMainViewport()->Size;
if (lastViewportSize.x == viewportSize.x && lastViewportSize.y == viewportSize.y && lastGlobalScale == globalScale)
{
return false;
}
float displaySizeScalePrev = lastViewportSize.x/1280.f * lastGlobalScale;
float displaySizeScale = viewportSize.x/1280.f * globalScale;
float newScale = displaySizeScale / displaySizeScalePrev;
settings.numberWindowBufferTop *= newScale;
settings.numberWindowBufferBottom *= newScale;
settings.headerImageScale *= newScale;
settings.headerImageOffsetX *= newScale;
settings.headerBoxBufferX *= newScale;
settings.headerBoxBufferY *= newScale;
settings.binImageScale *= newScale;
settings.binOffset *= newScale;
settings.binPercentBarOffset *= newScale;
settings.fontSize *= newScale;
settings.settingsFontScale *= newScale;
settings.lineGraphicsSpacing *= newScale;
settings.lineThickness *= newScale;
lastViewportSize = viewportSize;
lastGlobalScale = globalScale;
return true;
}
// Helpers
float getScaleFromCursor(const ImVec2& globalNumberPos, const ImVec2& mousePos) const
{
float distX = mousePos.x - globalNumberPos.x;
float distY = mousePos.y - globalNumberPos.y;
float distance = sqrt(distX * distX + distY * distY);
if (distance < displaySettings.mouseScaleRadius)
{
return 1.0f + (displaySettings.mouseScaleRadius - distance) / displaySettings.mouseScaleRadius * displaySettings.mouseScaleMultiplier;
}
return 1.0f;
}
int gridSize = 100;
std::shared_ptr<ImageDisplay> imageDisplay;
std::shared_ptr<NumberGrid> numberGrid;
ImFont* font;
ImVec2 panelOffset = ImVec2(0,0);
float panelScale = 0.3f;
std::string settingsSavePath = "./settings.json";
DisplaySettings displaySettings;
ControlSettings controlSettings;
PresetDisplaySettings displayPresets;
siv::PerlinNoise perlin{ 555 };
int t = 0;
// Debug options
bool revealMap = false;
struct Bin
{
int id;
ImVec2 pos = ImVec2(0, 0);
int badGroupsRefined = 0;
int maxBadGroups = 0;
ImVec2 updatePos(const ImVec2 &windowSize, const ImVec2 &windowPos, float offsetY)
{
pos = ImVec2(windowPos.x + (windowSize.x / 6)*id, windowPos.y + windowSize.y - offsetY);
return pos;
}
};
std::array<Bin, 5> bins = {Bin{1}, Bin{2}, Bin{3}, Bin{4}, Bin{5}};
ImVec2 lastViewportSize = ImVec2(1280, 720);
float lastGlobalScale = 1.f;
};
std::shared_ptr<NumbersPanel> createNumbersPanel(const std::shared_ptr<ImageDisplay>& imageDisplay)
{
return std::make_shared<NumbersPanelImpl>(imageDisplay);
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <memory>
class ImageDisplay;
class NumbersPanel {
public:
virtual void init() = 0;
virtual void update() = 0;
virtual void drawNumbersPanel() = 0;
virtual void triggerLoadAnimation() = 0;
virtual void drawSettings() = 0;
virtual ~NumbersPanel() = default;
};
std::shared_ptr<NumbersPanel> createNumbersPanel(const std::shared_ptr<ImageDisplay>& imageDisplay);

132
src/UI/Widgets/Settings.h Normal file
View File

@ -0,0 +1,132 @@
#pragma once
#include <fstream>
#include <iostream>
#include <optional>
#include <json.hpp>
struct PresetDisplaySettings
{
float numberWindowBufferTop = 125.f;
float numberWindowBufferBottom = 115.f;
float headerImageScale = 0.25f;
float headerImageOffsetX = 50.f;
float headerBoxBufferX = 55.f;
float headerBoxBufferY = 37.f;
float binImageScale = 0.75f;
float binOffset = 40.f;
float binPercentBarOffset = 50.f;
float fontSize = 25.f;
float settingsFontScale = 0.3f;
float lineGraphicsSpacing = 10.f;
float lineThickness = 5.f;
};
// Helpers
inline std::optional<nlohmann::json> loadSettingsFromJson(const std::string& jsonPath)
{
try {
std::ifstream file(jsonPath);
if (!file.is_open()) {
std::cerr << "Error: Cannot open settings file: " << jsonPath << std::endl;
return std::nullopt;
}
nlohmann::json json;
file >> json;
return json;
} catch (const std::exception& e) {
std::cerr << "Error loading json: " << e.what() << std::endl;
}
return std::nullopt;
}
inline void saveSettingsJson(const nlohmann::json& settingsJson, const std::string& jsonPath)
{
try {
std::ofstream file(jsonPath);
if (!file.is_open()) {
std::cerr << "Error: Cannot open file for writing: " << jsonPath << std::endl;
return;
}
file << settingsJson.dump(4);
file.flush();
file.close();
std::cout << "Settings saved to disk." << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Error saving settings: " << e.what() << std::endl;
}
}
// Numbers Panel settings
struct DisplaySettings
{
float globalScale = 1.f;
float imageScale = 0.75f;
float gridSpacing = 200.f;
float mouseScaleRadius = 100.f;
float mouseScaleMultiplier = 2.f;
float noiseSpeed = 0.004f;
float noiseScale = 1.f;
float noiseScaleOffset = 15;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(DisplaySettings,
globalScale,
imageScale,
gridSpacing,
mouseScaleRadius,
mouseScaleMultiplier,
noiseSpeed,
noiseScale,
noiseScaleOffset
);
};
struct ControlSettings
{
float arrowSensitivity = 25.f;
float zoomSensitivity = 0.1f;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(ControlSettings, arrowSensitivity, zoomSensitivity);
};
struct Settings
{
DisplaySettings displaySettings;
ControlSettings controlSettings;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Settings, displaySettings, controlSettings);
};
inline std::optional<Settings> loadSettings(const std::string& jsonPath)
{
try
{
if (auto json = loadSettingsFromJson(jsonPath))
{
return json->get<Settings>();
}
} catch (const std::exception& e)
{
std::cerr << "Error loading settings: " << e.what() << std::endl;
}
return std::nullopt;
}
inline void saveSettings(const Settings& settings, const std::string& jsonPath)
{
try
{
nlohmann::json json = settings;
saveSettingsJson(json, jsonPath);
} catch (const std::exception& e)
{
std::cerr << "Error saving settings: " << e.what() << std::endl;
}
}

91
src/main.cpp Normal file
View File

@ -0,0 +1,91 @@
#include "imgui.h"
#include "backends/imgui_impl_glfw.h"
#include <GLFW/glfw3.h>
#include <imgui_impl_opengl3.h>
#include <iostream>
#include "UI/UIManager.h"
void glfw_error_callback(int error, const char* description) {
std::cerr << "GLFW Error (" << error << "): " << description << std::endl;
}
int main(int argc, char** argv) {
// Set error callback
glfwSetErrorCallback(glfw_error_callback);
// Initialize GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW!" << std::endl;
return -1;
}
bool fullscreen = false;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--full-screen") == 0) {
fullscreen = true;
break;
}
}
GLFWwindow* window;
if (fullscreen)
{
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);
window = glfwCreateWindow(mode->width, mode->height, "MDR Severance", primaryMonitor, nullptr);
} else
{
window = glfwCreateWindow(1920, 1080, "MDR Severance", nullptr, nullptr);
}
if (!window) {
std::cerr << "Failed to create GLFW window!" << std::endl;
glfwTerminate();
return -1;
}
// Set the OpenGL context
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
std::shared_ptr<UIManager> uiManager = createUIManager();
uiManager->init();
while (!glfwWindowShouldClose(window)) {
// Poll events
glfwPollEvents();
// Close application with 'ESCAPE' key
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Start ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// Draw
uiManager->draw();
// Update
uiManager->update();
// Render ImGui
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// Swap buffers
glfwSwapBuffers(window);
}
// Cleanup
uiManager->cleanup();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}