Browse Source

fix(build): resolve GTK3/GTK4 symbol conflict by splitting binaries

- Split build into 'lumacs' (GTK) and 'lumacs-raylib' (Raylib) targets.
- Refactor 'gtk_editor' to isolate GTK headers and dependencies from main.cpp.
- Update CMakeLists.txt to manage separate targets and dependencies.
- Update main.cpp to auto-select backend based on available build configuration.
- Add Phase 8 to PLAN.md.
Bernardo Magri 1 week ago
parent
commit
b9976750bd
8 changed files with 559 additions and 112 deletions
  1. 53 36
      CMakeLists.txt
  2. 19 1
      documentation/PLAN.md
  3. 1 73
      include/lumacs/gtk_editor.hpp
  4. 57 0
      include/lumacs/raylib_editor.hpp
  5. 1 0
      shell.nix
  6. 64 0
      src/gtk_editor.cpp
  7. 19 2
      src/main.cpp
  8. 345 0
      src/raylib_editor.cpp

+ 53 - 36
CMakeLists.txt

@@ -57,14 +57,12 @@ find_package(GTest)
 
 if(GTest_FOUND)
     message(STATUS "Found system GoogleTest: ${GTest_VERSION}")
-    # Note: System GTest exports targets as GTest::gtest and GTest::gtest_main
 else()
     message(STATUS "System GoogleTest not found. Fetching via Git...")
-    
     FetchContent_Declare(
         googletest
         GIT_REPOSITORY https://github.com/google/googletest.git
-        GIT_TAG        v1.15.2  # Keep this updated to prevent the MacOS CMake 4.1 error!
+        GIT_TAG        v1.15.2
     )
     FetchContent_MakeAvailable(googletest)
 endif()
@@ -98,7 +96,7 @@ add_library(lumacs_core STATIC
 target_include_directories(lumacs_core PUBLIC
     ${CMAKE_CURRENT_SOURCE_DIR}/include
     ${LUA_INCLUDE_DIR}
-    ${sol2_SOURCE_DIR}/include # Explicitly add sol2 include directory
+    ${sol2_SOURCE_DIR}/include
 )
 
 target_link_libraries(lumacs_core PUBLIC
@@ -108,51 +106,70 @@ target_link_libraries(lumacs_core PUBLIC
     ${CURSES_LIBRARIES}
 )
 
-
-# Main Executable (Single binary)
-add_executable(lumacs
-    src/main.cpp
-    src/tui_editor.cpp
-    src/gtk_editor.cpp
-    src/gtk_renderer.cpp
-    src/gtk_completion_popup.cpp
-    src/command_system.cpp
-)
-
-target_link_libraries(lumacs PRIVATE
-    lumacs_core
-    ${CURSES_LIBRARIES}
-)
-
-target_include_directories(lumacs PRIVATE
-    ${CURSES_INCLUDE_DIR}
-)
-
-
-# Helper variable for your flags
+# Helper variable for compile flags
 if(MSVC)
     set(LUMACS_WARNINGS /W4 /WX)
 else()
     set(LUMACS_WARNINGS -Wall -Wextra -Wpedantic -Werror)
 endif()
 
-# Apply ONLY to your core library
 target_compile_options(lumacs_core PRIVATE ${LUMACS_WARNINGS})
 
-# Apply ONLY to your executable
-target_compile_options(lumacs PRIVATE ${LUMACS_WARNINGS})
-
+# Common sources for all frontends (Main entry point + TUI)
+set(LUMACS_COMMON_SOURCES
+    src/main.cpp
+    src/tui_editor.cpp
+)
 
-# Configure GTK if available
+# --- Target: lumacs (GTK preferred, TUI fallback) ---
 if(GTKMM_FOUND)
-    message(STATUS "GTKMM 4.0 found - Building with GUI support")
+    message(STATUS "GTKMM 4.0 found - Building 'lumacs' with GTK support")
+    add_executable(lumacs
+        ${LUMACS_COMMON_SOURCES}
+        src/gtk_editor.cpp
+        src/gtk_renderer.cpp
+        src/gtk_completion_popup.cpp
+    )
+    target_link_libraries(lumacs PRIVATE lumacs_core ${GTKMM_LIBRARIES})
+    target_include_directories(lumacs PRIVATE ${GTKMM_INCLUDE_DIRS} ${CURSES_INCLUDE_DIR})
     target_compile_definitions(lumacs PRIVATE LUMACS_WITH_GTK)
-    target_include_directories(lumacs PRIVATE ${GTKMM_INCLUDE_DIRS})
-    target_link_libraries(lumacs PRIVATE ${GTKMM_LIBRARIES})
 else()
-    message(WARNING "GTKMM 4.0 not found - Building TUI only")
+    message(WARNING "GTKMM 4.0 not found - 'lumacs' will be TUI only")
+    add_executable(lumacs ${LUMACS_COMMON_SOURCES})
+    target_link_libraries(lumacs PRIVATE lumacs_core)
+    target_include_directories(lumacs PRIVATE ${CURSES_INCLUDE_DIR})
+endif()
+
+target_compile_options(lumacs PRIVATE ${LUMACS_WARNINGS})
+
+# --- Target: lumacs-raylib (Raylib + TUI fallback) ---
+find_package(raylib QUIET)
+if(NOT raylib_FOUND)
+    pkg_check_modules(raylib raylib)
+endif()
+
+if(raylib_FOUND)
+    message(STATUS "Raylib found - Creating 'lumacs-raylib' target")
+    add_executable(lumacs-raylib
+        ${LUMACS_COMMON_SOURCES}
+        src/raylib_editor.cpp
+    )
+    target_link_libraries(lumacs-raylib PRIVATE lumacs_core)
+    
+    if(TARGET raylib)
+        target_link_libraries(lumacs-raylib PRIVATE raylib)
+    else()
+        target_include_directories(lumacs-raylib PRIVATE ${raylib_INCLUDE_DIRS})
+        target_link_libraries(lumacs-raylib PRIVATE ${raylib_LIBRARIES})
+    endif()
+
+    target_include_directories(lumacs-raylib PRIVATE ${CURSES_INCLUDE_DIR})
+    target_compile_definitions(lumacs-raylib PRIVATE LUMACS_WITH_RAYLIB)
+    target_compile_options(lumacs-raylib PRIVATE ${LUMACS_WARNINGS})
+else()
+    message(STATUS "Raylib not found - Skipping 'lumacs-raylib'")
 endif()
 
 # Enable testing
 enable_testing()
-add_subdirectory(tests)
+add_subdirectory(tests)

+ 19 - 1
documentation/PLAN.md

@@ -325,4 +325,22 @@ Comprehensive overhaul of the Lua integration to provide a complete Emacs-like e
 
 | Date | Issue # | Description | Build Status |
 |------|---------|-------------|--------------|
-| 2025-12-04 | 7.1-7.8 | Complete Lua integration overhaul | ✅ Pass (32 tests) |
+| 2025-12-04 | 7.1-7.8 | Complete Lua integration overhaul | ✅ Pass (32 tests) |
+
+---
+
+## Phase 8: Platform Compatibility and Stability (December 2024)
+
+Focused on ensuring Lumacs runs correctly on different platforms and configurations, addressing critical bugs and conflicts.
+
+### Issues
+
+| # | Issue | Status | Notes |
+|---|-------|--------|-------|
+| 8.1 | Fix GTK3/GTK4 Conflict with Raylib | 🟢 Completed | Split binary into `lumacs` (GTK) and `lumacs-raylib` (Raylib) |
+
+### Change Log (Phase 8)
+
+| Date | Issue # | Description | Build Status |
+|------|---------|-------------|--------------|
+| 2025-12-26 | 8.1 | Resolved GTK3 (Raylib) vs GTK4 (Lumacs) symbol conflict by creating separate `lumacs` and `lumacs-raylib` binaries. Refactored `gtk_editor` to isolate GTK dependencies. | ✅ Pass |

+ 1 - 73
include/lumacs/gtk_editor.hpp

@@ -1,82 +1,10 @@
 #pragma once
 
 #include "lumacs/ui_interface.hpp"
-#include "lumacs/gtk_renderer.hpp"
-#include "lumacs/window.hpp"
-#include "lumacs/editor_core.hpp"
-#include "lumacs/face.hpp"
-#include "lumacs/completion_common.hpp" // For CompletionCandidate
-#include "lumacs/cursor_blink.hpp"
-
-#include <chrono> // For std::chrono
-#include <gtkmm.h>
-#include <pangomm.h>
 #include <memory>
-#include <vector>
-#include <map>
 
 namespace lumacs {
 
-// Forward declarations
-class EditorCore;
-class Window;
-class GtkCompletionPopup; // Forward declaration
-class ModeActivator; // Forward declaration
-
-// Custom Gtk::ApplicationWindow to make constructor public
-class LumacsWindow : public Gtk::ApplicationWindow {
-public:
-    explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application);
-};
-
-class GtkEditor : public IEditorView {
-public:
-    GtkEditor();
-    ~GtkEditor() override;
-
-    void init() override;
-    void run() override;
-    void handle_editor_event(EditorEvent event) override;
-    void set_core(EditorCore* core) override;
-
-    void queue_redraw_all_windows(Gtk::Widget* widget);
-
-private:
-    EditorCore* core_;
-    std::shared_ptr<Window> cached_active_window_;
-
-    Glib::RefPtr<Gtk::Application> app_;
-    Gtk::Window* window_ = nullptr;
-    Gtk::DrawingArea* drawing_area_ = nullptr; // Raw pointer to the main drawing area if single window
-    Gtk::DrawingArea* minibuffer_drawing_area_ = nullptr; // Dedicated drawing area for minibuffer
-    Gtk::Widget* content_widget_ = nullptr; // The root widget of the editor content (Paned or DrawingArea)
-    
-    // Cursor blinking using shared controller
-    CursorBlinkController cursor_blink_;
-    sigc::connection cursor_timer_connection_;
-
-    std::unique_ptr<GtkRenderer> gtk_renderer_;
-    std::unique_ptr<GtkCompletionPopup> completion_popup_; // Completion popup
-    std::unique_ptr<ModeActivator> mode_activator_;
-
-protected:
-    void on_activate();
-    void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
-    bool on_cursor_blink();
-    void rebuild_layout();
-    Gtk::Widget* create_widget_for_layout_node(std::shared_ptr<LayoutNode> node);
-
-    // Completion popup helpers
-    void show_completion_popup();
-    void hide_completion_popup();
-    void on_completion_selected(CompletionCandidate candidate);
-    void on_completion_cancelled();
-
-    // Global key event handler
-    std::string get_lumacs_key_name(guint keyval, Gdk::ModifierType state);
-    bool on_global_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
-};
-
 std::unique_ptr<IEditorView> create_gtk_editor();
 
-} // namespace lumacs
+} // namespace lumacs

+ 57 - 0
include/lumacs/raylib_editor.hpp

@@ -0,0 +1,57 @@
+#pragma once
+
+#include "lumacs/ui_interface.hpp"
+
+#include <memory>
+
+#include <raylib.h>
+
+
+
+namespace lumacs {
+
+
+
+class Window;
+
+struct LayoutNode;
+
+
+
+class RaylibEditor : public IEditorView {
+
+public:
+    RaylibEditor();
+    ~RaylibEditor() override;
+
+    void init() override;
+    void run() override;
+    void handle_editor_event(EditorEvent event) override;
+    void set_core(EditorCore* core) override;
+
+private:
+    void update();
+    void render();
+    
+    // Input handling helpers
+    void process_input();
+    
+    // Rendering helpers
+    void draw_layout(LayoutNode* node, Rectangle area);
+    void draw_window(Window& window, Rectangle area);
+    void draw_modeline(Window& window, Rectangle area);
+    void draw_minibuffer(Rectangle area);
+
+    EditorCore* core_ = nullptr;
+    bool should_close_ = false;
+    
+    // Graphics Resources
+    Font font_;
+    int font_size_ = 20;
+    int char_width_ = 10;
+    int line_height_ = 20;
+};
+
+std::unique_ptr<IEditorView> create_raylib_editor();
+
+} // namespace lumacs

+ 1 - 0
shell.nix

@@ -33,6 +33,7 @@ pkgs.mkShell {
     sol2
     pcre2
     spdlog
+    raylib
   ];
 
   shellHook = ''

+ 64 - 0
src/gtk_editor.cpp

@@ -14,6 +14,10 @@
 #include "lumacs/macro_manager.hpp" // Include for MacroManager
 #include "lumacs/rectangle_manager.hpp" // Include for RectangleManager
 #include "lumacs/logger.hpp"
+#include "lumacs/face.hpp"
+#include "lumacs/completion_common.hpp"
+#include "lumacs/cursor_blink.hpp"
+#include "lumacs/window.hpp"
 #include <spdlog/spdlog.h>
 #include <filesystem>
 #include <vector>
@@ -28,6 +32,66 @@
 
 namespace lumacs {
 
+// Forward declarations
+class EditorCore;
+class Window;
+class GtkCompletionPopup; // Forward declaration
+class ModeActivator; // Forward declaration
+
+// Custom Gtk::ApplicationWindow to make constructor public
+class LumacsWindow : public Gtk::ApplicationWindow {
+public:
+    explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application);
+};
+
+class GtkEditor : public IEditorView {
+public:
+    GtkEditor();
+    ~GtkEditor() override;
+
+    void init() override;
+    void run() override;
+    void handle_editor_event(EditorEvent event) override;
+    void set_core(EditorCore* core) override;
+
+    void queue_redraw_all_windows(Gtk::Widget* widget);
+
+private:
+    EditorCore* core_;
+    std::shared_ptr<Window> cached_active_window_;
+
+    Glib::RefPtr<Gtk::Application> app_;
+    Gtk::Window* window_ = nullptr;
+    Gtk::DrawingArea* drawing_area_ = nullptr; // Raw pointer to the main drawing area if single window
+    Gtk::DrawingArea* minibuffer_drawing_area_ = nullptr; // Dedicated drawing area for minibuffer
+    Gtk::Widget* content_widget_ = nullptr; // The root widget of the editor content (Paned or DrawingArea)
+    
+    // Cursor blinking using shared controller
+    CursorBlinkController cursor_blink_;
+    sigc::connection cursor_timer_connection_;
+
+    std::unique_ptr<GtkRenderer> gtk_renderer_;
+    std::unique_ptr<GtkCompletionPopup> completion_popup_; // Completion popup
+    std::unique_ptr<ModeActivator> mode_activator_;
+
+protected:
+    void on_activate();
+    void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
+    bool on_cursor_blink();
+    void rebuild_layout();
+    Gtk::Widget* create_widget_for_layout_node(std::shared_ptr<LayoutNode> node);
+
+    // Completion popup helpers
+    void show_completion_popup();
+    void hide_completion_popup();
+    void on_completion_selected(CompletionCandidate candidate);
+    void on_completion_cancelled();
+
+    // Global key event handler
+    std::string get_lumacs_key_name(guint keyval, Gdk::ModifierType state);
+    bool on_global_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
+};
+
 // LumacsWindow method implementations
 LumacsWindow::LumacsWindow(const Glib::RefPtr<Gtk::Application>& application)
     : Gtk::ApplicationWindow(application) {

+ 19 - 2
src/main.cpp

@@ -1,6 +1,11 @@
 #include "lumacs/editor_core.hpp"
 #include "lumacs/tui_editor.hpp"
+#ifdef LUMACS_WITH_GTK
 #include "lumacs/gtk_editor.hpp"
+#endif
+#ifdef LUMACS_WITH_RAYLIB
+#include "lumacs/raylib_editor.hpp"
+#endif
 #include "lumacs/logger.hpp"
 #include "lumacs/lua_api.hpp" // Added for load_init_file
 #include <iostream>
@@ -15,6 +20,7 @@ void print_help() {
     std::cout << "Usage: lumacs [options] [file]\n"
               << "Options:\n"
               << "  -nw, --no-window   Run in terminal mode (TUI)\n"
+              << "  --raylib           Run with Raylib frontend\n"
               << "  --help             Show this help message\n";
 }
 
@@ -24,6 +30,7 @@ int main(int argc, char* argv[]) {
     spdlog::info("Lumacs starting up...");
 
     bool force_tui = false;
+    bool force_raylib = false;
     std::string filename;
 
     // Parse arguments
@@ -31,6 +38,8 @@ int main(int argc, char* argv[]) {
         std::string arg = argv[i];
         if (arg == "-nw" || arg == "--no-window") {
             force_tui = true;
+        } else if (arg == "--raylib") {
+            force_raylib = true;
         } else if (arg == "--help") {
             print_help();
             return 0;
@@ -50,12 +59,20 @@ int main(int argc, char* argv[]) {
         std::unique_ptr<IEditorView> view;
 
 #ifdef LUMACS_WITH_GTK
-        if (!force_tui) {
+        if (!force_tui && !force_raylib) {
             view = create_gtk_editor();
         }
 #endif
 
-        // Fallback to TUI if GTK not available or TUI forced
+#ifdef LUMACS_WITH_RAYLIB
+        if (!view && !force_tui) {
+            // If GTK wasn't created (or not requested), and TUI not forced, try Raylib.
+            (void)force_raylib; // Silence unused variable warning if GTK is disabled
+            view = create_raylib_editor();
+        }
+#endif
+
+        // Fallback to TUI if GTK/Raylib not available or TUI forced
         if (!view) {
             if (!force_tui && !view) {
                 // If we tried to load GTK and failed (e.g. create_gtk_editor returned null), warn user?

+ 345 - 0
src/raylib_editor.cpp

@@ -0,0 +1,345 @@
+#include "lumacs/raylib_editor.hpp"
+#include "lumacs/editor_core.hpp"
+#include "lumacs/logger.hpp"
+#include "lumacs/lua_api.hpp"
+#include "lumacs/keybinding.hpp"
+#include "lumacs/command_system.hpp"
+#include "lumacs/minibuffer_manager.hpp"
+#include "lumacs/window_manager.hpp"
+#include "lumacs/layout_node.hpp"
+#include "lumacs/theme.hpp"
+#include "lumacs/buffer.hpp"
+#include "lumacs/buffer_manager.hpp"
+#include <raylib.h>
+#include <spdlog/spdlog.h>
+#include <string>
+#include <vector>
+#include <fmt/core.h>
+
+namespace lumacs {
+
+// Helper to construct Raylib colors explicitly to avoid namespace collision with lumacs::Color
+static ::Color RL_Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255) {
+    return ::Color{r, g, b, a};
+}
+
+static ::Color ToRLColor(const lumacs::Color& c) {
+    if (c.r == -1) return RL_Color(0, 0, 0, 0); 
+    return RL_Color((unsigned char)c.r, (unsigned char)c.g, (unsigned char)c.b);
+}
+
+RaylibEditor::RaylibEditor() {
+}
+
+RaylibEditor::~RaylibEditor() {
+    if (IsWindowReady()) {
+        CloseWindow();
+    }
+}
+
+void RaylibEditor::init() {
+    InitWindow(1280, 800, "Lumacs (Raylib)");
+    SetTargetFPS(60);
+    SetExitKey(0); // Disable ESC to exit
+    
+    font_ = GetFontDefault();
+    font_size_ = 20; 
+    line_height_ = 20;
+    char_width_ = 10;
+    
+    const char* font_paths[] = {
+        "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
+        "/usr/share/fonts/TTF/DejaVuSansMono.ttf",
+        "resources/font.ttf"
+    };
+    
+    for (const char* path : font_paths) {
+        if (FileExists(path)) {
+            Font loaded = LoadFontEx(path, font_size_, 0, 0);
+            if (loaded.texture.id != 0) {
+                font_ = loaded;
+                SetTextureFilter(font_.texture, TEXTURE_FILTER_BILINEAR);
+                Vector2 size = MeasureTextEx(font_, "M", (float)font_size_, 1.0f);
+                char_width_ = (int)size.x;
+                line_height_ = (int)size.y;
+                spdlog::info("Loaded font: {}", path);
+                break;
+            }
+        }
+    }
+    
+    spdlog::info("Raylib initialized. Char size: {}x{}", char_width_, line_height_);
+}
+
+void RaylibEditor::set_core(EditorCore* core) {
+    core_ = core;
+}
+
+void RaylibEditor::handle_editor_event(EditorEvent event) {
+    if (event == EditorEvent::Quit) {
+        should_close_ = true;
+    }
+}
+
+void RaylibEditor::run() {
+    while (!WindowShouldClose() && !should_close_) {
+        update();
+        render();
+    }
+}
+
+void RaylibEditor::update() {
+    process_input();
+}
+
+static std::string resolve_raylib_key(int key, bool ctrl, bool alt, bool shift) {
+    std::string key_str;
+    switch (key) {
+        case KEY_ENTER: key_str = "Return"; break;
+        case KEY_BACKSPACE: key_str = "Backspace"; break;
+        case KEY_TAB: key_str = "Tab"; break;
+        case KEY_ESCAPE: key_str = "Escape"; break;
+        case KEY_DELETE: key_str = "Delete"; break;
+        case KEY_UP: key_str = "ArrowUp"; break;
+        case KEY_DOWN: key_str = "ArrowDown"; break;
+        case KEY_LEFT: key_str = "ArrowLeft"; break;
+        case KEY_RIGHT: key_str = "ArrowRight"; break;
+        case KEY_HOME: key_str = "Home"; break;
+        case KEY_END: key_str = "End"; break;
+        case KEY_PAGE_UP: key_str = "PageUp"; break;
+        case KEY_PAGE_DOWN: key_str = "PageDown"; break;
+        case KEY_F1: key_str = "F1"; break;
+        case KEY_F2: key_str = "F2"; break;
+        case KEY_F3: key_str = "F3"; break;
+        case KEY_F4: key_str = "F4"; break;
+        case KEY_F5: key_str = "F5"; break;
+        case KEY_F6: key_str = "F6"; break;
+        case KEY_F7: key_str = "F7"; break;
+        case KEY_F8: key_str = "F8"; break;
+        case KEY_F9: key_str = "F9"; break;
+        case KEY_F10: key_str = "F10"; break;
+        case KEY_F11: key_str = "F11"; break;
+        case KEY_F12: key_str = "F12"; break;
+        default: break;
+    }
+
+    if (key_str.empty()) {
+        if (key >= KEY_A && key <= KEY_Z) {
+            char c = (char)key;
+            if (!shift) c += 32;
+            key_str = std::string(1, c);
+        } else if (key >= KEY_ZERO && key <= KEY_NINE) {
+             key_str = std::string(1, (char)key);
+        } else {
+             switch(key) {
+                 case KEY_COMMA: key_str = ","; break;
+                 case KEY_PERIOD: key_str = "."; break;
+                 case KEY_SLASH: key_str = "/"; break;
+                 case KEY_SEMICOLON: key_str = ";"; break;
+                 case KEY_EQUAL: key_str = "="; break;
+                 case KEY_MINUS: key_str = "-"; break;
+                 case KEY_LEFT_BRACKET: key_str = "["; break;
+                 case KEY_RIGHT_BRACKET: key_str = "]"; break;
+                 case KEY_BACKSLASH: key_str = "\\"; break;
+                 case KEY_APOSTROPHE: key_str = "'"; break;
+                 case KEY_GRAVE: key_str = "`"; break;
+                 case KEY_SPACE: key_str = "Space"; break;
+             }
+        }
+    }
+
+    if (key_str.empty()) return "";
+
+    if (ctrl) key_str = "C-" + key_str;
+    if (alt) key_str = "M-" + key_str;
+    if (shift) {
+        if (key_str == "Tab") key_str = "S-Tab";
+    }
+    return key_str;
+}
+
+void RaylibEditor::process_input() {
+    bool ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);
+    bool alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT);
+    bool shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
+
+    int key = GetKeyPressed();
+    while (key != 0) {
+        std::string lumacs_key = resolve_raylib_key(key, ctrl, alt, shift);
+        bool is_printable = (key >= 32 && key <= 126); 
+        bool is_special = !is_printable || (key == KEY_SPACE);
+        bool should_handle = (ctrl || alt || is_special);
+
+        if (should_handle && !lumacs_key.empty()) {
+            bool handled_by_minibuffer = core_->minibuffer_manager().handle_key_event(lumacs_key);
+            if (!handled_by_minibuffer) {
+                KeyProcessingResult result = core_->keybinding_manager().process_key(lumacs_key);
+                if (result.type == KeyResult::Unbound) {
+                    if (lumacs_key == "Return") core_->command_system().execute("newline", {});
+                    else if (lumacs_key == "Backspace") core_->command_system().execute("delete-backward-char", {});
+                    else if (lumacs_key == "Delete") core_->command_system().execute("delete-char", {});
+                    else if (lumacs_key == "Tab") core_->command_system().execute("self-insert-command", {"\t"});
+                }
+            }
+        }
+        key = GetKeyPressed();
+    }
+
+    if (!ctrl && !alt) {
+        int char_code = GetCharPressed();
+        while (char_code != 0) {
+            if (char_code >= 32 && char_code <= 126) {
+                std::string text(1, (char)char_code);
+                bool handled_by_minibuffer = core_->minibuffer_manager().handle_key_event(text);
+                if (!handled_by_minibuffer) {
+                    KeyProcessingResult result = core_->keybinding_manager().process_key(text);
+                    if (result.type == KeyResult::Unbound) {
+                        core_->command_system().execute("self-insert-command", {text});
+                    }
+                }
+            }
+            char_code = GetCharPressed();
+        }
+    }
+}
+
+void RaylibEditor::render() {
+    BeginDrawing();
+    
+    auto theme = core_->theme_manager().active_theme();
+    ::Color bg = theme ? ToRLColor(theme->get_bg_color(ThemeElement::Normal)) : RL_Color(245, 245, 245);
+    ClearBackground(bg);
+    
+    if (core_->window_manager().root_layout()) {
+        float mini_h = (float)line_height_ * 1.5f;
+        Rectangle editor_area = {0, 0, (float)GetScreenWidth(), (float)GetScreenHeight() - mini_h};
+        Rectangle mini_area = {0, (float)GetScreenHeight() - mini_h, (float)GetScreenWidth(), mini_h};
+        
+        draw_layout(core_->window_manager().root_layout().get(), editor_area);
+        draw_minibuffer(mini_area);
+    }
+    
+    EndDrawing();
+}
+
+void RaylibEditor::draw_layout(LayoutNode* node, Rectangle area) {
+    if (!node) return;
+    
+    if (node->type == LayoutNode::Type::Leaf) {
+        if (node->window) {
+            draw_window(*node->window, area);
+        }
+    } else {
+        Rectangle area1 = area;
+        Rectangle area2 = area;
+        auto theme = core_->theme_manager().active_theme();
+        ::Color border = theme ? ToRLColor(theme->get_fg_color(ThemeElement::WindowBorder)) : RL_Color(0,0,0);
+        
+        if (node->type == LayoutNode::Type::HorizontalSplit) {
+            float h1 = area.height * node->ratio;
+            area1.height = h1;
+            area2.y += h1;
+            area2.height -= h1;
+            DrawLine((int)area.x, (int)area2.y, (int)area.width, (int)area2.y, border);
+        } else {
+            float w1 = area.width * node->ratio;
+            area1.width = w1;
+            area2.x += w1;
+            area2.width -= w1;
+            DrawLine((int)area2.x, (int)area.y, (int)area2.x, (int)area.height, border);
+        }
+        
+        if (node->child1) draw_layout(node->child1.get(), area1);
+        if (node->child2) draw_layout(node->child2.get(), area2);
+    }
+}
+
+void RaylibEditor::draw_window(Window& window, Rectangle area) {
+    Rectangle content_area = area;
+    content_area.height -= line_height_;
+    
+    int cols = (int)content_area.width / char_width_;
+    int lines = (int)content_area.height / line_height_;
+    if (cols < 1) cols = 1;
+    if (lines < 1) lines = 1;
+    window.set_viewport_size(cols, lines);
+    
+    auto theme = core_->theme_manager().active_theme();
+    ::Color bg = theme ? ToRLColor(theme->get_bg_color(ThemeElement::Normal)) : RL_Color(255,255,255);
+    ::Color fg = theme ? ToRLColor(theme->get_fg_color(ThemeElement::Normal)) : RL_Color(0,0,0);
+    
+    DrawRectangleRec(area, bg);
+    
+    BeginScissorMode((int)content_area.x, (int)content_area.y, (int)content_area.width, (int)content_area.height);
+    
+    auto range = window.visible_line_range();
+    float y = content_area.y;
+    
+    for (size_t i = range.first; i < range.second; ++i) {
+        if (i < window.buffer().line_count()) {
+            std::string line = window.buffer().line(i);
+            DrawTextEx(font_, line.c_str(), {content_area.x, y}, (float)font_size_, 1.0f, fg);
+            
+            if (i == window.cursor().line) {
+                float cx = content_area.x + (window.cursor().column * char_width_);
+                DrawRectangle((int)cx, (int)y, char_width_, line_height_, RL_Color(255, 0, 0, 128));
+            }
+        }
+        y += line_height_;
+    }
+    
+    EndScissorMode();
+    
+    Rectangle modeline_rect = {area.x, area.y + area.height - line_height_, area.width, (float)line_height_};
+    draw_modeline(window, modeline_rect);
+}
+
+void RaylibEditor::draw_modeline(Window& window, Rectangle area) {
+    auto theme = core_->theme_manager().active_theme();
+    ::Color bg = theme ? ToRLColor(theme->get_bg_color(ThemeElement::StatusLine)) : RL_Color(200,200,200);
+    ::Color fg = theme ? ToRLColor(theme->get_fg_color(ThemeElement::StatusLine)) : RL_Color(0,0,0);
+    
+    DrawRectangleRec(area, bg);
+    
+    std::string mode_name = "Fundamental"; 
+    std::string buf_name = window.buffer().name();
+    bool modified = window.buffer().is_modified();
+    
+    std::string text = fmt::format("{}{}  ({})  L:{} C:{}", 
+        modified ? "*" : "-", buf_name, mode_name, 
+        window.cursor().line + 1, window.cursor().column);
+        
+    DrawTextEx(font_, text.c_str(), {area.x + 5, area.y}, (float)font_size_, 1.0f, fg);
+}
+
+void RaylibEditor::draw_minibuffer(Rectangle area) {
+    auto theme = core_->theme_manager().active_theme();
+    ::Color bg = theme ? ToRLColor(theme->get_bg_color(ThemeElement::Normal)) : RL_Color(240,240,240); 
+    ::Color fg = theme ? ToRLColor(theme->get_fg_color(ThemeElement::Normal)) : RL_Color(0,0,0);
+    
+    DrawRectangleRec(area, bg);
+    DrawLine((int)area.x, (int)area.y, (int)area.width, (int)area.y, RL_Color(100,100,100));
+    
+    std::string msg = core_->last_message();
+    if (!msg.empty()) {
+        DrawTextEx(font_, msg.c_str(), {area.x + 5, area.y}, (float)font_size_, 1.0f, fg);
+    } else if (core_->minibuffer_manager().is_active()) {
+        std::string prompt = core_->minibuffer_manager().get_prompt();
+        std::string input = core_->minibuffer_manager().get_input_buffer();
+        std::string full = prompt + input;
+        
+        DrawTextEx(font_, full.c_str(), {area.x + 5, area.y}, (float)font_size_, 1.0f, fg);
+        
+        int cursor_idx = core_->minibuffer_manager().get_cursor_position();
+        float prompt_width = MeasureTextEx(font_, prompt.c_str(), (float)font_size_, 1.0f).x;
+        float input_before_cursor = MeasureTextEx(font_, input.substr(0, cursor_idx).c_str(), (float)font_size_, 1.0f).x;
+        
+        DrawRectangle((int)(area.x + 5 + prompt_width + input_before_cursor), (int)area.y, char_width_, line_height_, RL_Color(255, 0, 0, 128));
+    }
+}
+
+std::unique_ptr<IEditorView> create_raylib_editor() {
+    return std::make_unique<RaylibEditor>();
+}
+
+} // namespace lumacs