Files
whetstone_DSL/editor/src/panels/SettingsPanel.h

288 lines
11 KiB
C
Raw Normal View History

#pragma once
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../ThemeEngine.h"
2026-02-09 21:49:12 -07:00
#include <cstdlib>
static void openThemesFolder(const std::string& path) {
#ifdef _WIN32
std::string cmd = "start \"\" \"" + path + "\"";
std::system(cmd.c_str());
#elif __APPLE__
std::string cmd = "open \"" + path + "\"";
std::system(cmd.c_str());
#else
std::string cmd = "xdg-open \"" + path + "\"";
std::system(cmd.c_str());
#endif
}
static void renderSettingsPanel(EditorState& state) {
if (!state.ui.showSettingsPanel) return;
ImGui::Begin("Settings", &state.ui.showSettingsPanel);
ImGui::PushFont(state.uiFont);
bool settingsChanged = false;
bool emacsConfigChanged = false;
2026-02-09 21:35:54 -07:00
bool themeChanged = false;
ImGuiIO& io = ImGui::GetIO();
int fontSize = state.settings.getFontSize();
if (ImGui::SliderInt("Font Size", &fontSize, 12, 24)) {
state.settings.setFontSize(fontSize);
io.FontGlobalScale = fontSize / state.baseFontSize;
settingsChanged = true;
}
int tabSize = state.settings.getTabSize();
const int tabSizes[] = {2, 4, 8};
int tabIndex = 1;
for (int i = 0; i < 3; ++i) {
if (tabSize == tabSizes[i]) tabIndex = i;
}
const char* tabLabels[] = {"2", "4", "8"};
if (ImGui::Combo("Tab Size", &tabIndex, tabLabels, 3)) {
state.settings.setTabSize(tabSizes[tabIndex]);
state.applyTabSizeToBuffers(tabSizes[tabIndex]);
settingsChanged = true;
}
std::vector<std::string> themeNames = ThemeEngine::instance().listThemes();
if (themeNames.empty()) {
2026-02-09 21:44:45 -07:00
themeNames = {"Whetstone Dark", "Whetstone Light"};
}
2026-02-09 21:47:52 -07:00
std::string savedTheme = state.settings.getTheme();
if (savedTheme == "Dark") savedTheme = "Whetstone Dark";
if (savedTheme == "Light") savedTheme = "Whetstone Light";
static std::string selectedTheme;
static std::string previewAnchor;
static bool previewActive = false;
if (selectedTheme.empty()) selectedTheme = savedTheme;
bool selectedExists = false;
for (const auto& name : themeNames) {
if (name == selectedTheme) {
selectedExists = true;
break;
}
}
2026-02-09 21:47:52 -07:00
if (!selectedExists) selectedTheme = savedTheme;
if (ImGui::CollapsingHeader("Themes", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::TextDisabled("Hover to preview, click to select.");
const float cardWidth = 180.0f;
const float cardHeight = 90.0f;
const float padding = 8.0f;
float avail = ImGui::GetContentRegionAvail().x;
int columns = std::max(1, (int)(avail / (cardWidth + padding)));
bool anyHovered = false;
std::string hoveredTheme;
if (ImGui::BeginTable("##themeGallery", columns, ImGuiTableFlags_SizingFixedFit)) {
for (const auto& name : themeNames) {
ImGui::TableNextColumn();
ImGui::PushID(name.c_str());
ImGui::BeginChild("##themeCard", ImVec2(cardWidth, cardHeight), true);
ImGui::TextUnformatted(name.c_str());
if (name == selectedTheme) {
ImGui::SameLine();
ImGui::TextDisabled("Selected");
}
2026-02-09 21:49:12 -07:00
if (name == savedTheme) {
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetWindowPos();
ImVec2 size = ImGui::GetWindowSize();
ImVec2 a(pos.x + size.x - 18.0f, pos.y + 10.0f);
ImVec2 b(pos.x + size.x - 12.0f, pos.y + 16.0f);
ImVec2 c(pos.x + size.x - 4.0f, pos.y + 6.0f);
ImU32 color = IM_COL32(80, 200, 120, 255);
draw->AddLine(a, b, color, 2.0f);
draw->AddLine(b, c, color, 2.0f);
}
2026-02-09 21:47:52 -07:00
ImU32 swatch[4] = {
IM_COL32(40, 40, 40, 255),
IM_COL32(197, 134, 192, 255),
IM_COL32(206, 145, 120, 255),
IM_COL32(106, 153, 85, 255)
};
ThemeEngine::instance().getSwatch(name, swatch);
ImVec2 start = ImGui::GetCursorScreenPos();
ImDrawList* draw = ImGui::GetWindowDrawList();
float swatchWidth = (cardWidth - 2.0f * padding) / 4.0f;
float swatchHeight = 24.0f;
for (int i = 0; i < 4; ++i) {
ImVec2 a(start.x + i * swatchWidth, start.y);
ImVec2 b(start.x + (i + 1) * swatchWidth - 2.0f, start.y + swatchHeight);
draw->AddRectFilled(a, b, swatch[i], 3.0f);
}
ImGui::Dummy(ImVec2(0.0f, swatchHeight + 4.0f));
ImGui::EndChild();
if (ImGui::IsItemHovered()) {
anyHovered = true;
hoveredTheme = name;
}
if (ImGui::IsItemClicked()) {
selectedTheme = name;
}
ImGui::PopID();
}
ImGui::EndTable();
}
if (anyHovered) {
if (!previewActive) {
previewAnchor = savedTheme;
previewActive = true;
}
if (!hoveredTheme.empty() &&
ThemeEngine::instance().currentThemeName() != hoveredTheme) {
ThemeEngine::instance().applyTheme(hoveredTheme);
}
} else if (previewActive) {
ThemeEngine::instance().applyTheme(previewAnchor);
previewActive = false;
}
ImGui::Separator();
bool canApply = !selectedTheme.empty() && selectedTheme != savedTheme;
if (!canApply) ImGui::BeginDisabled();
if (ImGui::Button("Apply")) {
state.settings.setTheme(selectedTheme);
if (!ThemeEngine::instance().applyTheme(selectedTheme)) {
if (selectedTheme.find("Light") != std::string::npos)
SetupVSCodeLightTheme();
else
SetupVSCodeDarkTheme();
}
previewActive = false;
previewAnchor = selectedTheme;
settingsChanged = true;
themeChanged = true;
savedTheme = selectedTheme;
}
if (!canApply) ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button("Reset")) {
selectedTheme = savedTheme;
ThemeEngine::instance().applyTheme(savedTheme);
previewActive = false;
}
2026-02-09 21:49:12 -07:00
ImGui::SameLine();
if (ImGui::Button("Open Themes Folder")) {
openThemesFolder(ThemeEngine::userThemeDirectory());
}
}
bool telemetryOptIn = state.settings.getTelemetryOptIn();
if (ImGui::Checkbox("Telemetry (opt-in)", &telemetryOptIn)) {
state.settings.setTelemetryOptIn(telemetryOptIn);
state.telemetry.setOptIn(telemetryOptIn);
if (telemetryOptIn) {
state.telemetry.recordEvent("telemetry_opt_in", json::object());
}
settingsChanged = true;
}
char updateUrlBuf[256];
std::snprintf(updateUrlBuf, sizeof(updateUrlBuf), "%s",
state.settings.getUpdateUrl().c_str());
if (ImGui::InputText("Update URL", updateUrlBuf, sizeof(updateUrlBuf))) {
state.settings.setUpdateUrl(updateUrlBuf);
settingsChanged = true;
}
if (ImGui::Button("Check for Updates")) {
UpdateChecker checker;
auto info = checker.check(state.settings.getUpdateUrl());
if (info.available) {
2026-02-09 21:26:10 -07:00
state.notify(NotificationLevel::Info,
"[update] Available: " + info.version + " (" + info.url + ")");
} else {
2026-02-09 21:26:10 -07:00
state.notify(NotificationLevel::Info,
"[update] No update info (offline/stub).");
}
}
int autoSave = state.settings.getAutoSaveSeconds();
if (ImGui::InputInt("Auto-save (sec)", &autoSave)) {
autoSave = std::max(0, autoSave);
state.settings.setAutoSaveSeconds(autoSave);
settingsChanged = true;
}
bool showMinimap = state.ui.showMinimap;
if (ImGui::Checkbox("Show Minimap", &showMinimap)) {
state.ui.showMinimap = showMinimap;
settingsChanged = true;
}
bool showLineNumbers = state.ui.showLineNumbers;
if (ImGui::Checkbox("Show Line Numbers", &showLineNumbers)) {
state.ui.showLineNumbers = showLineNumbers;
settingsChanged = true;
}
LayoutPreset preset = state.ui.layoutPreset;
int presetIndex = 0;
if (preset == LayoutPreset::Emacs) presetIndex = 1;
else if (preset == LayoutPreset::JetBrains) presetIndex = 2;
const char* presetLabels[] = {"VSCode", "Emacs", "JetBrains"};
if (ImGui::Combo("Layout Preset", &presetIndex, presetLabels, 3)) {
state.ui.layoutPreset = (presetIndex == 1) ? LayoutPreset::Emacs :
(presetIndex == 2) ? LayoutPreset::JetBrains : LayoutPreset::VSCode;
settingsChanged = true;
}
int keyProfileIndex = 0;
if (state.keys.getProfile() == KeybindingProfile::JetBrains) keyProfileIndex = 1;
else if (state.keys.getProfile() == KeybindingProfile::Emacs) keyProfileIndex = 2;
const char* keyProfiles[] = {"VSCode", "JetBrains", "Emacs"};
if (ImGui::Combo("Keybindings", &keyProfileIndex, keyProfiles, 3)) {
KeybindingProfile profile = KeybindingProfile::VSCode;
if (keyProfileIndex == 1) profile = KeybindingProfile::JetBrains;
else if (keyProfileIndex == 2) profile = KeybindingProfile::Emacs;
state.keys.setProfile(profile);
state.registerCommands();
settingsChanged = true;
}
ImGui::Separator();
ImGui::TextUnformatted("Emacs Config");
ImGui::SetNextItemWidth(320);
if (InputTextStr("Config Path", &state.settings.getEmacsConfigPathMutable())) {
settingsChanged = true;
emacsConfigChanged = true;
}
if (ImGui::CollapsingHeader("LSP Servers", ImGuiTreeNodeFlags_DefaultOpen)) {
for (auto& cfg : state.settings.getLSPServersMutable()) {
ImGui::PushID(cfg.language.c_str());
ImGui::Checkbox("Enabled", &cfg.enabled);
ImGui::SameLine();
ImGui::Text("%s", cfg.language.c_str());
ImGui::SetNextItemWidth(320);
if (InputTextStr("Path", &cfg.path)) settingsChanged = true;
ImGui::SetNextItemWidth(320);
if (InputTextStr("Args", &cfg.argsLine)) {
state.settings.syncArgs(cfg);
settingsChanged = true;
}
ImGui::Separator();
ImGui::PopID();
}
}
if (settingsChanged) {
state.saveSettingsToDisk();
2026-02-09 21:35:54 -07:00
state.events.publish(UIEventType::SettingsChanged, {}, {}, ImGui::GetTime());
}
if (emacsConfigChanged) {
state.startEmacsDaemonFromSettings();
}
2026-02-09 21:35:54 -07:00
if (themeChanged) {
state.events.publish(UIEventType::ThemeChanged, {}, {}, ImGui::GetTime());
}
ImGui::PopFont();
ImGui::End();
}