gtk_editor.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #include "lumacs/gtk_editor.hpp"
  2. #include "lumacs/editor_core.hpp"
  3. #include "lumacs/lua_api.hpp"
  4. #include <iostream>
  5. // Check if GTK is enabled in build
  6. #ifdef LUMACS_WITH_GTK
  7. #include <gtkmm.h>
  8. #include <pangomm.h>
  9. namespace lumacs {
  10. // Custom Gtk::ApplicationWindow to make constructor public
  11. class LumacsWindow : public Gtk::ApplicationWindow {
  12. public:
  13. explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application)
  14. : Gtk::ApplicationWindow(application) {
  15. set_title("Lumacs - GTK4");
  16. set_default_size(1024, 768);
  17. }
  18. };
  19. class GtkEditor : public IEditorView {
  20. public:
  21. GtkEditor() : core_(nullptr) {}
  22. ~GtkEditor() override = default;
  23. void init() override {
  24. // Initialize GTK application
  25. app_ = Gtk::Application::create("org.lumacs.editor");
  26. app_->signal_activate().connect(sigc::mem_fun(*this, &GtkEditor::on_activate));
  27. }
  28. void run() override {
  29. // Run the application's event loop
  30. app_->run();
  31. }
  32. void handle_editor_event(EditorEvent event) override {
  33. // Request redraw on most events
  34. if (drawing_area_) {
  35. drawing_area_->queue_draw();
  36. }
  37. if (event == EditorEvent::Quit) {
  38. app_->quit(); // Quit the application gracefully
  39. }
  40. }
  41. void set_core(EditorCore* core) override {
  42. core_ = core;
  43. }
  44. private:
  45. EditorCore* core_;
  46. Glib::RefPtr<Gtk::Application> app_;
  47. // Window is managed by Gtk::Application
  48. Gtk::DrawingArea* drawing_area_ = nullptr;
  49. double char_width_ = 0;
  50. double line_height_ = 0;
  51. double ascent_ = 0;
  52. protected:
  53. void on_activate() {
  54. // Create main window and associate with the application
  55. auto window = Glib::RefPtr<LumacsWindow>(new LumacsWindow(app_));
  56. // Create drawing area for text rendering
  57. drawing_area_ = Gtk::make_managed<Gtk::DrawingArea>();
  58. drawing_area_->set_draw_func(sigc::mem_fun(*this, &GtkEditor::on_draw));
  59. drawing_area_->set_focusable(true);
  60. // Add to window
  61. window->set_child(*drawing_area_);
  62. // Input handling
  63. auto controller = Gtk::EventControllerKey::create();
  64. controller->signal_key_pressed().connect(sigc::mem_fun(*this, &GtkEditor::on_key_pressed), false);
  65. drawing_area_->add_controller(controller);
  66. // Show window
  67. window->present();
  68. drawing_area_->grab_focus();
  69. }
  70. // Rendering
  71. void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
  72. // Fill background
  73. auto theme = core_->active_theme();
  74. Color bg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
  75. cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
  76. cr->paint();
  77. // Create Pango layout
  78. auto layout = Pango::Layout::create(drawing_area_->get_pango_context());
  79. // Font configuration
  80. Pango::FontDescription font_desc("Monospace 12");
  81. layout->set_font_description(font_desc);
  82. // Get font metrics
  83. Pango::FontMetrics metrics = layout->get_context()->get_metrics(font_desc);
  84. line_height_ = (double)metrics.get_height() / PANGO_SCALE;
  85. ascent_ = (double)metrics.get_ascent() / PANGO_SCALE;
  86. // Measure character width (for a single 'm' character)
  87. layout->set_text("m");
  88. Pango::Rectangle ink_rect, logical_rect;
  89. layout->get_pixel_extents(ink_rect, logical_rect);
  90. char_width_ = (double)logical_rect.get_width() / PANGO_SCALE; // Use get_width()
  91. // Update core's viewport size based on actual font metrics
  92. int content_width_px = width; // Use actual width from parameter
  93. int content_height_px = height; // Use actual height from parameter
  94. int visible_lines = static_cast<int>(content_height_px / line_height_);
  95. int visible_cols = static_cast<int>(content_width_px / char_width_);
  96. // Leave 1 line for minibuffer at bottom, adjust content area
  97. int editor_lines = std::max(0, visible_lines - 1); // Reserve one line for future minibuffer
  98. core_->set_viewport_size(visible_cols, editor_lines);
  99. // Get default foreground color from theme
  100. // auto theme = core_->active_theme(); // Redundant, theme already defined
  101. Color fg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255); // Default to white
  102. cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  103. // Render visible lines
  104. const auto& buffer = core_->buffer();
  105. auto [start_line, end_line] = core_->active_window()->visible_line_range();
  106. for (int screen_y = 0; screen_y < editor_lines && start_line + screen_y < end_line; ++screen_y) { // Use editor_lines
  107. size_t buffer_line_idx = start_line + screen_y;
  108. const auto& line_text = buffer.line(buffer_line_idx);
  109. layout->set_text(line_text);
  110. // TODO: Apply Pango Attributes based on buffer styles (Faces)
  111. cr->move_to(0, screen_y * line_height_ + ascent_);
  112. layout->show_in_cairo_context(cr);
  113. }
  114. // Render Cursor
  115. const auto cursor = core_->cursor();
  116. if (cursor.line >= start_line && cursor.line < end_line) {
  117. int cursor_screen_x = static_cast<int>(cursor.column * char_width_);
  118. int cursor_screen_y = static_cast<int>((cursor.line - start_line) * line_height_);
  119. // Get cursor color from theme
  120. Color cursor_color = theme ? theme->get_fg_color(ThemeElement::Cursor) : Color(255, 255, 255);
  121. cr->set_source_rgb(cursor_color.r / 255.0, cursor_color.g / 255.0, cursor_color.b / 255.0);
  122. // Draw a block cursor
  123. cr->rectangle(cursor_screen_x, cursor_screen_y, char_width_, line_height_);
  124. cr->fill();
  125. }
  126. }
  127. // Input
  128. bool on_key_pressed(guint keyval, guint /*keycode*/, Gdk::ModifierType state) {
  129. std::string key_name;
  130. // Handle modifier keys
  131. unsigned int state_uint = static_cast<unsigned int>(state);
  132. bool is_control = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) != 0;
  133. bool is_alt = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::ALT_MASK)) != 0;
  134. bool is_meta = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::META_MASK)) != 0;
  135. // Combine Alt and Meta logic for Lumacs "M-"
  136. bool is_lumacs_meta = is_alt || is_meta;
  137. // Convert keyval to string
  138. switch (keyval) {
  139. case GDK_KEY_Return: key_name = "Return"; break;
  140. case GDK_KEY_Tab: key_name = "Tab"; break;
  141. case GDK_KEY_Escape: key_name = "Escape"; break;
  142. case GDK_KEY_BackSpace: key_name = "Backspace"; break;
  143. case GDK_KEY_Delete: key_name = "Delete"; break;
  144. case GDK_KEY_Up: key_name = "ArrowUp"; break;
  145. case GDK_KEY_Down: key_name = "ArrowDown"; break;
  146. case GDK_KEY_Left: key_name = "ArrowLeft"; break;
  147. case GDK_KEY_Right: key_name = "ArrowRight"; break;
  148. case GDK_KEY_Home: key_name = "Home"; break;
  149. case GDK_KEY_End: key_name = "End"; break;
  150. case GDK_KEY_F3: key_name = "F3"; break;
  151. case GDK_KEY_F4: key_name = "F4"; break;
  152. default:
  153. // Handle printable characters
  154. if (keyval >= GDK_KEY_a && keyval <= GDK_KEY_z) {
  155. key_name = std::string(1, static_cast<char>(keyval));
  156. if (is_control) { // Special handling for C-a to C-z
  157. // GDK sends keyval for 'a' to 'z' even with Control.
  158. // Lumacs expects 'C-a' not 'C-S-a' if Shift is also pressed.
  159. // So we only take the base character and then apply C- modifier.
  160. } else if ((static_cast<unsigned int>(state) & static_cast<unsigned int>(Gdk::ModifierType::SHIFT_MASK)) != 0) { // Shift-a is 'A' etc.
  161. key_name = std::string(1, static_cast<char>(keyval - (GDK_KEY_a - GDK_KEY_A)));
  162. }
  163. } else if (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9) {
  164. key_name = std::string(1, static_cast<char>(keyval));
  165. } else if (keyval >= 32 && keyval <= 126) { // Other printable ASCII
  166. key_name = std::string(1, static_cast<char>(keyval));
  167. } else {
  168. return false; // Unhandled key
  169. }
  170. break;
  171. }
  172. if (key_name.empty()) {
  173. return false;
  174. }
  175. // Apply modifiers
  176. if (is_control && key_name.length() == 1) { // Only apply C- to single chars, not special keys
  177. key_name = "C-" + key_name;
  178. }
  179. if (is_lumacs_meta) {
  180. key_name = "M-" + key_name;
  181. }
  182. std::cout << "Key pressed: " << key_name << std::endl; // Debug print
  183. core_->lua_api()->process_key(key_name);
  184. return true;
  185. }
  186. };
  187. std::unique_ptr<IEditorView> create_gtk_editor() {
  188. return std::make_unique<GtkEditor>();
  189. }
  190. } // namespace lumacs
  191. #else // LUMACS_WITH_GTK not defined
  192. namespace lumacs {
  193. std::unique_ptr<IEditorView> create_gtk_editor() {
  194. std::cerr << "Error: Lumacs was built without GTK support." << std::endl;
  195. return nullptr;
  196. }
  197. } // namespace lumacs
  198. #endif