Remove the Qt frontend

The Qt GUI was unused: out of make-app-release since March, not built by
the gate, and its app bundle could not start (no platform plugin). Every
renderer and input change had to be made in it separately. kge is the
ImGui frontend only.

Removes QtFrontend/QtInputHandler/QtRenderer, the KTE_USE_QT option and
its #if branches (Command.cc, GUITheme.h, main.cc), the Nix qt package
and dev shell, the commented-out Qt release steps, the Qt frontend plan,
and Qt mentions in AGENTS.md, DEVELOPER_GUIDE.md, REWRITE.md and code
comments. Git history keeps the code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 12:32:58 -07:00
co-authored by Claude Opus 5.5
parent 082f4facd3
commit 2125583407
19 changed files with 56 additions and 2230 deletions
+4 -6
View File
@@ -1,9 +1,9 @@
# kte # kte
kte (Kyle's Text Editor) is a C++20 text editor with a terminal-first design (ncurses) and optional GUI frontends (ImGui via SDL2/OpenGL/Freetype, or Qt6); it uses a WordStar/VDE-style command model. Standards: none. The Metacircular service standards do not apply here. kte (Kyle's Text Editor) is a C++20 text editor with a terminal-first design (ncurses) and an optional ImGui GUI frontend (SDL2/OpenGL/Freetype); it uses a WordStar/VDE-style command model. Standards: none. The Metacircular service standards do not apply here.
## Bootstrap (run once per checkout, idempotent) ## Bootstrap (run once per checkout, idempotent)
nix develop (default shell; also provides `terminal` and `qt` devshells) or system SDL2/Freetype/ncurses if not using nix. nix develop (default shell; also provides a `terminal` devshell) or system SDL2/Freetype/ncurses if not using nix.
## Gate (run before saying "done"; must exit 0) ## Gate (run before saying "done"; must exit 0)
make gate # what it runs: configure (cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_GUI=ON -DBUILD_TESTS=ON) -> build (cmake --build build) -> test (./build/kte_tests) make gate # what it runs: configure (cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_GUI=ON -DBUILD_TESTS=ON) -> build (cmake --build build) -> test (./build/kte_tests)
@@ -12,7 +12,7 @@ Also run clang-tidy on changed files via build/compile_commands.json (not part o
## Run ## Run
Terminal editor: `./build/kte <file>` (always built). Terminal editor: `./build/kte <file>` (always built).
GUI editor: `./build/kge <file>` (built when `-DBUILD_GUI=ON`; requires SDL2/OpenGL/Freetype, or Qt6 with `-DKTE_USE_QT=ON`). GUI editor: `./build/kge <file>` (built when `-DBUILD_GUI=ON`; requires SDL2/OpenGL/Freetype).
Docker (cross-platform Linux testing): `docker build -t kte-linux . && docker run --rm -v "$(pwd):/kte" kte-linux`. Docker (cross-platform Linux testing): `docker build -t kte-linux . && docker run --rm -v "$(pwd):/kte" kte-linux`.
## Sensors the harness watches ## Sensors the harness watches
@@ -42,7 +42,7 @@ None: kte builds and runs locally (or in the Docker/CI Linux image); it has no r
Three-layer design with strict frontend independence: Three-layer design with strict frontend independence:
``` ```
Frontend Layer (Terminal / ImGui / Qt / Test) Frontend Layer (Terminal / ImGui / Test)
InputHandler.h, Renderer.h, Frontend.h interfaces InputHandler.h, Renderer.h, Frontend.h interfaces
↓ ↓
Command Layer Command Layer
@@ -69,7 +69,6 @@ Core Model Layer
Each frontend implements three interfaces (`Frontend.h`, `InputHandler.h`, `Renderer.h`): Each frontend implements three interfaces (`Frontend.h`, `InputHandler.h`, `Renderer.h`):
- **Terminal**: ncurses-based (always built) - **Terminal**: ncurses-based (always built)
- **ImGui**: SDL2+OpenGL+Freetype (built with `-DBUILD_GUI=ON`) - **ImGui**: SDL2+OpenGL+Freetype (built with `-DBUILD_GUI=ON`)
- **Qt**: Qt6 (built with `-DBUILD_GUI=ON -DKTE_USE_QT=ON`)
- **Test**: Programmatic frontend for testing (always built, no UI deps) - **Test**: Programmatic frontend for testing (always built, no UI deps)
### Key CMake Options ### Key CMake Options
@@ -77,7 +76,6 @@ Each frontend implements three interfaces (`Frontend.h`, `InputHandler.h`, `Rend
| Flag | Default | Purpose | | Flag | Default | Purpose |
|------|---------|---------| |------|---------|---------|
| `BUILD_GUI` | ON | Build `kge` (ImGui GUI) | | `BUILD_GUI` | ON | Build `kge` (ImGui GUI) |
| `KTE_USE_QT` | OFF | Use Qt6 instead of ImGui for GUI |
| `BUILD_TESTS` | ON | Build test suite | | `BUILD_TESTS` | ON | Build test suite |
| `ENABLE_ASAN` | OFF | AddressSanitizer | | `ENABLE_ASAN` | OFF | AddressSanitizer |
| `KTE_STATIC_LINK` | OFF | Static linking (Linux only) | | `KTE_STATIC_LINK` | OFF | Static linking (Linux only) |
+3 -31
View File
@@ -9,7 +9,6 @@ set(KTE_VERSION "1.12.0")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default. # Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available. # Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
set(BUILD_GUI ON CACHE BOOL "Enable building the graphical version.") set(BUILD_GUI ON CACHE BOOL "Enable building the graphical version.")
set(KTE_USE_QT OFF CACHE BOOL "Build the QT frontend instead of ImGui.")
set(BUILD_TESTS ON CACHE BOOL "Enable building test programs.") set(BUILD_TESTS ON CACHE BOOL "Enable building test programs.")
set(KTE_FONT_SIZE "18.0" CACHE STRING "Default font size for GUI") set(KTE_FONT_SIZE "18.0" CACHE STRING "Default font size for GUI")
option(KTE_UNDO_DEBUG "Enable undo instrumentation logs" OFF) option(KTE_UNDO_DEBUG "Enable undo instrumentation logs" OFF)
@@ -163,17 +162,6 @@ if (BUILD_GUI)
set(GUI_SOURCES set(GUI_SOURCES
GUIConfig.cc GUIConfig.cc
) )
if (KTE_USE_QT)
find_package(Qt6 COMPONENTS Widgets REQUIRED)
set(GUI_SOURCES
${GUI_SOURCES}
QtFrontend.cc
QtInputHandler.cc
QtRenderer.cc
)
# Expose preprocessor switch so sources can exclude ImGui-specific code
add_compile_definitions(KTE_USE_QT)
else ()
set(GUI_SOURCES set(GUI_SOURCES
${GUI_SOURCES} ${GUI_SOURCES}
${FONT_SOURCES} ${FONT_SOURCES}
@@ -181,7 +169,6 @@ if (BUILD_GUI)
ImGuiInputHandler.cc ImGuiInputHandler.cc
ImGuiRenderer.cc ImGuiRenderer.cc
) )
endif ()
endif () endif ()
set(COMMON_SOURCES set(COMMON_SOURCES
@@ -311,14 +298,6 @@ if (BUILD_GUI)
GUIConfig.h GUIConfig.h
) )
if (KTE_USE_QT)
set(GUI_HEADERS
${GUI_HEADERS}
QtFrontend.h
QtInputHandler.h
QtRenderer.h
)
else ()
set(GUI_HEADERS set(GUI_HEADERS
${GUI_HEADERS} ${GUI_HEADERS}
${THEME_HEADERS} ${THEME_HEADERS}
@@ -328,7 +307,6 @@ if (BUILD_GUI)
ImGuiRenderer.h ImGuiRenderer.h
fonts/BerkeleyMono.h fonts/BerkeleyMono.h
) )
endif ()
endif () endif ()
# kte (terminal-first) executable # kte (terminal-first) executable
@@ -473,17 +451,10 @@ if (BUILD_GUI)
) )
target_compile_definitions(kge PRIVATE KTE_BUILD_GUI=1 KTE_DEFAULT_GUI=1 KTE_FONT_SIZE=${KTE_FONT_SIZE}) target_compile_definitions(kge PRIVATE KTE_BUILD_GUI=1 KTE_DEFAULT_GUI=1 KTE_FONT_SIZE=${KTE_FONT_SIZE})
if (KTE_USE_QT)
target_compile_definitions(kge PRIVATE KTE_USE_QT=1)
endif ()
if (KTE_UNDO_DEBUG) if (KTE_UNDO_DEBUG)
target_compile_definitions(kge PRIVATE KTE_UNDO_DEBUG=1) target_compile_definitions(kge PRIVATE KTE_UNDO_DEBUG=1)
endif () endif ()
if (KTE_USE_QT)
target_link_libraries(kge ${CURSES_LIBRARIES} Qt6::Widgets)
else ()
target_link_libraries(kge ${CURSES_LIBRARIES} imgui) target_link_libraries(kge ${CURSES_LIBRARIES} imgui)
endif ()
# Static linking on Linux only (macOS does not support static linking of system libraries) # Static linking on Linux only (macOS does not support static linking of system libraries)
if (NOT APPLE AND KTE_STATIC_LINK) if (NOT APPLE AND KTE_STATIC_LINK)
@@ -565,7 +536,8 @@ if (BUILD_GUI)
# Optional post-build bundle fixup (can also be run from scripts). # Optional post-build bundle fixup (can also be run from scripts).
# This provides a CMake target to run BundleUtilities' fixup_bundle on the # This provides a CMake target to run BundleUtilities' fixup_bundle on the
# built app, useful after macdeployqt to ensure non-Qt dylibs are internalized. # built app, copying non-system dylibs (e.g. Homebrew SDL2/SDL3, freetype,
# pcre2) into the bundle so it runs on machines without them installed.
if (APPLE AND TARGET kge) if (APPLE AND TARGET kge)
get_target_property(IS_BUNDLE kge MACOSX_BUNDLE) get_target_property(IS_BUNDLE kge MACOSX_BUNDLE)
if (IS_BUNDLE) if (IS_BUNDLE)
@@ -573,7 +545,7 @@ if (BUILD_GUI)
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-DAPP_BUNDLE=${CMAKE_CURRENT_BINARY_DIR}/$<TARGET_PROPERTY:kge,MACOSX_BUNDLE_BUNDLE_NAME>.app -DAPP_BUNDLE=${CMAKE_CURRENT_BINARY_DIR}/$<TARGET_PROPERTY:kge,MACOSX_BUNDLE_BUNDLE_NAME>.app
-P ${CMAKE_CURRENT_LIST_DIR}/cmake/fix_bundle.cmake -P ${CMAKE_CURRENT_LIST_DIR}/cmake/fix_bundle.cmake
COMMENT "Running fixup_bundle on kge.app to internalize non-Qt dylibs" COMMENT "Running fixup_bundle on kge.app to internalize non-system dylibs"
VERBATIM) VERBATIM)
add_dependencies(kge_fixup_bundle kge) add_dependencies(kge_fixup_bundle kge)
endif () endif ()
+11 -74
View File
@@ -24,27 +24,21 @@
#include "syntax/CppHighlighter.h" #include "syntax/CppHighlighter.h"
#ifdef KTE_BUILD_GUI #ifdef KTE_BUILD_GUI
# include "GUITheme.h" # include "GUITheme.h"
# if !defined(KTE_USE_QT)
# include "fonts/FontRegistry.h" # include "fonts/FontRegistry.h"
# include "imgui.h" # include "imgui.h"
# endif
# if defined(KTE_USE_QT)
# include <QFontDatabase>
# include <QStringList>
# endif
#endif #endif
// Define cross-frontend theme change flags declared in GUITheme.h // Define cross-frontend theme change flags declared in GUITheme.h
namespace kte { namespace kte {
bool gThemeChangePending = false; bool gThemeChangePending = false;
std::string gThemeChangeRequest; std::string gThemeChangeRequest;
// Qt font change globals // Font change request globals
bool gFontChangePending = false; bool gFontChangePending = false;
std::string gFontFamilyRequest; std::string gFontFamilyRequest;
float gFontSizeRequest = 0.0f; float gFontSizeRequest = 0.0f;
std::string gCurrentFontFamily; std::string gCurrentFontFamily;
float gCurrentFontSize = 0.0f; float gCurrentFontSize = 0.0f;
// Request Qt visual font dialog // Request a visual font dialog
bool gFontDialogRequested = false; bool gFontDialogRequested = false;
} }
@@ -1749,7 +1743,7 @@ cmd_set_option(CommandContext &ctx)
// GUI theme cycling commands (available in GUI build; ImGui-only for now) // GUI theme cycling commands (available in GUI build; ImGui-only for now)
#if defined(KTE_BUILD_GUI) && !defined(KTE_USE_QT) #if defined(KTE_BUILD_GUI)
static bool static bool
cmd_theme_next(CommandContext &ctx) cmd_theme_next(CommandContext &ctx)
{ {
@@ -1785,7 +1779,7 @@ cmd_theme_prev(CommandContext &ctx)
// Theme set by name command // Theme set by name command
#if defined(KTE_BUILD_GUI) && !defined(KTE_USE_QT) #if defined(KTE_BUILD_GUI)
static bool static bool
cmd_theme_set_by_name(const CommandContext &ctx) cmd_theme_set_by_name(const CommandContext &ctx)
{ {
@@ -1829,41 +1823,15 @@ cmd_theme_set_by_name(const CommandContext &ctx)
static bool static bool
cmd_theme_set_by_name(CommandContext &ctx) cmd_theme_set_by_name(CommandContext &ctx)
{ {
# if defined(KTE_BUILD_GUI) && defined(KTE_USE_QT)
// Qt GUI build: schedule theme change for frontend
std::string name = ctx.arg;
// trim spaces
auto ltrim = [](std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
};
auto rtrim = [](std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
};
ltrim(name);
rtrim(name);
if (name.empty()) {
ctx.editor.SetStatus("theme: provide a name (e.g., nord, solarized-dark, gruvbox-light, eink)");
return true;
}
kte::gThemeChangeRequest = name;
kte::gThemeChangePending = true;
ctx.editor.SetStatus(std::string("Theme requested: ") + name);
return true;
# else
(void) ctx; (void) ctx;
// No-op in terminal build // No-op in terminal build
return true; return true;
# endif
} }
#endif #endif
// Font set by name (GUI) // Font set by name (GUI)
#if defined(KTE_BUILD_GUI) && !defined(KTE_USE_QT) #if defined(KTE_BUILD_GUI)
static bool static bool
cmd_font_set_by_name(const CommandContext &ctx) cmd_font_set_by_name(const CommandContext &ctx)
{ {
@@ -1916,7 +1884,7 @@ cmd_font_set_by_name(const CommandContext &ctx)
static bool static bool
cmd_font_set_by_name(CommandContext &ctx) cmd_font_set_by_name(CommandContext &ctx)
{ {
// Qt build: queue font family change // Non-GUI build: record the requested font family
std::string name = ctx.arg; std::string name = ctx.arg;
// trim // trim
auto ltrim = [](std::string &s) { auto ltrim = [](std::string &s) {
@@ -1947,7 +1915,7 @@ cmd_font_set_by_name(CommandContext &ctx)
// Font size set (GUI, ImGui-only for now) // Font size set (GUI, ImGui-only for now)
#if defined(KTE_BUILD_GUI) && !defined(KTE_USE_QT) #if defined(KTE_BUILD_GUI)
static bool static bool
cmd_font_set_size(const CommandContext &ctx) cmd_font_set_size(const CommandContext &ctx)
{ {
@@ -2006,7 +1974,7 @@ cmd_font_set_size(const CommandContext &ctx)
static bool static bool
cmd_font_set_size(CommandContext &ctx) cmd_font_set_size(CommandContext &ctx)
{ {
// Qt build: parse size and queue change // Non-GUI build: parse size and record the request
std::string a = ctx.arg; std::string a = ctx.arg;
auto ltrim = [](std::string &s) { auto ltrim = [](std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
@@ -2081,7 +2049,7 @@ cmd_toggle_edit_mode(const CommandContext &ctx)
// Background set command (GUI, ImGui-only for now) // Background set command (GUI, ImGui-only for now)
#if defined(KTE_BUILD_GUI) && !defined(KTE_USE_QT) #if defined(KTE_BUILD_GUI)
static bool static bool
cmd_background_set(const CommandContext &ctx) cmd_background_set(const CommandContext &ctx)
{ {
@@ -2254,7 +2222,7 @@ cmd_visual_file_picker_toggle(const CommandContext &ctx)
} }
// GUI: request visual font picker (Qt frontend will consume flag) // GUI: request visual font picker (sets a flag for the frontend)
static bool static bool
cmd_visual_font_picker_toggle(const CommandContext &ctx) cmd_visual_font_picker_toggle(const CommandContext &ctx)
{ {
@@ -2570,7 +2538,6 @@ cmd_insert_text(CommandContext &ctx)
// Only special-case argument completion for certain commands // Only special-case argument completion for certain commands
if (cmd == "theme") { if (cmd == "theme") {
#if defined(KTE_BUILD_GUI) #if defined(KTE_BUILD_GUI)
# if !defined(KTE_USE_QT)
std::vector<std::string> cands; std::vector<std::string> cands;
const auto &reg = kte::ThemeRegistry(); const auto &reg = kte::ThemeRegistry();
for (const auto &t: reg) { for (const auto &t: reg) {
@@ -2578,23 +2545,6 @@ cmd_insert_text(CommandContext &ctx)
if (argprefix.empty() || n.rfind(argprefix, 0) == 0) if (argprefix.empty() || n.rfind(argprefix, 0) == 0)
cands.push_back(n); cands.push_back(n);
} }
# else
// Qt: offer known theme names handled by ApplyQtThemeByName
static const char *qt_themes[] = {
"nord",
"solarized-dark",
"solarized-light",
"gruvbox-dark",
"gruvbox-light",
"eink"
};
std::vector<std::string> cands;
for (const char *t: qt_themes) {
std::string n(t);
if (argprefix.empty() || n.rfind(argprefix, 0) == 0)
cands.push_back(n);
}
# endif
if (cands.empty()) { if (cands.empty()) {
// no change // no change
} else if (cands.size() == 1) { } else if (cands.size() == 1) {
@@ -2626,20 +2576,7 @@ cmd_insert_text(CommandContext &ctx)
[](unsigned char c) { [](unsigned char c) {
return (char) std::tolower(c); return (char) std::tolower(c);
}); });
#if defined(KTE_BUILD_GUI) && defined(KTE_USE_QT) #if defined(KTE_BUILD_GUI)
// Qt: complete against system font families
QStringList fams = QFontDatabase::families();
for (const auto &fam: fams) {
std::string n = fam.toStdString();
std::string nlower = n;
std::transform(nlower.begin(), nlower.end(), nlower.begin(),
[](unsigned char c) {
return (char) std::tolower(c);
});
if (apfx_lower.empty() || nlower.rfind(apfx_lower, 0) == 0)
cands.push_back(n);
}
#elif defined(KTE_BUILD_GUI)
// ImGui: complete against embedded font registry // ImGui: complete against embedded font registry
for (const auto &n : kte::Fonts::FontRegistry::Instance().FontNames()) { for (const auto &n : kte::Fonts::FontRegistry::Instance().FontNames()) {
if (apfx_lower.empty() || n.rfind(apfx_lower, 0) == 0) if (apfx_lower.empty() || n.rfind(apfx_lower, 0) == 0)
+4 -282
View File
@@ -12,292 +12,16 @@
namespace kte { namespace kte {
extern bool gThemeChangePending; extern bool gThemeChangePending;
extern std::string gThemeChangeRequest; // raw user-provided name extern std::string gThemeChangeRequest; // raw user-provided name
// Qt GUI: cross-frontend font change hooks and current font state // Font change request hooks and current font state (set by command handlers)
extern bool gFontChangePending; extern bool gFontChangePending;
extern std::string gFontFamilyRequest; // requested family (case-insensitive) extern std::string gFontFamilyRequest; // requested family (case-insensitive)
extern float gFontSizeRequest; // <= 0 means keep size extern float gFontSizeRequest; // <= 0 means keep size
extern std::string gCurrentFontFamily; // last applied family (Qt) extern std::string gCurrentFontFamily; // last applied family
extern float gCurrentFontSize; // last applied size (Qt) extern float gCurrentFontSize; // last applied size
// Qt GUI: request to show a visual font dialog (set by command handler) // Request to show a visual font dialog (set by command handler)
extern bool gFontDialogRequested; extern bool gFontDialogRequested;
} }
#if defined(KTE_USE_QT)
// Qt build: avoid hard dependency on ImGui headers/types.
// Provide a lightweight color vector matching ImVec4 fields used by renderers.
struct KteColor {
float x{0}, y{0}, z{0}, w{1};
};
static inline KteColor
RGBA(unsigned int rgb, float a = 1.0f)
{
const float r = static_cast<float>((rgb >> 16) & 0xFF) / 255.0f;
const float g = static_cast<float>((rgb >> 8) & 0xFF) / 255.0f;
const float b = static_cast<float>(rgb & 0xFF) / 255.0f;
return {r, g, b, a};
}
namespace kte {
// Background mode selection for light/dark palettes
enum class BackgroundMode { Light, Dark };
// Global background mode; default to Dark to match prior defaults
static inline auto gBackgroundMode = BackgroundMode::Dark;
static inline void
SetBackgroundMode(const BackgroundMode m)
{
gBackgroundMode = m;
}
static inline BackgroundMode
GetBackgroundMode()
{
return gBackgroundMode;
}
// Minimal GUI palette for Qt builds. This mirrors the defaults used in the ImGui
// frontend (Nord-ish) and switches for light/dark background mode.
struct Palette {
KteColor bg; // editor background
KteColor fg; // default foreground text
KteColor sel_bg; // selection background
KteColor cur_bg; // cursor cell background
KteColor status_bg; // status bar background
KteColor status_fg; // status bar foreground
};
// Optional theme override (Qt): when set, GetPalette() will return this instead
// of the generic light/dark defaults. This allows honoring theme names in kge.ini.
static inline bool gPaletteOverride = false;
static inline Palette gOverridePalette{};
static inline std::string gOverrideThemeName = ""; // lowercased name
static inline Palette
GetPalette()
{
const bool dark = (GetBackgroundMode() == BackgroundMode::Dark);
if (gPaletteOverride) {
return gOverridePalette;
}
if (dark) {
return Palette{
/*bg*/ RGBA(0x1C1C1E),
/*fg*/ RGBA(0xDCDCDC),
/*sel_bg*/ RGBA(0xC8C800, 0.35f),
/*cur_bg*/ RGBA(0xC8C8FF, 0.50f),
/*status_bg*/ RGBA(0x28282C),
/*status_fg*/ RGBA(0xB4B48C)
};
} else {
// Light palette tuned for readability
return Palette{
/*bg*/ RGBA(0xFBFBFC),
/*fg*/ RGBA(0x30343A),
/*sel_bg*/ RGBA(0x268BD2, 0.22f),
/*cur_bg*/ RGBA(0x000000, 0.15f),
/*status_bg*/ RGBA(0xE6E8EA),
/*status_fg*/ RGBA(0x50555A)
};
}
}
// A few named palettes to provide visible differences between themes in Qt.
// These are approximate and palette-based (no widget style changes like ImGuiStyle).
static inline Palette
NordDark()
{
return {
/*bg*/RGBA(0x2E3440), /*fg*/RGBA(0xD8DEE9), /*sel_bg*/RGBA(0x88C0D0, 0.25f),
/*cur_bg*/RGBA(0x81A1C1, 0.35f), /*status_bg*/RGBA(0x3B4252), /*status_fg*/RGBA(0xE5E9F0)
};
}
static inline Palette
NordLight()
{
return {
/*bg*/RGBA(0xECEFF4), /*fg*/RGBA(0x2E3440), /*sel_bg*/RGBA(0x5E81AC, 0.22f),
/*cur_bg*/RGBA(0x000000, 0.12f), /*status_bg*/RGBA(0xE5E9F0), /*status_fg*/RGBA(0x4C566A)
};
}
static inline Palette
SolarizedDark()
{
return {
/*bg*/RGBA(0x002b36), /*fg*/RGBA(0x93a1a1), /*sel_bg*/RGBA(0x586e75, 0.40f),
/*cur_bg*/RGBA(0x657b83, 0.35f), /*status_bg*/RGBA(0x073642), /*status_fg*/RGBA(0xeee8d5)
};
}
static inline Palette
SolarizedLight()
{
return {
/*bg*/RGBA(0xfdf6e3), /*fg*/RGBA(0x586e75), /*sel_bg*/RGBA(0x268bd2, 0.25f),
/*cur_bg*/RGBA(0x000000, 0.10f), /*status_bg*/RGBA(0xeee8d5), /*status_fg*/RGBA(0x657b83)
};
}
static inline Palette
GruvboxDark()
{
return {
/*bg*/RGBA(0x282828), /*fg*/RGBA(0xebdbb2), /*sel_bg*/RGBA(0xd79921, 0.35f),
/*cur_bg*/RGBA(0x458588, 0.40f), /*status_bg*/RGBA(0x3c3836), /*status_fg*/RGBA(0xd5c4a1)
};
}
static inline Palette
GruvboxLight()
{
return {
/*bg*/RGBA(0xfbf1c7), /*fg*/RGBA(0x3c3836), /*sel_bg*/RGBA(0x076678, 0.22f),
/*cur_bg*/RGBA(0x000000, 0.10f), /*status_bg*/RGBA(0xebdbb2), /*status_fg*/RGBA(0x504945)
};
}
static inline Palette
EInk()
{
return {
/*bg*/RGBA(0xffffff), /*fg*/RGBA(0x000000), /*sel_bg*/RGBA(0x000000, 0.10f),
/*cur_bg*/RGBA(0x000000, 0.12f), /*status_bg*/RGBA(0x000000), /*status_fg*/RGBA(0xffffff)
};
}
// Apply a Qt theme by name. Returns true on success. Name matching is case-insensitive and
// supports common aliases (e.g., "solarized-light" or "solarized light"). If the name conveys
// a background (light/dark), BackgroundMode is updated to keep SyntaxInk consistent.
static inline bool
ApplyQtThemeByName(std::string name)
{
// normalize
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) {
return (char) std::tolower(c);
});
auto has = [&](const std::string &s) {
return name.find(s) != std::string::npos;
};
if (name.empty() || name == "default" || name == "nord") {
// Choose variant by current background mode
if (GetBackgroundMode() == BackgroundMode::Dark) {
gOverridePalette = NordDark();
} else {
gOverridePalette = NordLight();
}
gPaletteOverride = true;
gOverrideThemeName = "nord";
return true;
}
if (has("solarized")) {
if (has("light")) {
SetBackgroundMode(BackgroundMode::Light);
gOverridePalette = SolarizedLight();
} else if (has("dark")) {
SetBackgroundMode(BackgroundMode::Dark);
gOverridePalette = SolarizedDark();
} else {
// pick from current background
gOverridePalette = (GetBackgroundMode() == BackgroundMode::Dark)
? SolarizedDark()
: SolarizedLight();
}
gPaletteOverride = true;
gOverrideThemeName = "solarized";
return true;
}
if (has("gruvbox")) {
if (has("light")) {
SetBackgroundMode(BackgroundMode::Light);
gOverridePalette = GruvboxLight();
} else if (has("dark")) {
SetBackgroundMode(BackgroundMode::Dark);
gOverridePalette = GruvboxDark();
} else {
gOverridePalette = (GetBackgroundMode() == BackgroundMode::Dark)
? GruvboxDark()
: GruvboxLight();
}
gPaletteOverride = true;
gOverrideThemeName = "gruvbox";
return true;
}
if (has("eink") || has("e-ink") || has("paper")) {
SetBackgroundMode(BackgroundMode::Light);
gOverridePalette = EInk();
gPaletteOverride = true;
gOverrideThemeName = "eink";
return true;
}
// Unknown -> clear override so default light/dark applies; return false.
gPaletteOverride = false;
gOverrideThemeName.clear();
return false;
}
// Minimal SyntaxInk mapping for Qt builds, returning KteColor
[[maybe_unused]] static KteColor
SyntaxInk(const TokenKind k)
{
const bool dark = (GetBackgroundMode() == BackgroundMode::Dark);
const KteColor def = dark ? RGBA(0xD8DEE9) : RGBA(0x2E3440);
switch (k) {
case TokenKind::Keyword:
return dark ? RGBA(0x81A1C1) : RGBA(0x5E81AC);
case TokenKind::Type:
return dark ? RGBA(0x8FBCBB) : RGBA(0x4C566A);
case TokenKind::String:
return dark ? RGBA(0xA3BE8C) : RGBA(0x6C8E5E);
case TokenKind::Char:
return dark ? RGBA(0xA3BE8C) : RGBA(0x6C8E5E);
case TokenKind::Comment:
return dark ? RGBA(0x616E88) : RGBA(0x7A869A);
case TokenKind::Number:
return dark ? RGBA(0xEBCB8B) : RGBA(0xB58900);
case TokenKind::Preproc:
return dark ? RGBA(0xD08770) : RGBA(0xAF3A03);
case TokenKind::Constant:
return dark ? RGBA(0xB48EAD) : RGBA(0x7B4B7F);
case TokenKind::Function:
return dark ? RGBA(0x88C0D0) : RGBA(0x3465A4);
case TokenKind::Operator:
return dark ? RGBA(0xECEFF4) : RGBA(0x2E3440); // as the ImGui palette
case TokenKind::Punctuation:
return dark ? RGBA(0xECEFF4) : RGBA(0x2E3440);
case TokenKind::Identifier:
return def;
case TokenKind::Whitespace:
return def;
case TokenKind::Error:
return dark ? RGBA(0xBF616A) : RGBA(0xCC0000);
case TokenKind::Default: default:
return def;
}
}
} // namespace kte
#else
#include <imgui.h> #include <imgui.h>
#include <vector> #include <vector>
#include <memory> #include <memory>
@@ -1109,5 +833,3 @@ SyntaxInk(const TokenKind k)
} }
} }
} // namespace kte } // namespace kte
#endif // KTE_USE_QT
+1 -1
View File
@@ -12,7 +12,7 @@
// Verbose k-prefix suffix logging for debugging macOS/SDL key translation // Verbose k-prefix suffix logging for debugging macOS/SDL key translation
// issues. Default to off; enable by defining IMGUI_IH_DEBUG=1 at compile // issues. Default to off; enable by defining IMGUI_IH_DEBUG=1 at compile
// time. Mirrors QtInputHandler.cc's QT_IH_DEBUG gate. // time.
#ifndef IMGUI_IH_DEBUG #ifndef IMGUI_IH_DEBUG
#define IMGUI_IH_DEBUG 0 #define IMGUI_IH_DEBUG 0
#endif #endif
-923
View File
@@ -1,923 +0,0 @@
#include "QtFrontend.h"
#include "SearchHighlight.h"
#include "TermWidth.h"
#include <QApplication>
#include <QWidget>
#include <QKeyEvent>
#include <QTimer>
#include <QScreen>
#include <QFont>
#include <QFontMetrics>
#include <QFontMetricsF>
#include <QFontDatabase>
#include <QFileDialog>
#include <QFontDialog>
#include <QPainter>
#include <QPaintEvent>
#include <QWheelEvent>
#include "Editor.h"
#include "Command.h"
#include "Buffer.h"
#include "GUITheme.h"
#include "Highlight.h"
namespace {
// Text layout, shared by painting and the editor's row/column count: they
// must agree, or the editor scrolls the cursor to a column the window does
// not show.
struct TextLayout {
int line_h;
qreal cell_w; // fractional: rounding it drifts over long lines
int ch_w; // cell_w rounded, for pixel-delta arithmetic
int status_h;
static constexpr int pad_l = 8, pad_t = 6, pad_r = 8, pad_b = 6;
explicit TextLayout(const QFont &f)
: line_h(std::max(1, QFontMetrics(f).height())),
cell_w(std::max<qreal>(1.0, QFontMetricsF(f).horizontalAdvance(QStringLiteral(" ")))),
ch_w(std::max(1, qRound(cell_w))),
status_h(line_h + 6) {}
// Rows (text rows plus the status row, see Editor::ContentRows) and
// columns for a w x h window.
void SetEditorDimensions(Editor &ed, int w, int h) const
{
const int avail_w = std::max(0, w - pad_l - pad_r);
const int avail_h = std::max(0, h - pad_t - pad_b - status_h);
ed.SetDimensions(std::max<std::size_t>(1, avail_h / line_h) + 1,
std::max<std::size_t>(1, static_cast<std::size_t>(avail_w / cell_w)));
}
};
class MainWindow : public QWidget {
public:
explicit MainWindow(class QtInputHandler &ih, QWidget *parent = nullptr)
: QWidget(parent), input_(ih)
{
// Match ImGui window title format
setWindowTitle(QStringLiteral("kge - kyle's graphical editor ")
+ QStringLiteral(KTE_VERSION_STR));
resize(1280, 800);
setFocusPolicy(Qt::StrongFocus);
}
bool WasClosed() const
{
return closed_;
}
void SetEditor(Editor *ed)
{
ed_ = ed;
}
void SetFontFamilyAndSize(QString family, int px)
{
if (family.isEmpty())
family = QStringLiteral("Brass Mono");
if (px <= 0)
px = 18;
font_family_ = std::move(family);
font_px_ = px;
update();
}
protected:
void keyPressEvent(QKeyEvent *event) override
{
// Route to editor keymap; if handled, accept and stop propagation so
// Qt doesn't trigger any default widget shortcuts.
if (input_.ProcessKeyEvent(*event)) {
event->accept();
return;
}
QWidget::keyPressEvent(event);
}
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter p(this);
p.setRenderHint(QPainter::TextAntialiasing, true);
// Colors from GUITheme palette (Qt branch)
auto to_qcolor = [](const KteColor &c) -> QColor {
int r = int(std::round(c.x * 255.0f));
int g = int(std::round(c.y * 255.0f));
int b = int(std::round(c.z * 255.0f));
int a = int(std::round(c.w * 255.0f));
return QColor(r, g, b, a);
};
const auto pal = kte::GetPalette();
const QColor bg = to_qcolor(pal.bg);
const QColor fg = to_qcolor(pal.fg);
const QColor sel_bg = to_qcolor(pal.sel_bg);
const QColor cur_bg = to_qcolor(pal.cur_bg);
const QColor status_bg = to_qcolor(pal.status_bg);
const QColor status_fg = to_qcolor(pal.status_fg);
// Background
p.fillRect(rect(), bg);
// Font/metrics (configured or defaults)
QFont f(font_family_, font_px_);
p.setFont(f);
QFontMetrics fm(f);
const TextLayout layout(f);
const int line_h = layout.line_h;
const qreal cell_w = layout.cell_w;
const int pad_l = TextLayout::pad_l;
const int pad_t = TextLayout::pad_t;
const int pad_r = TextLayout::pad_r;
const int pad_b = TextLayout::pad_b;
const int status_h = layout.status_h;
// Content area (text viewport)
const QRect content_rect(pad_l,
pad_t,
width() - pad_l - pad_r,
height() - pad_t - pad_b - status_h);
// Text viewport occupies all content area (no extra title row)
QRect viewport(content_rect.x(), content_rect.y(), content_rect.width(), content_rect.height());
// Draw buffer contents
if (ed_ && viewport.height() > 0 && viewport.width() > 0) {
const Buffer *buf = ed_->CurrentBuffer();
if (buf) {
const std::size_t nrows = buf->Nrows();
const std::size_t rowoffs = buf->Rowoffs();
const std::size_t coloffs = buf->Coloffs();
const std::size_t cy = buf->Cury();
const std::size_t cx = buf->Curx();
// Visible line count
const int max_lines = (line_h > 0) ? (viewport.height() / line_h) : 0;
const std::size_t last_row = std::min<std::size_t>(
nrows, rowoffs + std::max(0, max_lines));
// Prepare painter clip to viewport
p.save();
p.setClipRect(viewport);
// Search matches: the pattern is compiled once per frame.
const kte::SearchHighlight search_hl(*ed_);
std::vector<std::pair<std::size_t, std::size_t> > hl_src_ranges;
std::vector<std::size_t> col_of; // per line, see DisplayColumns
// Left edge of display column col (fractional pixels).
auto col_x = [&](std::size_t col) -> qreal {
return viewport.x() + (static_cast<qreal>(col) - static_cast<qreal>(coloffs)) * cell_w;
};
// Columns the viewport shows, plus one partly visible.
const std::size_t vis_cols = static_cast<std::size_t>(viewport.width() / cell_w) + 1;
// Iterate visible lines
for (std::size_t i = rowoffs, vis_idx = 0; i < last_row; ++i, ++vis_idx) {
// Get line as string for regex/iterator usage and general string ops.
const std::string line = buf->GetLineString(i);
const int y = viewport.y() + static_cast<int>(vis_idx) * line_h;
const int baseline = y + fm.ascent();
// Display column (cells, as the command layer's scrolling
// counts them) of each source byte.
kte::DisplayColumns(line, col_of);
auto src_to_rx_line = [&](std::size_t src_col) -> std::size_t {
return col_of[std::min(src_col, line.size())];
};
// Draw source bytes [s, e) (tabs expanded) at their column;
// the viewport clip hides what lies left of coloffs. ASCII runs
// are drawn whole; other characters one at a time at their
// cell, since a glyph from a fallback font can be wider than a
// cell and would push the rest of the run out of place.
auto draw_text_at = [&](std::size_t col, const char *d, std::size_t n) {
p.drawText(QPointF(col_x(col), baseline), QString::fromUtf8(d, static_cast<int>(n)));
};
auto draw_seg = [&](std::size_t s, std::size_t e, const QColor &color) {
// Only the visible columns: characters that end at or
// before coloffs, or start right of the viewport, are
// skipped. A tab or wide character straddling coloffs
// starts left of it; it is drawn at its own column and
// the clip cuts its left part.
const std::size_t first = s;
s = static_cast<std::size_t>(std::lower_bound(col_of.begin() + s, col_of.begin() + e,
coloffs) - col_of.begin());
if (s > first && col_of[s] > coloffs) {
// The character before s ends past coloffs: back up to
// its first byte.
--s;
while (s > first && (static_cast<unsigned char>(line[s]) & 0xC0) == 0x80)
--s;
}
e = static_cast<std::size_t>(std::lower_bound(col_of.begin() + s, col_of.begin() + e,
coloffs + vis_cols) - col_of.begin());
if (e <= s)
return;
p.setPen(color);
std::string run; // ASCII, tabs expanded
std::size_t run_col = col_of[s];
for (std::size_t k = s; k < e;) {
const unsigned char c = static_cast<unsigned char>(line[k]);
if (c < 0x80) {
if (run.empty())
run_col = col_of[k];
if (c == '\t')
run.append(col_of[k + 1] - col_of[k], ' ');
else
run.push_back(static_cast<char>(c));
++k;
continue;
}
if (!run.empty()) {
draw_text_at(run_col, run.data(), run.size());
run.clear();
}
std::size_t n = 1; // bytes sharing this character's column
while (k + n < e && col_of[k + n] == col_of[k] &&
(static_cast<unsigned char>(line[k + n]) & 0xC0) == 0x80)
++n;
draw_text_at(col_of[k], line.data() + k, n);
k += n;
}
if (!run.empty())
draw_text_at(run_col, run.data(), run.size());
};
// Search-match background highlights first (under text)
if (search_hl.Active()) {
search_hl.Ranges(line, hl_src_ranges);
if (!hl_src_ranges.empty()) {
const bool has_current =
ed_->SearchMatchLen() > 0 && ed_->SearchMatchY() == i;
const std::size_t cur_x = has_current ? ed_->SearchMatchX() : 0;
const std::size_t cur_end = has_current
? (ed_->SearchMatchX() + ed_->SearchMatchLen())
: 0;
for (const auto &rg: hl_src_ranges) {
std::size_t sx = rg.first, ex = rg.second;
std::size_t rx_s = src_to_rx_line(sx);
std::size_t rx_e = src_to_rx_line(ex);
if (rx_e <= coloffs)
continue; // fully left of view
const int vx0 = qRound(col_x(std::max(rx_s, coloffs)));
const int vx1 = qRound(col_x(rx_e));
QRect r(vx0, y, std::max(0, vx1 - vx0), line_h);
if (r.width() <= 0)
continue;
bool is_current =
has_current && sx == cur_x && ex == cur_end;
QColor col = is_current
? QColor(255, 220, 120, 140)
: QColor(200, 200, 0, 90);
p.fillRect(r, col);
}
}
}
// Selection background (if active on this line)
if (buf->MarkSet() && (
i == buf->MarkCury() || i == cy || (
i > std::min(buf->MarkCury(), cy) && i < std::max(
buf->MarkCury(), cy)))) {
std::size_t sx = 0, ex = 0;
if (buf->MarkCury() == i && cy == i) {
sx = std::min(buf->MarkCurx(), cx);
ex = std::max(buf->MarkCurx(), cx);
} else if (i == buf->MarkCury()) {
sx = buf->MarkCurx();
ex = line.size();
} else if (i == cy) {
sx = 0;
ex = cx;
} else {
sx = 0;
ex = line.size();
}
std::size_t rx_s = src_to_rx_line(sx);
std::size_t rx_e = src_to_rx_line(ex);
if (rx_e > coloffs) {
const int vx0 = qRound(col_x(std::max(rx_s, coloffs)));
const int vx1 = qRound(col_x(rx_e));
QRect sel_r(vx0, y, std::max(0, vx1 - vx0), line_h);
if (sel_r.width() > 0)
p.fillRect(sel_r, sel_bg);
}
}
// Syntax-colored spans, the text between them in the default
// color (drawing only the spans dropped unhighlighted text such
// as operators).
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->
HasHighlighter()) {
const kte::LineHighlight &lh = buf->Highlighter()->GetLine(
*buf, static_cast<int>(i), buf->Version());
std::vector<kte::HighlightSpan> spans; // clamped, ordered, whole characters
kte::SanitizeSpans(line, lh.spans, spans);
auto colorFor = [](kte::TokenKind k) -> QColor {
// GUITheme provides colors via ImGui vector; avoid direct dependency types
const auto v = kte::SyntaxInk(k);
return QColor(int(v.x * 255.0f), int(v.y * 255.0f),
int(v.z * 255.0f), int(v.w * 255.0f));
};
std::size_t pos = 0;
for (const auto &sp: spans) {
const std::size_t s = std::max(static_cast<std::size_t>(sp.col_start), pos);
draw_seg(pos, s, fg);
draw_seg(s, static_cast<std::size_t>(sp.col_end), colorFor(sp.kind));
pos = std::max(pos, static_cast<std::size_t>(sp.col_end));
}
draw_seg(pos, line.size(), fg);
} else {
draw_seg(0, line.size(), fg);
}
// Cursor indicator on current line
if (i == cy) {
const std::size_t rx_cur = src_to_rx_line(cx);
if (rx_cur >= coloffs) {
const int x0 = qRound(col_x(rx_cur));
p.fillRect(QRect(x0, y, qRound(col_x(rx_cur + 1)) - x0, line_h), cur_bg);
}
}
}
p.restore();
}
}
// Status bar
const int bar_y = height() - status_h;
QRect status_rect(0, bar_y, width(), status_h);
p.fillRect(status_rect, status_bg);
p.setPen(status_fg);
if (ed_) {
const int pad = 6;
const int left_x = status_rect.x() + pad;
const int right_x_max = status_rect.x() + status_rect.width() - pad;
const int baseline_y = bar_y + (status_h + fm.ascent() - fm.descent()) / 2;
// If a prompt is active, mirror ImGui/TUI: show only the prompt across the bar
if (ed_->PromptActive()) {
std::string label = ed_->PromptLabel();
std::string text = ed_->PromptText();
// Map $HOME to ~ for path prompts (Open/Save/Chdir)
auto kind = ed_->CurrentPromptKind();
if (kind == Editor::PromptKind::OpenFile ||
kind == Editor::PromptKind::SaveAs ||
kind == Editor::PromptKind::Chdir) {
const char *home_c = std::getenv("HOME");
if (home_c && *home_c) {
std::string home(home_c);
if (text.rfind(home, 0) == 0) {
std::string rest = text.substr(home.size());
if (rest.empty())
text = "~";
else if (!rest.empty() && (rest[0] == '/' || rest[0] == '\\'))
text = std::string("~") + rest;
}
}
}
std::string prefix;
if (kind == Editor::PromptKind::Command)
prefix = ": ";
else if (!label.empty())
prefix = label + ": ";
// Compose text and elide per behavior:
const int max_w = status_rect.width() - 2 * pad;
QString qprefix = QString::fromStdString(prefix);
QString qtext = QString::fromStdString(text);
int avail_w = std::max(0, max_w - fm.horizontalAdvance(qprefix));
Qt::TextElideMode mode = Qt::ElideRight;
if (kind == Editor::PromptKind::OpenFile ||
kind == Editor::PromptKind::SaveAs ||
kind == Editor::PromptKind::Chdir) {
mode = Qt::ElideLeft;
}
QString shown = fm.elidedText(qtext, mode, avail_w);
p.drawText(left_x, baseline_y, qprefix + shown);
} else {
// Build left segment: app/version, buffer idx/total, filename [+dirty], line count
QString left;
left += QStringLiteral("kge ");
left += QStringLiteral(KTE_VERSION_STR);
const Buffer *buf = ed_->CurrentBuffer();
if (buf) {
// buffer index/total
std::size_t total = ed_->BufferCount();
if (total > 0) {
std::size_t idx1 = ed_->CurrentBufferIndex() + 1; // 1-based
left += QStringLiteral(" [");
left += QString::number(static_cast<qlonglong>(idx1));
left += QStringLiteral("/");
left += QString::number(static_cast<qlonglong>(total));
left += QStringLiteral("] ");
} else {
left += QStringLiteral(" ");
}
// buffer display name
std::string disp;
try {
disp = ed_->DisplayNameFor(*buf);
} catch (...) {
disp = buf->Filename();
}
if (disp.empty())
disp = "[No Name]";
left += QString::fromStdString(disp);
if (buf->Dirty())
left += QStringLiteral(" *");
// total lines suffix " <n>L"
unsigned long lcount = static_cast<unsigned long>(buf->Nrows());
left += QStringLiteral(" ");
left += QString::number(static_cast<qlonglong>(lcount));
left += QStringLiteral("L");
}
// Build right segment: cursor and mark
QString right;
if (buf) {
int row1 = static_cast<int>(buf->Cury()) + 1;
int col1 = static_cast<int>(buf->Curx()) + 1;
bool have_mark = buf->MarkSet();
int mrow1 = have_mark ? static_cast<int>(buf->MarkCury()) + 1 : 0;
int mcol1 = have_mark ? static_cast<int>(buf->MarkCurx()) + 1 : 0;
if (have_mark)
right = QString("%1,%2 | M: %3,%4").arg(row1).arg(col1).arg(mrow1).arg(
mcol1);
else
right = QString("%1,%2 | M: not set").arg(row1).arg(col1);
}
// Middle message: status text
QString mid = QString::fromStdString(ed_->Status());
// Measure and layout
int left_w = fm.horizontalAdvance(left);
int right_w = fm.horizontalAdvance(right);
int lx = left_x;
int rx = std::max(left_x, right_x_max - right_w);
// If overlap, elide left to make space for right
if (lx + left_w + pad > rx) {
int max_left_w = std::max(0, rx - lx - pad);
left = fm.elidedText(left, Qt::ElideRight, max_left_w);
left_w = fm.horizontalAdvance(left);
}
// Draw left and right
p.drawText(lx, baseline_y, left);
if (!right.isEmpty())
p.drawText(rx, baseline_y, right);
// Middle message clipped between end of left and start of right
int mid_left = lx + left_w + pad;
int mid_right = std::max(mid_left, rx - pad);
int mid_w = std::max(0, mid_right - mid_left);
if (mid_w > 0 && !mid.isEmpty()) {
QString mid_show = fm.elidedText(mid, Qt::ElideRight, mid_w);
p.save();
p.setClipRect(QRect(mid_left, bar_y, mid_w, status_h));
p.drawText(mid_left, baseline_y, mid_show);
p.restore();
}
}
}
}
void resizeEvent(QResizeEvent *event) override
{
QWidget::resizeEvent(event);
if (!ed_)
return;
// Update editor dimensions based on new size
QFont f(font_family_, font_px_);
TextLayout(f).SetEditorDimensions(*ed_, width(), height());
}
void wheelEvent(QWheelEvent *event) override
{
if (!ed_) {
QWidget::wheelEvent(event);
return;
}
Buffer *buf = ed_->CurrentBuffer();
if (!buf) {
QWidget::wheelEvent(event);
return;
}
// Recompute metrics to map pixel deltas to rows/cols
const TextLayout layout(QFont(font_family_, font_px_));
const int line_h = layout.line_h;
const int ch_w = layout.ch_w;
// Determine scroll intent: use pixelDelta when available (trackpads), otherwise angleDelta
QPoint pixel = event->pixelDelta();
QPoint angle = event->angleDelta();
double v_lines_delta = 0.0;
double h_cols_delta = 0.0;
// Horizontal scroll with Shift or explicit horizontal delta
bool horiz_mode = (event->modifiers() & Qt::ShiftModifier) || (!pixel.isNull() && pixel.x() != 0) || (
!angle.isNull() && angle.x() != 0);
if (!pixel.isNull()) {
// Trackpad smooth scrolling (pixels)
v_lines_delta = -static_cast<double>(pixel.y()) / std::max(1, line_h);
h_cols_delta = -static_cast<double>(pixel.x()) / std::max(1, ch_w);
} else if (!angle.isNull()) {
// Mouse wheel: 120 units per notch; map one notch to 3 lines similar to ImGui UX
v_lines_delta = -static_cast<double>(angle.y()) / 120.0 * 3.0;
// For horizontal wheels, each notch scrolls 8 columns
h_cols_delta = -static_cast<double>(angle.x()) / 120.0 * 8.0;
}
// Accumulate fractional deltas across events
v_scroll_accum_ += v_lines_delta;
h_scroll_accum_ += h_cols_delta;
int d_rows = 0;
int d_cols = 0;
if (std::fabs(v_scroll_accum_) >= 1.0 && (!horiz_mode || std::fabs(v_scroll_accum_) > std::fabs(
h_scroll_accum_))) {
d_rows = static_cast<int>(v_scroll_accum_);
v_scroll_accum_ -= d_rows;
}
if (std::fabs(h_scroll_accum_) >= 1.0 && (horiz_mode || std::fabs(h_scroll_accum_) >= std::fabs(
v_scroll_accum_))) {
d_cols = static_cast<int>(h_scroll_accum_);
h_scroll_accum_ -= d_cols;
}
if (d_rows != 0 || d_cols != 0) {
std::size_t new_rowoffs = buf->Rowoffs();
std::size_t new_coloffs = buf->Coloffs();
// Clamp vertical between 0 and last row (leaving at least one visible line)
if (d_rows != 0) {
long nr = static_cast<long>(new_rowoffs) + d_rows;
if (nr < 0)
nr = 0;
const auto nrows = static_cast<long>(buf->Nrows());
if (nr > std::max(0L, nrows - 1))
nr = std::max(0L, nrows - 1);
new_rowoffs = static_cast<std::size_t>(nr);
}
if (d_cols != 0) {
long nc = static_cast<long>(new_coloffs) + d_cols;
if (nc < 0)
nc = 0;
new_coloffs = static_cast<std::size_t>(nc);
}
buf->SetOffsets(new_rowoffs, new_coloffs);
update();
event->accept();
return;
}
QWidget::wheelEvent(event);
}
void closeEvent(QCloseEvent *event) override
{
closed_ = true;
QWidget::closeEvent(event);
}
private:
QtInputHandler &input_;
bool closed_ = false;
Editor *ed_ = nullptr;
double v_scroll_accum_ = 0.0;
double h_scroll_accum_ = 0.0;
QString font_family_ = QStringLiteral("Brass Mono");
int font_px_ = 18;
};
} // namespace
bool
GUIFrontend::Init(int &argc, char **argv, Editor &ed)
{
app_ = new QApplication(argc, argv);
window_ = new MainWindow(input_);
window_->show();
// Ensure the window becomes the active, focused window so it receives key events
window_->activateWindow();
window_->raise();
window_->setFocus(Qt::OtherFocusReason);
renderer_.Attach(window_);
input_.Attach(&ed);
if (auto *mw = dynamic_cast<MainWindow *>(window_))
mw->SetEditor(&ed);
// Load GUI configuration (kge.ini) and configure font for Qt
config_ = GUIConfig::Load();
// Apply background mode from config to match ImGui frontend behavior
if (config_.background == "light")
kte::SetBackgroundMode(kte::BackgroundMode::Light);
else
kte::SetBackgroundMode(kte::BackgroundMode::Dark);
// Apply theme by name for Qt palette-based theming (maps to named palettes).
// If unknown, falls back to the generic light/dark palette.
(void) kte::ApplyQtThemeByName(config_.theme);
if (window_)
window_->update();
// Map GUIConfig font name to a system family (Qt uses installed fonts)
auto choose_family = [](const std::string &name) -> QString {
QString fam;
std::string n = name;
std::transform(n.begin(), n.end(), n.begin(), [](unsigned char c) {
return (char) std::tolower(c);
});
if (n.empty() || n == "default" || n == "brassmono" || n == "brassmonocode") {
fam = QStringLiteral("Brass Mono");
} else if (n == "jetbrains" || n == "jetbrains mono" || n == "jetbrains-mono") {
fam = QStringLiteral("JetBrains Mono");
} else if (n == "iosevka") {
fam = QStringLiteral("Iosevka");
} else if (n == "inconsolata" || n == "inconsolataex") {
fam = QStringLiteral("Inconsolata");
} else if (n == "space" || n == "spacemono" || n == "space mono") {
fam = QStringLiteral("Space Mono");
} else if (n == "go") {
fam = QStringLiteral("Go Mono");
} else if (n == "ibm" || n == "ibm plex mono" || n == "ibm-plex-mono") {
fam = QStringLiteral("IBM Plex Mono");
} else if (n == "fira" || n == "fira code" || n == "fira-code") {
fam = QStringLiteral("Fira Code");
} else if (!name.empty()) {
fam = QString::fromStdString(name);
}
// Validate availability; choose a fallback if needed
const auto families = QFontDatabase::families();
if (!fam.isEmpty() && families.contains(fam)) {
return fam;
}
// Preferred fallback chain on macOS; otherwise, try common monospace families
const QStringList fallbacks = {
QStringLiteral("Brass Mono"),
QStringLiteral("JetBrains Mono"),
QStringLiteral("SF Mono"),
QStringLiteral("Menlo"),
QStringLiteral("Monaco"),
QStringLiteral("Courier New"),
QStringLiteral("Courier"),
QStringLiteral("Monospace")
};
for (const auto &fb: fallbacks) {
if (families.contains(fb))
return fb;
}
// As a last resort, return the request (Qt will substitute)
return fam.isEmpty() ? QStringLiteral("Monospace") : fam;
};
QString family = choose_family(config_.font);
int px_size = (config_.font_size > 0.0f) ? (int) std::lround(config_.font_size) : 18;
if (auto *mw = dynamic_cast<MainWindow *>(window_)) {
mw->SetFontFamilyAndSize(family, px_size);
}
// Track current font in globals for command/status queries
kte::gCurrentFontFamily = family.toStdString();
kte::gCurrentFontSize = static_cast<float>(px_size);
// Set initial dimensions based on font metrics
QFont f(family, px_size);
TextLayout(f).SetEditorDimensions(ed, window_->width(), window_->height());
return true;
}
void
GUIFrontend::Step(Editor &ed, bool &running)
{
// Pump Qt events
if (app_)
app_->processEvents();
// Allow deferred opens (including swap recovery prompts) to run.
ed.ProcessPendingOpens();
// Drain input queue
for (;;) {
MappedInput mi;
if (!input_.Poll(mi))
break;
if (mi.hasCommand) {
Execute(ed, mi.id, mi.arg, mi.count);
}
}
if (ed.QuitRequested()) {
running = false;
}
// --- Visual File Picker (Qt): invoked via CommandId::VisualFilePickerToggle ---
if (ed.FilePickerVisible()) {
QString startDir;
if (!ed.FilePickerDir().empty()) {
startDir = QString::fromStdString(ed.FilePickerDir());
}
QFileDialog dlg(window_, QStringLiteral("Open File"), startDir);
dlg.setFileMode(QFileDialog::ExistingFile);
if (dlg.exec() == QDialog::Accepted) {
const QStringList files = dlg.selectedFiles();
if (!files.isEmpty()) {
const QString fp = files.front();
ed.RequestOpenFile(fp.toStdString());
(void) ed.ProcessPendingOpens();
// Update picker dir for next time
QFileInfo info(fp);
ed.SetFilePickerDir(info.dir().absolutePath().toStdString());
}
}
// Close picker overlay regardless of outcome
ed.SetFilePickerVisible(false);
if (window_)
window_->update();
}
// Apply any queued theme change requests (from command handler)
if (kte::gThemeChangePending) {
if (!kte::gThemeChangeRequest.empty()) {
// Apply Qt palette theme by name; if unknown, keep current palette
(void) kte::ApplyQtThemeByName(kte::gThemeChangeRequest);
}
kte::gThemeChangePending = false;
kte::gThemeChangeRequest.clear();
if (window_)
window_->update();
}
// Visual font picker request (Qt only)
if (kte::gFontDialogRequested) {
// Seed initial font from current or default
QFont seed;
if (!kte::gCurrentFontFamily.empty()) {
seed = QFont(QString::fromStdString(kte::gCurrentFontFamily),
(int) std::lround(kte::gCurrentFontSize > 0 ? kte::gCurrentFontSize : 18));
} else {
seed = window_ ? window_->font() : QFont();
}
bool ok = false;
const QFont chosen = QFontDialog::getFont(&ok, seed, window_, QStringLiteral("Choose Editor Font"));
if (ok) {
// Queue font change via existing hooks
kte::gFontFamilyRequest = chosen.family().toStdString();
// Use pixel size if available, otherwise convert from point size approximately
int px = chosen.pixelSize();
if (px <= 0) {
// Approximate points to pixels (96 DPI assumption); Qt will rasterize appropriately
px = (int) std::lround(chosen.pointSizeF() * 96.0 / 72.0);
if (px <= 0)
px = 18;
}
kte::gFontSizeRequest = static_cast<float>(px);
kte::gFontChangePending = true;
}
kte::gFontDialogRequested = false;
if (window_)
window_->update();
}
// Apply any queued font change requests (Qt)
if (kte::gFontChangePending) {
// Derive target family
auto map_family = [](const std::string &name) -> QString {
std::string n = name;
std::transform(n.begin(), n.end(), n.begin(), [](unsigned char c) {
return (char) std::tolower(c);
});
QString fam;
if (n == "brass" || n == "brassmono" || n == "brass mono") {
fam = QStringLiteral("Brass Mono");
} else if (n == "jetbrains" || n == "jetbrains mono" || n == "jetbrains-mono") {
fam = QStringLiteral("JetBrains Mono");
} else if (n == "iosevka") {
fam = QStringLiteral("Iosevka");
} else if (n == "inconsolata" || n == "inconsolataex") {
fam = QStringLiteral("Inconsolata");
} else if (n == "space" || n == "spacemono" || n == "space mono") {
fam = QStringLiteral("Space Mono");
} else if (n == "go") {
fam = QStringLiteral("Go Mono");
} else if (n == "ibm" || n == "ibm plex mono" || n == "ibm-plex-mono") {
fam = QStringLiteral("IBM Plex Mono");
} else if (n == "fira" || n == "fira code" || n == "fira-code") {
fam = QStringLiteral("Fira Code");
} else if (!name.empty()) {
fam = QString::fromStdString(name);
}
// Validate availability; choose fallback if needed
const auto families = QFontDatabase::families();
if (!fam.isEmpty() && families.contains(fam)) {
return fam;
}
// Fallback chain
const QStringList fallbacks = {
QStringLiteral("Brass Mono"),
QStringLiteral("JetBrains Mono"),
QStringLiteral("SF Mono"),
QStringLiteral("Menlo"),
QStringLiteral("Monaco"),
QStringLiteral("Courier New"),
QStringLiteral("Courier"),
QStringLiteral("Monospace")
};
for (const auto &fb: fallbacks) {
if (families.contains(fb))
return fb;
}
return fam.isEmpty() ? QStringLiteral("Monospace") : fam;
};
QString target_family;
if (!kte::gFontFamilyRequest.empty()) {
target_family = map_family(kte::gFontFamilyRequest);
} else if (!kte::gCurrentFontFamily.empty()) {
target_family = QString::fromStdString(kte::gCurrentFontFamily);
}
int target_px = 0;
if (kte::gFontSizeRequest > 0.0f) {
target_px = (int) std::lround(kte::gFontSizeRequest);
} else if (kte::gCurrentFontSize > 0.0f) {
target_px = (int) std::lround(kte::gCurrentFontSize);
}
if (target_px <= 0)
target_px = 18;
if (target_family.isEmpty())
target_family = QStringLiteral("Monospace");
if (auto *mw = dynamic_cast<MainWindow *>(window_)) {
mw->SetFontFamilyAndSize(target_family, target_px);
}
// Update globals
kte::gCurrentFontFamily = target_family.toStdString();
kte::gCurrentFontSize = static_cast<float>(target_px);
// Reset requests
kte::gFontChangePending = false;
kte::gFontFamilyRequest.clear();
kte::gFontSizeRequest = 0.0f;
// Recompute editor dimensions to match new metrics
QFont f(target_family, target_px);
TextLayout(f).SetEditorDimensions(ed, window_ ? window_->width() : 0,
window_ ? window_->height() : 0);
if (window_)
window_->update();
}
// Draw current frame (request repaint)
renderer_.Draw(ed);
// Detect window close
if (auto *mw = dynamic_cast<MainWindow *>(window_)) {
if (mw->WasClosed()) {
running = false;
}
}
}
void
GUIFrontend::Shutdown()
{
if (window_) {
window_->close();
delete window_;
window_ = nullptr;
}
if (app_) {
delete app_;
app_ = nullptr;
}
}
-36
View File
@@ -1,36 +0,0 @@
/*
* QtFrontend - couples QtInputHandler + QtRenderer and owns Qt lifecycle
*/
#pragma once
#include "Frontend.h"
#include "GUIConfig.h"
#include "QtInputHandler.h"
#include "QtRenderer.h"
class QApplication;
class QWidget;
// Keep the public class name GUIFrontend to match main.cc selection logic.
class GUIFrontend final : public Frontend {
public:
GUIFrontend() = default;
~GUIFrontend() override = default;
bool Init(int &argc, char **argv, Editor &ed) override;
void Step(Editor &ed, bool &running) override;
void Shutdown() override;
private:
GUIConfig config_{};
QtInputHandler input_{};
QtRenderer renderer_{};
QApplication *app_ = nullptr; // owned
QWidget *window_ = nullptr; // owned
int width_ = 1280;
int height_ = 800;
};
-536
View File
@@ -1,536 +0,0 @@
// Refactor: route all key processing through command subsystem, mirroring ImGuiInputHandler
#include "QtInputHandler.h"
#include <QKeyEvent>
#include <ncurses.h>
#include "Editor.h"
#include "KKeymap.h"
// Temporary verbose logging to debug macOS Qt key translation issues
// Default to off; enable by defining QT_IH_DEBUG=1 at compile time when needed.
#ifndef QT_IH_DEBUG
#define QT_IH_DEBUG 0
#endif
#if QT_IH_DEBUG
#include <cstdio>
static const char *
mods_str(Qt::KeyboardModifiers m)
{
static thread_local char buf[64];
buf[0] = '\0';
bool first = true;
auto add = [&](const char *s) {
if (!first)
std::snprintf(buf + std::strlen(buf), sizeof(buf) - std::strlen(buf), "|");
std::snprintf(buf + std::strlen(buf), sizeof(buf) - std::strlen(buf), "%s", s);
first = false;
};
if (m & Qt::ShiftModifier)
add("Shift");
if (m & Qt::ControlModifier)
add("Ctrl");
if (m & Qt::AltModifier)
add("Alt");
if (m & Qt::MetaModifier)
add("Meta");
if (first)
std::snprintf(buf, sizeof(buf), "none");
return buf;
}
#define LOGF(...) std::fprintf(stderr, __VA_ARGS__)
#else
#define LOGF(...) ((void)0)
#endif
static bool
IsPrintableQt(const QKeyEvent &e)
{
// Printable if it yields non-empty text and no Ctrl/Meta modifier
if (e.modifiers() & (Qt::ControlModifier | Qt::MetaModifier))
return false;
const QString t = e.text();
return !t.isEmpty() && !t.at(0).isNull();
}
static int
ToAsciiKey(const QKeyEvent &e)
{
const QString t = e.text();
if (!t.isEmpty()) {
const QChar c = t.at(0);
if (!c.isNull())
return KLowerAscii(c.unicode());
}
// When modifiers (like Control) are held, Qt::text() can be empty on macOS.
// Fall back to mapping common virtual keys to ASCII.
switch (e.key()) {
case Qt::Key_A:
return 'a';
case Qt::Key_B:
return 'b';
case Qt::Key_C:
return 'c';
case Qt::Key_D:
return 'd';
case Qt::Key_E:
return 'e';
case Qt::Key_F:
return 'f';
case Qt::Key_G:
return 'g';
case Qt::Key_H:
return 'h';
case Qt::Key_I:
return 'i';
case Qt::Key_J:
return 'j';
case Qt::Key_K:
return 'k';
case Qt::Key_L:
return 'l';
case Qt::Key_M:
return 'm';
case Qt::Key_N:
return 'n';
case Qt::Key_O:
return 'o';
case Qt::Key_P:
return 'p';
case Qt::Key_Q:
return 'q';
case Qt::Key_R:
return 'r';
case Qt::Key_S:
return 's';
case Qt::Key_T:
return 't';
case Qt::Key_U:
return 'u';
case Qt::Key_V:
return 'v';
case Qt::Key_W:
return 'w';
case Qt::Key_X:
return 'x';
case Qt::Key_Y:
return 'y';
case Qt::Key_Z:
return 'z';
case Qt::Key_0:
return '0';
case Qt::Key_1:
return '1';
case Qt::Key_2:
return '2';
case Qt::Key_3:
return '3';
case Qt::Key_4:
return '4';
case Qt::Key_5:
return '5';
case Qt::Key_6:
return '6';
case Qt::Key_7:
return '7';
case Qt::Key_8:
return '8';
case Qt::Key_9:
return '9';
case Qt::Key_Comma:
return ',';
case Qt::Key_Period:
return '.';
case Qt::Key_Semicolon:
return ';';
case Qt::Key_Apostrophe:
return '\'';
case Qt::Key_Minus:
return '-';
case Qt::Key_Equal:
return '=';
case Qt::Key_Slash:
return '/';
case Qt::Key_Backslash:
return '\\';
case Qt::Key_BracketLeft:
return '[';
case Qt::Key_BracketRight:
return ']';
case Qt::Key_QuoteLeft:
return '`';
case Qt::Key_Space:
return ' ';
default:
break;
}
return 0;
}
// Case-preserving ASCII derivation for k-prefix handling where we need to
// distinguish between 'C' and 'c'. Falls back to virtual-key mapping if
// event text is unavailable (common when Control/Meta held on macOS).
static int
ToAsciiKeyPreserveCase(const QKeyEvent &e)
{
const QString t = e.text();
if (!t.isEmpty()) {
const QChar c = t.at(0);
if (!c.isNull())
return c.unicode();
}
// Fall back to virtual key mapping (letters as uppercase A..Z)
switch (e.key()) {
case Qt::Key_A:
return 'A';
case Qt::Key_B:
return 'B';
case Qt::Key_C:
return 'C';
case Qt::Key_D:
return 'D';
case Qt::Key_E:
return 'E';
case Qt::Key_F:
return 'F';
case Qt::Key_G:
return 'G';
case Qt::Key_H:
return 'H';
case Qt::Key_I:
return 'I';
case Qt::Key_J:
return 'J';
case Qt::Key_K:
return 'K';
case Qt::Key_L:
return 'L';
case Qt::Key_M:
return 'M';
case Qt::Key_N:
return 'N';
case Qt::Key_O:
return 'O';
case Qt::Key_P:
return 'P';
case Qt::Key_Q:
return 'Q';
case Qt::Key_R:
return 'R';
case Qt::Key_S:
return 'S';
case Qt::Key_T:
return 'T';
case Qt::Key_U:
return 'U';
case Qt::Key_V:
return 'V';
case Qt::Key_W:
return 'W';
case Qt::Key_X:
return 'X';
case Qt::Key_Y:
return 'Y';
case Qt::Key_Z:
return 'Z';
case Qt::Key_Comma:
return ',';
case Qt::Key_Period:
return '.';
case Qt::Key_Semicolon:
return ';';
case Qt::Key_Apostrophe:
return '\'';
case Qt::Key_Minus:
return '-';
case Qt::Key_Equal:
return '=';
case Qt::Key_Slash:
return '/';
case Qt::Key_Backslash:
return '\\';
case Qt::Key_BracketLeft:
return '[';
case Qt::Key_BracketRight:
return ']';
case Qt::Key_QuoteLeft:
return '`';
case Qt::Key_Space:
return ' ';
default:
break;
}
return 0;
}
bool
QtInputHandler::ProcessKeyEvent(const QKeyEvent &e)
{
const Qt::KeyboardModifiers mods = e.modifiers();
LOGF("[QtIH] keyPress key=0x%X mods=%s text='%s' k_prefix=%d k_ctrl_pending=%d esc_meta=%d\n",
e.key(), mods_str(mods), e.text().toUtf8().constData(), (int)k_prefix_, (int)k_ctrl_pending_,
(int)esc_meta_);
// Control-chord detection: only treat the physical Control key as control-like.
// Do NOT include Meta (Command) here so that ⌘-letter shortcuts do not fall into
// the Ctrl map (prevents ⌘-T being mistaken for C-t).
const bool ctrl_like = (mods & Qt::ControlModifier);
// 1) Universal argument digits (when active), consume digits without enqueuing commands
if (ed_ && ed_
->
UArg() != 0
) {
if (!(mods & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) {
if (e.key() >= Qt::Key_0 && e.key() <= Qt::Key_9) {
int d = e.key() - Qt::Key_0;
ed_->UArgDigit(d);
// request status refresh
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, CommandId::UArgStatus, std::string(), 0});
LOGF("[QtIH] UArg digit %d -> enqueue UArgStatus\n", d);
return true;
}
}
}
// 2) Enter k-prefix on C-k
if (ctrl_like && (e.key() == Qt::Key_K)) {
k_prefix_ = true;
k_ctrl_pending_ = false;
LOGF("[QtIH] Enter KPrefix\n");
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, CommandId::KPrefix, std::string(), 0});
return true;
}
// 3) If currently in k-prefix, resolve next key via KLookupKCommand
if (k_prefix_) {
// ESC/meta prefix should not interfere with k-suffix resolution
esc_meta_ = false;
// Support literal 'C' (uppercase) or '^' to indicate the next key is Ctrl-qualified.
// Use case-preserving derivation so that 'c' (lowercase) can still be a valid suffix
// like C-k c (BufferClose).
int ascii_raw = ToAsciiKeyPreserveCase(e);
if (ascii_raw == 'C' || ascii_raw == '^') {
k_ctrl_pending_ = true;
if (ed_)
ed_->SetStatus("C-k C _");
LOGF("[QtIH] KPrefix: set k_ctrl_pending via '%c'\n", (ascii_raw == 'C') ? 'C' : '^');
return true; // consume, wait for next key
}
int ascii_key = (ascii_raw != 0) ? ascii_raw : ToAsciiKey(e);
int lower = KLowerAscii(ascii_key);
// Only pass a control suffix for specific supported keys (d/x/q),
// matching ImGui behavior so that holding Ctrl during the suffix
// doesn't break other mappings like C-k c (BufferClose).
bool ctrl_suffix_supported = (lower == 'd' || lower == 'x' || lower == 'q');
bool pass_ctrl = (ctrl_like || k_ctrl_pending_) && ctrl_suffix_supported;
k_ctrl_pending_ = false; // consume pending qualifier on any suffix
LOGF("[QtIH] KPrefix: ascii_key=%d lower=%d pass_ctrl=%d\n", ascii_key, lower, (int)pass_ctrl);
if (ascii_key != 0) {
CommandId id;
if (KLookupKCommand(ascii_key, pass_ctrl, id)) {
LOGF("[QtIH] KPrefix: mapped to command id=%d\n", (int)id);
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, id, std::string(), 0});
} else {
// Unknown k-command: notify
std::string a;
a.push_back(static_cast<char>(ascii_key));
LOGF("[QtIH] KPrefix: unknown command for '%c'\n", (char)ascii_key);
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, CommandId::UnknownKCommand, a, 0});
}
k_prefix_ = false;
return true;
}
// If not resolvable, consume and exit k-prefix
k_prefix_ = false;
LOGF("[QtIH] KPrefix: unresolved key; exiting prefix\n");
return true;
}
// 3.5) GUI shortcut: Command/Meta + T opens the visual font picker (Qt only).
// Require Meta present and Control NOT present so Ctrl-T never triggers this.
if ((mods & Qt::MetaModifier) && !(mods & Qt::ControlModifier) && e.key() == Qt::Key_T) {
LOGF("[QtIH] Meta/Super-T -> VisualFontPickerToggle\n");
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, CommandId::VisualFontPickerToggle, std::string(), 0});
return true;
}
// 4) ESC as Meta prefix (set state). Alt/Meta chord handled below directly.
if (e.key() == Qt::Key_Escape) {
esc_meta_ = true;
LOGF("[QtIH] ESC: set esc_meta\n");
return true; // consumed
}
// 5) Alt/Meta bindings (ESC f/b equivalent). Handle either Alt/Meta or pending esc_meta_
// ESC/meta chords: on macOS, do NOT treat Meta as ESC; only Alt (Option) should trigger.
#if defined(__APPLE__)
if (esc_meta_ || (mods & Qt::AltModifier)) {
#else
if (esc_meta_ || (mods & (Qt::AltModifier | Qt::MetaModifier))) {
#endif
int ascii_key = 0;
if (e.key() == Qt::Key_Backspace) {
ascii_key = KEY_BACKSPACE;
} else if (e.key() >= Qt::Key_A && e.key() <= Qt::Key_Z) {
ascii_key = 'a' + (e.key() - Qt::Key_A);
} else if (e.key() == Qt::Key_Comma) {
ascii_key = '<';
} else if (e.key() == Qt::Key_Period) {
ascii_key = '>';
}
// If still unknown, try deriving from text (covers digits, punctuation, locale)
if (ascii_key == 0) {
ascii_key = ToAsciiKey(e);
}
esc_meta_ = false; // one-shot regardless
if (ascii_key != 0) {
ascii_key = KLowerAscii(ascii_key);
CommandId id;
if (KLookupEscCommand(ascii_key, id)) {
LOGF("[QtIH] ESC/Meta: mapped '%d' -> id=%d\n", ascii_key, (int)id);
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, id, std::string(), 0});
return true;
} else {
// Report invalid ESC sequence just like ImGui path
LOGF("[QtIH] ESC/Meta: unknown command for ascii=%d\n", ascii_key);
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, CommandId::UnknownEscCommand, std::string(), 0});
return true;
}
}
// Nothing derivable: consume (ESC prefix cleared) and do not insert text
return true;
}
// 6) Control-chord direct mappings (e.g., C-n/C-p/C-f/C-b...)
if (ctrl_like) {
// Universal argument handling: C-u starts collection; C-g cancels
if (e.key() == Qt::Key_U) {
if (ed_)
ed_->UArgStart();
LOGF("[QtIH] Ctrl-chord: start universal argument\n");
return true;
}
if (e.key() == Qt::Key_G) {
if (ed_)
ed_->UArgClear();
k_ctrl_pending_ = false;
k_prefix_ = false;
LOGF("[QtIH] Ctrl-chord: cancel universal argument and k-prefix via C-g\n");
// Fall through to map C-g to Refresh via ctrl map
}
if (e.key() >= Qt::Key_A && e.key() <= Qt::Key_Z) {
int ascii_key = 'a' + (e.key() - Qt::Key_A);
CommandId id;
if (KLookupCtrlCommand(ascii_key, id)) {
LOGF("[QtIH] Ctrl-chord: 'C-%c' -> id=%d\n", (char)ascii_key, (int)id);
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, id, std::string(), 0});
return true;
}
}
// If no mapping, continue to allow other keys below
}
// 7) Special navigation/edit keys (match ImGui behavior)
{
CommandId id;
bool has = false;
switch (e.key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
id = CommandId::Newline;
has = true;
break;
case Qt::Key_Backspace:
id = CommandId::Backspace;
has = true;
break;
case Qt::Key_Delete:
id = CommandId::DeleteChar;
has = true;
break;
case Qt::Key_Left:
id = CommandId::MoveLeft;
has = true;
break;
case Qt::Key_Right:
id = CommandId::MoveRight;
has = true;
break;
case Qt::Key_Up:
id = CommandId::MoveUp;
has = true;
break;
case Qt::Key_Down:
id = CommandId::MoveDown;
has = true;
break;
case Qt::Key_Home:
id = CommandId::MoveHome;
has = true;
break;
case Qt::Key_End:
id = CommandId::MoveEnd;
has = true;
break;
case Qt::Key_PageUp:
id = CommandId::PageUp;
has = true;
break;
case Qt::Key_PageDown:
id = CommandId::PageDown;
has = true;
break;
default:
break;
}
if (has) {
LOGF("[QtIH] Special key -> id=%d\n", (int)id);
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, id, std::string(), 0});
return true;
}
}
// 8) Insert printable text
if (IsPrintableQt(e)) {
std::string s = e.text().toStdString();
if (!s.empty()) {
LOGF("[QtIH] InsertText '%s'\n", s.c_str());
std::lock_guard<std::mutex> lk(mu_);
q_.push(MappedInput{true, CommandId::InsertText, s, 0});
return true;
}
}
LOGF("[QtIH] Unhandled key\n");
return false;
}
bool
QtInputHandler::Poll(MappedInput &out)
{
std::lock_guard<std::mutex> lock(mu_);
if (q_.empty())
return false;
out = q_.front();
q_.pop();
return true;
}
-40
View File
@@ -1,40 +0,0 @@
/*
* QtInputHandler - Qt-based input mapping for GUI mode
*/
#pragma once
#include <mutex>
#include <queue>
#include "InputHandler.h"
class QKeyEvent;
class QtInputHandler final : public InputHandler {
public:
QtInputHandler() = default;
~QtInputHandler() override = default;
void Attach(Editor *ed) override
{
ed_ = ed;
}
// Translate a Qt key event to editor command and enqueue if applicable.
// Returns true if it produced a mapped command or consumed input.
bool ProcessKeyEvent(const QKeyEvent &e);
bool Poll(MappedInput &out) override;
private:
std::mutex mu_;
std::queue<MappedInput> q_;
bool k_prefix_ = false;
bool k_ctrl_pending_ = false; // C-k C-… qualifier
bool esc_meta_ = false; // ESC-prefix for next key
bool suppress_text_input_once_ = false; // reserved (Qt sends text in keyPressEvent)
Editor *ed_ = nullptr;
};
-76
View File
@@ -1,76 +0,0 @@
#include "QtRenderer.h"
#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QFont>
#include <QFontMetrics>
#include "Editor.h"
namespace {
class EditorWidget : public QWidget {
public:
explicit EditorWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setAttribute(Qt::WA_OpaquePaintEvent);
setFocusPolicy(Qt::StrongFocus);
}
void SetEditor(Editor *ed)
{
ed_ = ed;
}
protected:
void paintEvent(QPaintEvent *event) override
{
Q_UNUSED(event);
QPainter p(this);
// Background
const QColor bg(28, 28, 30);
p.fillRect(rect(), bg);
// Font and metrics
QFont f("JetBrains Mono", 13);
p.setFont(f);
QFontMetrics fm(f);
const int line_h = fm.height();
// Title
p.setPen(QColor(220, 220, 220));
p.drawText(8, fm.ascent() + 4, QStringLiteral("kte (Qt frontend)"));
// Status bar at bottom
const int bar_h = line_h + 6; // padding
const int bar_y = height() - bar_h;
QRect status_rect(0, bar_y, width(), bar_h);
p.fillRect(status_rect, QColor(40, 40, 44));
p.setPen(QColor(180, 180, 140));
if (ed_) {
const QString status = QString::fromStdString(ed_->Status());
// draw at baseline within the bar
const int baseline = bar_y + 3 + fm.ascent();
p.drawText(8, baseline, status);
}
}
private:
Editor *ed_ = nullptr;
};
} // namespace
void
QtRenderer::Draw(Editor &ed)
{
if (!widget_)
return;
// If our widget is an EditorWidget, pass the editor pointer for painting
if (auto *ew = dynamic_cast<EditorWidget *>(widget_)) {
ew->SetEditor(&ed);
}
// Request a repaint
widget_->update();
}
-27
View File
@@ -1,27 +0,0 @@
/*
* QtRenderer - minimal Qt-based renderer
*/
#pragma once
#include "Renderer.h"
class QWidget;
class QtRenderer final : public Renderer {
public:
QtRenderer() = default;
~QtRenderer() override = default;
void Attach(QWidget *widget)
{
widget_ = widget;
}
void Draw(Editor &ed) override;
private:
QWidget *widget_ = nullptr; // not owned
};
+4 -4
View File
@@ -938,7 +938,7 @@ pub enum CommandError {
**Current C++ Implementation:** **Current C++ Implementation:**
- `Frontend` trait with Init/Step/Shutdown lifecycle - `Frontend` trait with Init/Step/Shutdown lifecycle
- Three implementations: Terminal (ncurses), ImGui (SDL2/OpenGL), Qt - Two implementations: Terminal (ncurses), ImGui (SDL2/OpenGL)
- Step() does one iteration: poll input, dispatch commands, render - Step() does one iteration: poll input, dispatch commands, render
- Each frontend owns InputHandler and Renderer instances - Each frontend owns InputHandler and Renderer instances
@@ -986,7 +986,7 @@ pub enum FrontendError {
- `InputHandler` trait with Poll() returning `MappedInput` - `InputHandler` trait with Poll() returning `MappedInput`
- `MappedInput`: hasCommand bool, CommandId, arg string, count - `MappedInput`: hasCommand bool, CommandId, arg string, count
- Terminal: ncurses-based, translates keys to commands - Terminal: ncurses-based, translates keys to commands
- ImGui/Qt: event-based, handles text input differently - ImGui: event-based, handles text input differently
- Keymap lookups via `KKeymap` functions (KCommand, Ctrl, Esc) - Keymap lookups via `KKeymap` functions (KCommand, Ctrl, Esc)
**Rust Design:** **Rust Design:**
@@ -1075,7 +1075,7 @@ impl InputHandler for TerminalInputHandler {
- `Renderer` trait with single Draw(Editor&) method - `Renderer` trait with single Draw(Editor&) method
- Terminal: ncurses-based, manual cursor positioning - Terminal: ncurses-based, manual cursor positioning
- ImGui/Qt: retained mode GUI, draw calls within Begin/End blocks - ImGui: immediate mode GUI, draw calls within Begin/End blocks
- Syntax highlighting integrated via Buffer::Highlighter() - Syntax highlighting integrated via Buffer::Highlighter()
**Rust Design:** **Rust Design:**
@@ -1200,7 +1200,7 @@ impl Frontend for TerminalFrontend {
#### 9.2 GUI Frontend (egui-based) #### 9.2 GUI Frontend (egui-based)
For GUI, consider replacing ImGui/Qt with `egui` (pure Rust, immediate For GUI, consider replacing ImGui with `egui` (pure Rust, immediate
mode): mode):
```rust ```rust
+3 -4
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
# Fix up a macOS .app bundle by copying non-Qt dylibs into # Fix up a macOS .app bundle by copying non-system dylibs into
# Contents/Frameworks and rewriting install names to use @rpath/@loader_path. # Contents/Frameworks and rewriting install names to use @rpath/@loader_path.
# #
# Usage: # Usage:
@@ -29,9 +29,8 @@ set(DIRS
) )
# Note: We pass empty plugin list so fixup_bundle scans the executable and all # Note: We pass empty plugin list so fixup_bundle scans the executable and all
# libs it references recursively. Qt frameworks already live in the bundle after # libs it references recursively, copying every non-system dylib (e.g. Homebrew
# macdeployqt; this step is primarily for non-Qt dylibs (glib, icu, pcre2, zstd, # SDL2, freetype, pcre2) into Contents/Frameworks; SDL3 is handled below.
# dbus, etc.).
# fixup_bundle often fails if copied libraries are read-only. # fixup_bundle often fails if copied libraries are read-only.
# We also try to use the system install_name_tool and otool to avoid issues with Anaconda's version. # We also try to use the system install_name_tool and otool to avoid issues with Anaconda's version.
# Note: BundleUtilities uses find_program(gp_otool "otool") internally, so we might need to set it differently. # Note: BundleUtilities uses find_program(gp_otool "otool") internally, so we might need to set it differently.
+3 -16
View File
@@ -8,13 +8,10 @@
SDL2, SDL2,
libGL, libGL,
xorg, xorg,
kdePackages,
qt6Packages ? kdePackages.qt6Packages,
installShellFiles, installShellFiles,
copyDesktopItems, copyDesktopItems,
makeDesktopItem, makeDesktopItem,
graphical ? false, graphical ? false,
graphical-qt ? false,
}: }:
let let
cmakeContent = builtins.readFile ./CMakeLists.txt; cmakeContent = builtins.readFile ./CMakeLists.txt;
@@ -25,7 +22,7 @@ let
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine); version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
in in
stdenv.mkDerivation { stdenv.mkDerivation {
pname = if graphical then (if graphical-qt then "kge-qt" else "kge") else "kte"; pname = if graphical then "kge" else "kte";
inherit version; inherit version;
src = lib.cleanSource ./.; src = lib.cleanSource ./.;
@@ -36,8 +33,6 @@ stdenv.mkDerivation {
installShellFiles installShellFiles
] ++ lib.optionals graphical [ ] ++ lib.optionals graphical [
copyDesktopItems copyDesktopItems
] ++ lib.optionals graphical-qt [
qt6Packages.wrapQtAppsHook
]; ];
buildInputs = [ buildInputs = [
@@ -47,14 +42,10 @@ stdenv.mkDerivation {
SDL2 SDL2
libGL libGL
xorg.libX11 xorg.libX11
] ++ lib.optionals graphical-qt [
kdePackages.qt6ct
qt6Packages.qtbase
]; ];
cmakeFlags = [ cmakeFlags = [
"-DBUILD_GUI=${if graphical then "ON" else "OFF"}" "-DBUILD_GUI=${if graphical then "ON" else "OFF"}"
"-DKTE_USE_QT=${if graphical-qt then "ON" else "OFF"}"
"-DCMAKE_BUILD_TYPE=Debug" "-DCMAKE_BUILD_TYPE=Debug"
"-DKTE_STATIC_LINK=OFF" "-DKTE_STATIC_LINK=OFF"
]; ];
@@ -65,7 +56,7 @@ stdenv.mkDerivation {
desktopName = "kge"; desktopName = "kge";
genericName = "Text Editor"; genericName = "Text Editor";
comment = "kyle's graphical text editor"; comment = "kyle's graphical text editor";
exec = if graphical-qt then "kge-qt %F" else "kge %F"; exec = "kge %F";
icon = "kge"; icon = "kge";
terminal = false; terminal = false;
categories = [ "Utility" "TextEditor" "Development" ]; categories = [ "Utility" "TextEditor" "Development" ];
@@ -91,11 +82,7 @@ stdenv.mkDerivation {
installManPage ../docs/kte.1 installManPage ../docs/kte.1
${lib.optionalString graphical '' ${lib.optionalString graphical ''
${if graphical-qt then ''
cp kge $out/bin/kge-qt
'' else ''
cp kge $out/bin/kge cp kge $out/bin/kge
''}
installManPage ../docs/kge.1 installManPage ../docs/kge.1
mkdir -p $out/share/icons/hicolor/256x256/apps mkdir -p $out/share/icons/hicolor/256x256/apps
@@ -108,6 +95,6 @@ stdenv.mkDerivation {
meta = { meta = {
description = "kyle's text editor" + lib.optionalString graphical " (graphical)"; description = "kyle's text editor" + lib.optionalString graphical " (graphical)";
platforms = lib.platforms.unix; platforms = lib.platforms.unix;
mainProgram = if graphical then (if graphical-qt then "kge-qt" else "kge") else "kte"; mainProgram = if graphical then "kge" else "kte";
}; };
} }
+1 -3
View File
@@ -20,7 +20,7 @@ kte follows a clean separation of concerns with three main layers:
``` ```
┌─────────────────────────────────────────┐ ┌─────────────────────────────────────────┐
│ Frontend Layer (Terminal/ImGui/Qt) │ │ Frontend Layer (Terminal/ImGui) │
│ - TerminalFrontend / ImGuiFrontend │ │ - TerminalFrontend / ImGuiFrontend │
│ - InputHandler + Renderer interfaces │ │ - InputHandler + Renderer interfaces │
└─────────────────────────────────────────┘ └─────────────────────────────────────────┘
@@ -154,7 +154,6 @@ Implementations:
`TerminalInputHandler`, `TerminalRenderer`) `TerminalInputHandler`, `TerminalRenderer`)
- **ImGui**: Dear ImGui-based (`ImGuiFrontend`, `ImGuiInputHandler`, - **ImGui**: Dear ImGui-based (`ImGuiFrontend`, `ImGuiInputHandler`,
`ImGuiRenderer`) `ImGuiRenderer`)
- **Qt**: Qt-based (`QtFrontend`, `QtInputHandler`, `QtRenderer`)
- **Test**: Programmatic testing (`TestFrontend`, `TestInputHandler`, - **Test**: Programmatic testing (`TestFrontend`, `TestInputHandler`,
`TestRenderer`) `TestRenderer`)
@@ -196,7 +195,6 @@ Large files that may need attention:
into logical groups into logical groups
- `Swap.cc` (1300 lines) - Crash recovery system (migrated to direct - `Swap.cc` (1300 lines) - Crash recovery system (migrated to direct
PieceTable operations) PieceTable operations)
- `QtFrontend.cc` (985 lines) - Qt integration
- `ImGuiRenderer.cc` (930 lines) - ImGui rendering - `ImGuiRenderer.cc` (930 lines) - ImGui rendering
- `PieceTable.cc` (800 lines) - Core data structure - `PieceTable.cc` (800 lines) - Core data structure
- `Buffer.cc` (763 lines) - Document model - `Buffer.cc` (763 lines) - Document model
-124
View File
@@ -1,124 +0,0 @@
Based on the project structure and the presence of files like
`imgui.ini`, `GUIFrontend.h`, and `TerminalFrontend.h`, here is an
analysis of the difficulty and challenges involved in adding a GTK or Qt
version of the GUI.
### **Executive Summary: Difficulty Level - Moderate**
The project is well-architected for this task. It already supports
multiple frontends (Terminal vs. GUI), meaning the "Core Logic" (
Buffers, Syntax, Commands) is successfully decoupled from the "View" (
Rendering/Input). However, the specific move from an **Immediate Mode**
GUI (likely Dear ImGui, implied by `imgui.ini` and standard naming
patterns) to a **Retained Mode** GUI (Qt/GTK) introduces specific
architectural frictions regarding the event loop and state management.
---
### **1. Architectural Analysis**
The existence of abstract interfaces—likely `Frontend`, `Renderer`, and
`InputHandler`—is the biggest asset here.
* **Current State:**
* **Abstract Layer:** `Frontend.h`, `Renderer.h`, `InputHandler.h`
likely define the contract.
* **Implementations:**
* `Terminal*` files implement the TUI (likely ncurses or VT100).
* `GUI*` files (currently ImGui) implement the graphical
version.
* **The Path Forward:**
* You would create `QtFrontend`, `QtRenderer`, `QtInputHandler` (or
GTK equivalents).
* Because the core logic (`Editor.cc`, `Buffer.cc`) calls these
interfaces, you theoretically don't need to touch the core text
manipulation code.
### **2. Key Challenges**
#### **A. The Event Loop Inversion (Main Challenge)**
* **Current (ImGui):** Typically, the application owns the loop:
`while (running) { HandleInput(); Update(); Render(); }`. The
application explicitly tells the GUI to draw every frame.
* **Target (Qt/GTK):** The framework owns the loop: `app.exec()` or
`gtk_main()`. The framework calls *you* when events happen.
* **Difficulty:** You will need to refactor `main.cc` or the entry point
to hand over control to the Qt/GTK application object. The Editor's "
tick" function might need to be connected to a timer or an idle event
in the new framework to ensure logic updates happen.
#### **B. Rendering Paradigm: Canvas vs. Widgets**
* **The "Easy" Way (Custom Canvas):**
* Implement the `QtRenderer` by subclassing `QWidget` and overriding
`paintEvent`.
* Use `QPainter` (or Cairo in GTK) to draw text, cursors, and
selections exactly where the `Renderer` interface says to.
* **Pros:** Keeps the code similar to the current ImGui/Terminal
renderers.
* **Cons:** You lose native accessibility and some native "feel" (
scrolling physics, native text context menus).
* **The "Hard" Way (Native Widgets):**
* Trying to map an internal `Buffer` directly to a `QTextEdit` or
`GtkTextView`.
* **Difficulty:** This is usually very hard because the Editor core
likely manages its own cursor, selection, and syntax highlighting.
Syncing that internal state with a complex native widget often
leads to conflicts.
* **Recommendation:** Stick to the "Custom Canvas" approach (drawing
text manually on a surface) to preserve the custom editor
behavior (vim-like modes, specific syntax highlighting).
#### **C. Input Handling**
* **Challenge:** Mapping Qt/GTK key events to the internal `Keymap`.
* **Detail:** ImGui and Terminal libraries often provide raw scancodes
or simple chars. Qt/GTK provide complex Event objects. You will need a
translation layer in `QtInputHandler::keyPressEvent` that converts
`Qt::Key_Escape` -> `KKey::Escape` (or your internal equivalent).
### **3. Portability of Assets**
#### **Themes (Colors)**
* **Feasibility:** High.
* **Approach:** `GUITheme.h` likely contains structs with RGB/Hex
values. Qt supports stylesheets (QSS) and GTK uses CSS. You can write
a converter that reads your current theme configuration and generates
a CSS string to apply to your window, or simply use the RGB values
directly in your custom `QPainter`/Cairo drawing logic.
#### **Fonts**
* **Feasibility:** Moderate.
* **Approach:**
* **ImGui:** Usually loads a TTF into a texture atlas.
* **Qt/GTK:** Uses the system font engine (Freetype/Pango).
* **Challenge:** You won't use the texture atlas anymore. You will
simply request a font family and size (e.g.,
`QFont("JetBrains Mono", 12)`). You may need to ensure your custom
renderer calculates character width/height metrics correctly using
`QFontMetrics` (Qt) or `PangoLayout` (GTK) to align the grid
correctly.
### **4. Summary Recommendation**
If you proceed, **Qt** is generally considered easier to integrate with
C++ projects than GTK (which is C-based, though `gtkmm` exists).
1. **Create a `QtFrontend`** class inheriting from `Frontend`.
2. **Create a `QtWindow`** class inheriting from `QWidget`.
3. **Implement `QtRenderer`** that holds a pointer to the `QtWindow`.
When the core calls `DrawText()`, `QtRenderer` should queue that
command or draw directly to the widget's paint buffer.
4. **Refactor `main.cc`** to instantiate `QApplication` instead of the
current manual loop.
---
Note (2025-12): The Qt frontend defers all key processing to the
existing command subsystem and keymaps, mirroring the ImGui path. There
are no Qt-only keybindings; `QtInputHandler` translates Qt key events
into the shared keymap flow (C-k prefix, Ctrl chords, ESC/Meta,
universal-argument digits, printable insertion).
+2 -7
View File
@@ -12,9 +12,8 @@
packages = eachSystem (system: rec { packages = eachSystem (system: rec {
default = kte; default = kte;
full = kge; full = kge;
kte = (pkgsFor system).callPackage ./default.nix { graphical = false; graphical-qt = false; }; kte = (pkgsFor system).callPackage ./default.nix { graphical = false; };
kge = (pkgsFor system).callPackage ./default.nix { graphical = true; graphical-qt = false; }; kge = (pkgsFor system).callPackage ./default.nix { graphical = true; };
qt = (pkgsFor system).callPackage ./default.nix { graphical = true; graphical-qt = true; };
}); });
devShells = eachSystem (system: devShells = eachSystem (system:
@@ -27,10 +26,6 @@
terminal = pkgs.mkShell { terminal = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.kte ]; inputsFrom = [ self.packages.${system}.kte ];
}; };
qt = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.qt ];
packages = with pkgs; [ gdb valgrind ];
};
} }
); );
-4
View File
@@ -25,12 +25,8 @@
#include "ErrorHandler.h" #include "ErrorHandler.h"
#if defined(KTE_BUILD_GUI) #if defined(KTE_BUILD_GUI)
#if defined(KTE_USE_QT)
#include "QtFrontend.h"
#else
#include "ImGuiFrontend.h" #include "ImGuiFrontend.h"
#endif #endif
#endif
#ifndef KTE_VERSION_STR #ifndef KTE_VERSION_STR
-16
View File
@@ -14,19 +14,3 @@ zip -r kge.app.zip kge.app
sha256sum kge.app.zip sha256sum kge.app.zip
open . open .
cd .. cd ..
# Qt build disabled — ImGui frontend is the primary GUI.
# mkdir -p cmake-build-release-qt
# cmake -S . -B cmake-build-release-qt -DBUILD_GUI=ON -DKTE_USE_QT=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_ASAN=OFF
#
# cd cmake-build-release-qt
# make clean
# rm -fr kge.app* kge-qt.app*
# make
# mv -f kge.app kge-qt.app
# macdeployqt kge-qt.app -always-overwrite -verbose=3
# cmake -DAPP_BUNDLE="$(pwd)/kge-qt.app" -P "${PWD%/*}/cmake/fix_bundle.cmake"
# zip -r kge-qt.app.zip kge-qt.app
# sha256sum kge-qt.app.zip
# open .
# cd ..