| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- #include "lumacs/gtk_editor.hpp"
- #include "lumacs/editor_core.hpp"
- #include "lumacs/lua_api.hpp"
- #include <iostream>
- // Check if GTK is enabled in build
- #ifdef LUMACS_WITH_GTK
- #include <gtkmm.h>
- #include <pangomm.h>
- namespace lumacs {
- // Custom Gtk::ApplicationWindow to make constructor public
- class LumacsWindow : public Gtk::ApplicationWindow {
- public:
- explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application)
- : Gtk::ApplicationWindow(application) {
- set_title("Lumacs - GTK4");
- set_default_size(1024, 768);
- }
- };
- class GtkEditor : public IEditorView {
- public:
- GtkEditor() : core_(nullptr) {}
- ~GtkEditor() override = default;
- void init() override {
- // Initialize GTK application
- app_ = Gtk::Application::create("org.lumacs.editor");
- app_->signal_activate().connect(sigc::mem_fun(*this, &GtkEditor::on_activate));
- }
- void run() override {
- // Run the application's event loop
- app_->run();
- }
- void handle_editor_event(EditorEvent event) override {
- // Request redraw on most events
- if (drawing_area_) {
- drawing_area_->queue_draw();
- }
-
- if (event == EditorEvent::Quit) {
- app_->quit(); // Quit the application gracefully
- }
- }
- void set_core(EditorCore* core) override {
- core_ = core;
- }
- private:
- EditorCore* core_;
- Glib::RefPtr<Gtk::Application> app_;
- // Window is managed by Gtk::Application
- Gtk::DrawingArea* drawing_area_ = nullptr;
- double char_width_ = 0;
- double line_height_ = 0;
- double ascent_ = 0;
- protected:
- void on_activate() {
- // Create main window and associate with the application
- auto window = Glib::RefPtr<LumacsWindow>(new LumacsWindow(app_));
-
- // Create drawing area for text rendering
- drawing_area_ = Gtk::make_managed<Gtk::DrawingArea>();
- drawing_area_->set_draw_func(sigc::mem_fun(*this, &GtkEditor::on_draw));
- drawing_area_->set_focusable(true);
-
- // Add to window
- window->set_child(*drawing_area_);
- // Input handling
- auto controller = Gtk::EventControllerKey::create();
- controller->signal_key_pressed().connect(sigc::mem_fun(*this, &GtkEditor::on_key_pressed), false);
- drawing_area_->add_controller(controller);
- // Show window
- window->present();
- drawing_area_->grab_focus();
- }
- // Rendering
- void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
- // Fill background
- auto theme = core_->active_theme();
- Color bg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
-
- cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
- cr->paint();
- // Create Pango layout
- auto layout = Pango::Layout::create(drawing_area_->get_pango_context());
-
- // Font configuration
- Pango::FontDescription font_desc("Monospace 12");
- layout->set_font_description(font_desc);
- // Get font metrics
- Pango::FontMetrics metrics = layout->get_context()->get_metrics(font_desc);
- line_height_ = (double)metrics.get_height() / PANGO_SCALE;
- ascent_ = (double)metrics.get_ascent() / PANGO_SCALE;
-
- // Measure character width (for a single 'm' character)
- layout->set_text("m");
- Pango::Rectangle ink_rect, logical_rect;
- layout->get_pixel_extents(ink_rect, logical_rect);
- char_width_ = (double)logical_rect.get_width() / PANGO_SCALE; // Use get_width()
- // Update core's viewport size based on actual font metrics
- int content_width_px = width; // Use actual width from parameter
- int content_height_px = height; // Use actual height from parameter
- int visible_lines = static_cast<int>(content_height_px / line_height_);
- int visible_cols = static_cast<int>(content_width_px / char_width_);
-
- // Leave 1 line for minibuffer at bottom, adjust content area
- int editor_lines = std::max(0, visible_lines - 1); // Reserve one line for future minibuffer
- core_->set_viewport_size(visible_cols, editor_lines);
- // Get default foreground color from theme
- // auto theme = core_->active_theme(); // Redundant, theme already defined
- Color fg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255); // Default to white
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- // Render visible lines
- const auto& buffer = core_->buffer();
- auto [start_line, end_line] = core_->active_window()->visible_line_range();
- for (int screen_y = 0; screen_y < editor_lines && start_line + screen_y < end_line; ++screen_y) { // Use editor_lines
- size_t buffer_line_idx = start_line + screen_y;
- const auto& line_text = buffer.line(buffer_line_idx);
-
- layout->set_text(line_text);
-
- // TODO: Apply Pango Attributes based on buffer styles (Faces)
-
- cr->move_to(0, screen_y * line_height_ + ascent_);
- layout->show_in_cairo_context(cr);
- }
- // Render Cursor
- const auto cursor = core_->cursor();
- if (cursor.line >= start_line && cursor.line < end_line) {
- int cursor_screen_x = static_cast<int>(cursor.column * char_width_);
- int cursor_screen_y = static_cast<int>((cursor.line - start_line) * line_height_);
- // Get cursor color from theme
- Color cursor_color = theme ? theme->get_fg_color(ThemeElement::Cursor) : Color(255, 255, 255);
- cr->set_source_rgb(cursor_color.r / 255.0, cursor_color.g / 255.0, cursor_color.b / 255.0);
-
- // Draw a block cursor
- cr->rectangle(cursor_screen_x, cursor_screen_y, char_width_, line_height_);
- cr->fill();
- }
- }
- // Input
- bool on_key_pressed(guint keyval, guint /*keycode*/, Gdk::ModifierType state) {
- std::string key_name;
- // Handle modifier keys
- unsigned int state_uint = static_cast<unsigned int>(state);
- bool is_control = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) != 0;
- bool is_alt = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::ALT_MASK)) != 0;
- bool is_meta = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::META_MASK)) != 0;
-
- // Combine Alt and Meta logic for Lumacs "M-"
- bool is_lumacs_meta = is_alt || is_meta;
- // Convert keyval to string
- switch (keyval) {
- case GDK_KEY_Return: key_name = "Return"; break;
- case GDK_KEY_Tab: key_name = "Tab"; break;
- case GDK_KEY_Escape: key_name = "Escape"; break;
- case GDK_KEY_BackSpace: key_name = "Backspace"; break;
- case GDK_KEY_Delete: key_name = "Delete"; break;
- case GDK_KEY_Up: key_name = "ArrowUp"; break;
- case GDK_KEY_Down: key_name = "ArrowDown"; break;
- case GDK_KEY_Left: key_name = "ArrowLeft"; break;
- case GDK_KEY_Right: key_name = "ArrowRight"; break;
- case GDK_KEY_Home: key_name = "Home"; break;
- case GDK_KEY_End: key_name = "End"; break;
- case GDK_KEY_F3: key_name = "F3"; break;
- case GDK_KEY_F4: key_name = "F4"; break;
- default:
- // Handle printable characters
- if (keyval >= GDK_KEY_a && keyval <= GDK_KEY_z) {
- key_name = std::string(1, static_cast<char>(keyval));
- if (is_control) { // Special handling for C-a to C-z
- // GDK sends keyval for 'a' to 'z' even with Control.
- // Lumacs expects 'C-a' not 'C-S-a' if Shift is also pressed.
- // So we only take the base character and then apply C- modifier.
- } else if ((static_cast<unsigned int>(state) & static_cast<unsigned int>(Gdk::ModifierType::SHIFT_MASK)) != 0) { // Shift-a is 'A' etc.
- key_name = std::string(1, static_cast<char>(keyval - (GDK_KEY_a - GDK_KEY_A)));
- }
- } else if (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9) {
- key_name = std::string(1, static_cast<char>(keyval));
- } else if (keyval >= 32 && keyval <= 126) { // Other printable ASCII
- key_name = std::string(1, static_cast<char>(keyval));
- } else {
- return false; // Unhandled key
- }
- break;
- }
- if (key_name.empty()) {
- return false;
- }
- // Apply modifiers
- if (is_control && key_name.length() == 1) { // Only apply C- to single chars, not special keys
- key_name = "C-" + key_name;
- }
- if (is_lumacs_meta) {
- key_name = "M-" + key_name;
- }
-
- std::cout << "Key pressed: " << key_name << std::endl; // Debug print
- core_->lua_api()->process_key(key_name);
- return true;
- }
- };
- std::unique_ptr<IEditorView> create_gtk_editor() {
- return std::make_unique<GtkEditor>();
- }
- } // namespace lumacs
- #else // LUMACS_WITH_GTK not defined
- namespace lumacs {
- std::unique_ptr<IEditorView> create_gtk_editor() {
- std::cerr << "Error: Lumacs was built without GTK support." << std::endl;
- return nullptr;
- }
- } // namespace lumacs
- #endif
|