Introduce file picker and GUI configuration with enhancements.

- Add visual file picker for GUI with toggle support.
- Introduce `GUIConfig` class for loading GUI settings from configuration file.
- Refactor window initialization to support dynamic sizing based on configuration.
- Add macOS-specific handling for fullscreen behavior.
- Improve header inclusion order and minor code cleanup.
This commit is contained in:
2025-11-30 18:35:12 -08:00
parent ba9bd4a27d
commit e4cd4877cc
51 changed files with 592 additions and 94 deletions

View File

@@ -1,29 +1,44 @@
#include "GUIRenderer.h"
#include "Editor.h"
#include "Buffer.h"
#include "Command.h"
#include <cmath>
#include <cstdio>
#include <filesystem>
#include <limits>
#include <string>
#include <imgui.h>
#include <cstdio>
#include <string>
#include <filesystem>
#include <cmath>
#include <limits>
#include "GUIRenderer.h"
#include "Buffer.h"
#include "Command.h"
#include "Editor.h"
// Version string expected to be provided by build system as KTE_VERSION_STR
#ifndef KTE_VERSION_STR
# define KTE_VERSION_STR "dev"
#endif
// ImGui compatibility: some bundled ImGui versions (or builds without docking)
// don't define ImGuiWindowFlags_NoDocking. Treat it as 0 in that case.
#ifndef ImGuiWindowFlags_NoDocking
# define ImGuiWindowFlags_NoDocking 0
#endif
void
GUIRenderer::Draw(Editor &ed)
{
// Make the editor window occupy the entire GUI container/viewport
ImGuiViewport *vp = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(vp->Pos);
ImGui::SetNextWindowSize(vp->Size);
// On HiDPI/Retina, snap to integer pixels to prevent any draw vs hit-test
// mismatches that can appear on the very first maximized frame.
ImVec2 main_pos = vp->Pos;
ImVec2 main_sz = vp->Size;
main_pos.x = std::floor(main_pos.x + 0.5f);
main_pos.y = std::floor(main_pos.y + 0.5f);
main_sz.x = std::floor(main_sz.x + 0.5f);
main_sz.y = std::floor(main_sz.y + 0.5f);
ImGui::SetNextWindowPos(main_pos);
ImGui::SetNextWindowSize(main_sz);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize
@@ -379,4 +394,153 @@ GUIRenderer::Draw(Editor &ed)
ImGui::End();
ImGui::PopStyleVar(3);
// --- Visual File Picker overlay (GUI only) ---
if (ed.FilePickerVisible()) {
// Centered popup-style window that always fits within the current viewport
ImGuiViewport *vp2 = ImGui::GetMainViewport();
// Desired size, min size, and margins
const ImVec2 want(800.0f, 500.0f);
const ImVec2 min_sz(240.0f, 160.0f);
const float margin = 20.0f; // space from viewport edges
// Compute the maximum allowed size (viewport minus margins) and make sure it's not negative
ImVec2 max_sz(std::max(32.0f, vp2->Size.x - 2.0f * margin),
std::max(32.0f, vp2->Size.y - 2.0f * margin));
// Clamp desired size to [min_sz, max_sz]
ImVec2 size(std::min(want.x, max_sz.x), std::min(want.y, max_sz.y));
size.x = std::max(size.x, std::min(min_sz.x, max_sz.x));
size.y = std::max(size.y, std::min(min_sz.y, max_sz.y));
// Center within the viewport using the final size
ImVec2 pos(vp2->Pos.x + std::max(margin, (vp2->Size.x - size.x) * 0.5f),
vp2->Pos.y + std::max(margin, (vp2->Size.y - size.y) * 0.5f));
// On HiDPI displays (macOS Retina), ensure integer pixel alignment to avoid
// potential hit-test vs draw mismatches from sub-pixel positions.
pos.x = std::floor(pos.x + 0.5f);
pos.y = std::floor(pos.y + 0.5f);
size.x = std::floor(size.x + 0.5f);
size.y = std::floor(size.y + 0.5f);
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
ImGui::SetNextWindowSize(size, ImGuiCond_Always);
ImGuiWindowFlags wflags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoDocking;
bool open = true;
if (ImGui::Begin("File Picker", &open, wflags)) {
// Current directory
std::string curdir = ed.FilePickerDir();
if (curdir.empty()) {
try {
curdir = std::filesystem::current_path().string();
} catch (...) {
curdir = ".";
}
ed.SetFilePickerDir(curdir);
}
ImGui::TextUnformatted(curdir.c_str());
ImGui::SameLine();
if (ImGui::Button("Up")) {
try {
std::filesystem::path p(curdir);
if (p.has_parent_path()) {
ed.SetFilePickerDir(p.parent_path().string());
}
} catch (...) {}
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
ed.SetFilePickerVisible(false);
}
ImGui::Separator();
// Header
ImGui::TextUnformatted("Name");
ImGui::Separator();
// Scrollable list
ImGui::BeginChild("picker-list", ImVec2(0, 0), true);
// Build entries: directories first then files, alphabetical
struct Entry {
std::string name;
std::filesystem::path path;
bool is_dir;
};
std::vector<Entry> entries;
entries.reserve(256);
// Optional parent entry
try {
std::filesystem::path base(curdir);
std::error_code ec;
for (auto it = std::filesystem::directory_iterator(base, ec);
!ec && it != std::filesystem::directory_iterator(); it.increment(ec)) {
const auto &p = it->path();
std::string nm;
try {
nm = p.filename().string();
} catch (...) {
continue;
}
if (nm == "." || nm == "..")
continue;
bool is_dir = false;
std::error_code ec2;
is_dir = it->is_directory(ec2);
entries.push_back({nm, p, is_dir});
}
} catch (...) {
// ignore listing errors; show empty
}
std::sort(entries.begin(), entries.end(), [](const Entry &a, const Entry &b) {
if (a.is_dir != b.is_dir)
return a.is_dir && !b.is_dir;
return a.name < b.name;
});
// Draw rows
int idx = 0;
for (const auto &e: entries) {
ImGui::PushID(idx++); // ensure unique/stable IDs even if names repeat
std::string label;
label.reserve(e.name.size() + 4);
if (e.is_dir)
label += "[";
label += e.name;
if (e.is_dir)
label += "]";
// Render selectable row
ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick);
// Activate based strictly on hover + mouse, to avoid any off-by-one due to click routing
if (ImGui::IsItemHovered()) {
if (e.is_dir && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
// Enter directory on double-click
ed.SetFilePickerDir(e.path.string());
} else if (!e.is_dir && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
// Open file on single click
std::string err;
if (!ed.OpenFile(e.path.string(), err)) {
ed.SetStatus(std::string("open: ") + err);
} else {
ed.SetStatus(std::string("Opened: ") + e.name);
}
ed.SetFilePickerVisible(false);
}
}
ImGui::PopID();
}
ImGui::EndChild();
}
ImGui::End();
if (!open) {
ed.SetFilePickerVisible(false);
}
}
}