gtk_renderer.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. #include "lumacs/gtk_renderer.hpp"
  2. #include "lumacs/editor_core.hpp"
  3. #include "lumacs/minibuffer_manager.hpp" // For MinibufferManager and MinibufferMode
  4. #include <algorithm> // For std::max, std::min
  5. #include <spdlog/spdlog.h> // Added for debug logging
  6. namespace lumacs {
  7. GtkRenderer::GtkRenderer(EditorCore& core, Gtk::DrawingArea& main_drawing_area)
  8. : core_(core), main_drawing_area_(main_drawing_area) {
  9. initialize_font_metrics(); // Initialize font metrics once during construction
  10. }
  11. void GtkRenderer::initialize_font_metrics() {
  12. if (font_initialized_) return;
  13. // Use a minimal string for layout creation
  14. auto layout = main_drawing_area_.create_pango_layout(" ");
  15. font_desc_ = Pango::FontDescription("Monospace 12");
  16. layout->set_font_description(font_desc_);
  17. Pango::FontMetrics metrics = layout->get_context()->get_metrics(font_desc_);
  18. line_height_ = (double)metrics.get_height() / PANGO_SCALE;
  19. ascent_ = (double)metrics.get_ascent() / PANGO_SCALE;
  20. // Get character width more precisely using index_to_pos for a known monospace char
  21. layout->set_text("M"); // Use a single, representative character
  22. Pango::Rectangle pos0 = layout->index_to_pos(0); // Position at index 0 (before 'M')
  23. Pango::Rectangle pos1 = layout->index_to_pos(1); // Position at index 1 (after 'M')
  24. char_width_ = (double)(pos1.get_x() - pos0.get_x()) / PANGO_SCALE;
  25. spdlog::debug("GtkRenderer font metrics: line_height={}, char_width={}", line_height_, char_width_);
  26. font_initialized_ = true;
  27. }
  28. void GtkRenderer::apply_face_attributes(Pango::AttrList& attr_list, const FaceAttributes& face, int start_index, int end_index) {
  29. if (start_index >= end_index) return;
  30. // Foreground
  31. if (face.foreground) {
  32. auto attr = Pango::Attribute::create_attr_foreground(
  33. face.foreground->r * 257, face.foreground->g * 257, face.foreground->b * 257);
  34. attr.set_start_index(start_index);
  35. attr.set_end_index(end_index);
  36. attr_list.insert(attr);
  37. }
  38. // Background
  39. if (face.background) {
  40. auto attr = Pango::Attribute::create_attr_background(
  41. face.background->r * 257, face.background->g * 257, face.background->b * 257);
  42. attr.set_start_index(start_index);
  43. attr.set_end_index(end_index);
  44. attr_list.insert(attr);
  45. }
  46. // Font Family
  47. if (face.family) {
  48. auto attr = Pango::Attribute::create_attr_family(*face.family);
  49. attr.set_start_index(start_index);
  50. attr.set_end_index(end_index);
  51. attr_list.insert(attr);
  52. }
  53. // Weight
  54. if (face.weight) {
  55. Pango::Weight w = Pango::Weight::NORMAL;
  56. if (*face.weight == FontWeight::Bold) w = Pango::Weight::BOLD;
  57. else if (*face.weight == FontWeight::Light) w = Pango::Weight::LIGHT;
  58. auto attr = Pango::Attribute::create_attr_weight(w);
  59. attr.set_start_index(start_index);
  60. attr.set_end_index(end_index);
  61. attr_list.insert(attr);
  62. }
  63. // Slant/Style
  64. if (face.slant) {
  65. Pango::Style s = Pango::Style::NORMAL;
  66. if (*face.slant == FontSlant::Italic) s = Pango::Style::ITALIC;
  67. else if (*face.slant == FontSlant::Oblique) s = Pango::Style::OBLIQUE;
  68. auto attr = Pango::Attribute::create_attr_style(s);
  69. attr.set_start_index(start_index);
  70. attr.set_end_index(end_index);
  71. attr_list.insert(attr);
  72. }
  73. // Underline
  74. if (face.underline && *face.underline) {
  75. auto attr = Pango::Attribute::create_attr_underline(Pango::Underline::SINGLE);
  76. attr.set_start_index(start_index);
  77. attr.set_end_index(end_index);
  78. attr_list.insert(attr);
  79. }
  80. }
  81. std::optional<Position> GtkRenderer::resolve_screen_pos(std::shared_ptr<Window> window, double x, double y) {
  82. if (!window || line_height_ <= 0 || char_width_ <= 0) return std::nullopt;
  83. int row = static_cast<int>((y - PADDING_TOP) / line_height_);
  84. int col = static_cast<int>((x - PADDING_LEFT) / char_width_);
  85. if (row < 0) row = 0;
  86. if (col < 0) col = 0;
  87. auto viewport = window->viewport();
  88. size_t target_line = viewport.scroll_offset + row;
  89. size_t target_col = viewport.horizontal_offset + col;
  90. // Clamp to buffer bounds
  91. if (target_line >= window->buffer().line_count()) {
  92. target_line = window->buffer().line_count() - 1;
  93. }
  94. // Clamp column to line length
  95. size_t line_len = window->buffer().line(target_line).length();
  96. if (target_col > line_len) target_col = line_len;
  97. return Position{target_line, target_col};
  98. }
  99. void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
  100. std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
  101. // Safety check - don't draw if core has no active theme (during destruction)
  102. if (!core_.theme_manager().active_theme()) return;
  103. // Fill background of the entire drawing area
  104. auto theme = core_.theme_manager().active_theme();
  105. Color bg = theme->get_bg_color(ThemeElement::Background);
  106. cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
  107. cr->paint();
  108. // Get the active window from core to render it
  109. auto active_window = core_.active_window();
  110. if (!active_window) return;
  111. // Render the active window (main editor area)
  112. draw_window(cr, width, height, active_window, &main_drawing_area_, active_window_cache, cursor_visible_state);
  113. // Use a temporary layout for modeline/minibuffer as they are dynamic
  114. auto temp_layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
  115. temp_layout->set_font_description(font_desc_);
  116. // Render modeline
  117. render_modeline_for_window(cr, width, height, active_window, active_window_cache);
  118. // Render minibuffer
  119. render_minibuffer(cr, width, height);
  120. }
  121. void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
  122. std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
  123. std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
  124. if (!core_.theme_manager().active_theme() || !window || !widget) return;
  125. // Use a temporary layout that is properly initialized
  126. auto layout = Pango::Layout::create(widget->get_pango_context());
  127. layout->set_font_description(font_desc_);
  128. const auto cursor = window->cursor();
  129. const auto& buffer = window->buffer();
  130. auto theme = core_.theme_manager().active_theme();
  131. // Fill background
  132. Color bg = theme->get_bg_color(ThemeElement::Background);
  133. cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
  134. cr->paint();
  135. int content_width_px = width - static_cast<int>(PADDING_LEFT + PADDING_RIGHT);
  136. int content_height_px = height - static_cast<int>(PADDING_TOP + PADDING_BOTTOM);
  137. int visible_lines = static_cast<int>(content_height_px / line_height_);
  138. int visible_cols = static_cast<int>(content_width_px / char_width_);
  139. // Reserve space for modeline (1 line). Minibuffer is global and rendered separately.
  140. bool is_main_window = (window == active_window_cache);
  141. int editor_lines = is_main_window ? std::max(0, visible_lines - 2) : std::max(0, visible_lines - 1);
  142. window->set_viewport_size(visible_cols, editor_lines);
  143. // Region/Mark Calculation
  144. std::optional<Range> selection_range;
  145. if (buffer.has_active_mark() && buffer.mark()) {
  146. selection_range = buffer.get_region(window->cursor());
  147. }
  148. // Get default foreground color from theme
  149. Color fg = theme->get_fg_color(ThemeElement::Normal);
  150. cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  151. // Render visible lines
  152. auto [start_line, end_line] = window->visible_line_range();
  153. int horizontal_offset = window->viewport().horizontal_offset;
  154. auto& window_cache = render_cache_[window.get()];
  155. for (int screen_y = 0; screen_y < editor_lines && start_line + screen_y < end_line; ++screen_y) {
  156. size_t buffer_line_idx = start_line + screen_y;
  157. if (buffer_line_idx >= buffer.line_count()) break; // Safety check
  158. const auto& line_text = buffer.line(buffer_line_idx);
  159. const auto& styles = buffer.get_line_styles(buffer_line_idx);
  160. // Apply horizontal scrolling
  161. std::string visible_text;
  162. if (horizontal_offset < static_cast<int>(line_text.length())) {
  163. visible_text = line_text.substr(horizontal_offset);
  164. }
  165. // Always set text for the layout before any further operations, especially cursor positioning
  166. layout->set_text(visible_text);
  167. double text_x = PADDING_LEFT;
  168. double text_y = PADDING_TOP + screen_y * line_height_;
  169. // Check for Region/Selection Intersection
  170. bool has_selection = false;
  171. int sel_start = 0, sel_end = 0;
  172. if (selection_range) {
  173. if (buffer_line_idx >= selection_range->start.line && buffer_line_idx <= selection_range->end.line) {
  174. has_selection = true;
  175. size_t s_col = (buffer_line_idx == selection_range->start.line) ? selection_range->start.column : 0;
  176. size_t e_col = (buffer_line_idx == selection_range->end.line) ? selection_range->end.column : line_text.length();
  177. sel_start = static_cast<int>(s_col) - horizontal_offset;
  178. sel_end = static_cast<int>(e_col) - horizontal_offset;
  179. sel_start = std::max(0, sel_start);
  180. sel_end = std::min(static_cast<int>(visible_text.length()), sel_end);
  181. if (sel_start >= sel_end) has_selection = false;
  182. }
  183. }
  184. bool use_cache = false;
  185. if (!has_selection && window_cache.count(buffer_line_idx)) {
  186. auto& entry = window_cache[buffer_line_idx];
  187. if (entry.text_content == visible_text) { // Simple validation
  188. cr->set_source(entry.surface, text_x, text_y);
  189. cr->paint();
  190. use_cache = true;
  191. }
  192. }
  193. if (!use_cache) {
  194. // Draw fresh
  195. // layout->set_text(visible_text); // Moved this line outside the if block
  196. Pango::AttrList attr_list;
  197. // 1. Apply Syntax Highlighting
  198. for (const auto& style : styles) {
  199. if (auto face = theme->get_face(style.attr.face_name)) {
  200. int start = static_cast<int>(style.range.start.column) - horizontal_offset;
  201. int end = static_cast<int>(style.range.end.column) - horizontal_offset;
  202. start = std::max(0, start);
  203. end = std::min(static_cast<int>(visible_text.length()), end);
  204. if (start < end) {
  205. apply_face_attributes(attr_list, *face, start, end);
  206. }
  207. }
  208. }
  209. // 2. Apply Region/Selection Highlight (if any)
  210. if (has_selection) {
  211. if (auto region_face = theme->get_face("region")) {
  212. apply_face_attributes(attr_list, *region_face, sel_start, sel_end);
  213. } else {
  214. auto attr = Pango::Attribute::create_attr_background(0x4444, 0x4444, 0x4444);
  215. attr.set_start_index(sel_start);
  216. attr.set_end_index(sel_end);
  217. attr_list.insert(attr);
  218. }
  219. }
  220. layout->set_attributes(attr_list);
  221. // Render
  222. if (!has_selection) {
  223. // Render to Cache surface first
  224. int surf_width = std::max(1, static_cast<int>(content_width_px));
  225. auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, surf_width, static_cast<int>(line_height_)); auto cr_surf = Cairo::Context::create(surface);
  226. // Clear surface
  227. cr_surf->set_operator(Cairo::Context::Operator::CLEAR);
  228. cr_surf->paint();
  229. cr_surf->set_operator(Cairo::Context::Operator::OVER);
  230. cr_surf->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  231. layout->show_in_cairo_context(cr_surf); // Render layout to surface
  232. // Store
  233. LineCacheEntry entry;
  234. entry.text_content = visible_text;
  235. entry.surface = surface;
  236. entry.styles = styles;
  237. window_cache[buffer_line_idx] = entry;
  238. // Blit to screen
  239. cr->set_source(surface, text_x, text_y);
  240. cr->paint();
  241. } else {
  242. // Render directly to screen (don't cache selected text)
  243. cr->move_to(text_x, text_y);
  244. cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  245. layout->show_in_cairo_context(cr);
  246. }
  247. }
  248. // Render Cursor (assuming `cursor_visible_` is managed by GtkEditor)
  249. bool cursor_visible_ = cursor_visible_state;
  250. bool is_active_window = (window == active_window_cache);
  251. if (is_active_window && cursor_visible_ && buffer_line_idx == cursor.line) {
  252. int cursor_idx = static_cast<int>(cursor.column) - horizontal_offset;
  253. spdlog::debug("Cursor debug: Logical Curs ({},{}) | H-Offset {} | VisTextLen {} | CursorIdx {}",
  254. cursor.line, cursor.column, horizontal_offset, visible_text.length(), cursor_idx);
  255. Pango::Rectangle pos;
  256. if (cursor_idx < 0) {
  257. // Out of view
  258. spdlog::debug("Cursor debug: Cursor out of view (left)");
  259. continue; // Don't draw if out of view
  260. } else if (cursor_idx > static_cast<int>(visible_text.length())) {
  261. // Past end of line (after last character)
  262. pos = layout->index_to_pos(visible_text.length());
  263. int diff = cursor_idx - static_cast<int>(visible_text.length());
  264. if (diff > 0) {
  265. pos.set_x(pos.get_x() + diff * char_width_ * PANGO_SCALE);
  266. }
  267. spdlog::debug("Cursor debug: Past end of line. PangoX={}, FinalPangoX={}", layout->index_to_pos(visible_text.length()).get_x(), pos.get_x());
  268. } else {
  269. pos = layout->index_to_pos(cursor_idx);
  270. spdlog::debug("Cursor debug: Within line. PangoX={}", pos.get_x());
  271. }
  272. double cursor_screen_x = PADDING_LEFT + (pos.get_x() / (double)PANGO_SCALE);
  273. spdlog::debug("Cursor debug: ScreenX={}, ScreenY={} | CharWidth={}, LineHeight={}",
  274. cursor_screen_x, text_y, char_width_, line_height_);
  275. if (cursor_screen_x >= PADDING_LEFT && cursor_screen_x < (width - PADDING_RIGHT)) {
  276. // Determine cursor width
  277. double cur_width = char_width_;
  278. if (cursor_idx < static_cast<int>(visible_text.length())) {
  279. Pango::Rectangle next_pos;
  280. next_pos = layout->index_to_pos(cursor_idx + 1);
  281. cur_width = (next_pos.get_x() - pos.get_x()) / (double)PANGO_SCALE;
  282. }
  283. // Draw Cursor Block
  284. Color cursor_bg = fg;
  285. cr->set_source_rgb(cursor_bg.r / 255.0, cursor_bg.g / 255.0, cursor_bg.b / 255.0);
  286. cr->rectangle(cursor_screen_x, text_y, cur_width, line_height_);
  287. cr->fill();
  288. // Draw Character Inverted
  289. if (cursor_idx < static_cast<int>(visible_text.length())) {
  290. char cursor_char = visible_text[cursor_idx];
  291. cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
  292. auto cursor_layout = widget->create_pango_layout(std::string(1, cursor_char));
  293. cursor_layout->set_font_description(font_desc_);
  294. cr->move_to(cursor_screen_x, text_y);
  295. cursor_layout->show_in_cairo_context(cr);
  296. }
  297. }
  298. }
  299. }
  300. // Refined Loop with Selection Logic:
  301. // (This replaces the loop in the file)
  302. // Since I cannot easily rewrite the whole loop with complex logic insertion in one go without potentially breaking,
  303. // I will rewrite the loop logic completely in the replacement string.
  304. } // Closing brace for GtkRenderer::draw_window method
  305. void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
  306. std::shared_ptr<Window> window,
  307. std::shared_ptr<Window> active_window_cache) {
  308. if (!core_.theme_manager().active_theme() || !window) return;
  309. // The logic for is_active needs to be passed from GtkEditor
  310. bool is_active = (window == active_window_cache);
  311. // Calculate modeline position (second line from bottom)
  312. double modeline_y = height - (2 * line_height_) - PADDING_BOTTOM; // Position above minibuffer
  313. double modeline_x = PADDING_LEFT;
  314. // Get theme colors
  315. auto theme = core_.theme_manager().active_theme();
  316. ThemeElement element = is_active ? ThemeElement::StatusLine : ThemeElement::StatusLineInactive;
  317. Color bg = theme->get_bg_color(element);
  318. Color fg = theme->get_fg_color(element);
  319. // Draw modeline background
  320. cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
  321. cr->rectangle(0, modeline_y, width, line_height_);
  322. cr->fill();
  323. // Build modeline content using ModelineManager
  324. auto content = core_.modeline_manager().generate_content(window, is_active);
  325. double x_offset = modeline_x;
  326. for (const auto& chunk : content) {
  327. Color chunk_fg = fg;
  328. // Resolve chunk face if needed
  329. if (!chunk.face_name.empty()) {
  330. if (auto face = theme->get_face(chunk.face_name)) {
  331. if (face->foreground) chunk_fg = *face->foreground;
  332. }
  333. }
  334. // Use a temporary layout for modeline chunk
  335. auto layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
  336. layout->set_font_description(font_desc_);
  337. layout->set_text(chunk.text);
  338. // Apply attributes
  339. Pango::AttrList attr_list;
  340. if (!chunk.face_name.empty()) {
  341. if (auto face = theme->get_face(chunk.face_name)) {
  342. apply_face_attributes(attr_list, *face, 0, static_cast<int>(chunk.text.length()));
  343. }
  344. }
  345. layout->set_attributes(attr_list);
  346. cr->set_source_rgb(chunk_fg.r / 255.0, chunk_fg.g / 255.0, chunk_fg.b / 255.0);
  347. cr->move_to(x_offset, modeline_y);
  348. layout->show_in_cairo_context(cr);
  349. // Advance
  350. int w, h;
  351. layout->get_pixel_size(w, h);
  352. x_offset += w;
  353. }
  354. }
  355. void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
  356. if (!core_.theme_manager().active_theme()) return;
  357. // Get theme colors
  358. auto theme = core_.theme_manager().active_theme();
  359. Color bg = theme->get_bg_color(ThemeElement::Background);
  360. Color fg = theme->get_fg_color(ThemeElement::Normal);
  361. // ALWAYS draw minibuffer background to prevent it from disappearing
  362. // Clear the ENTIRE surface first to avoid artifacts
  363. cr->set_source_rgb(bg.r / 255.0 * 0.9, bg.g / 255.0 * 0.9, bg.b / 255.0 * 0.9);
  364. cr->rectangle(0, 0, width, height); // Clear full dedicated area
  365. cr->fill();
  366. // Draw separator line above minibuffer
  367. cr->set_source_rgb(fg.r / 255.0 * 0.5, fg.g / 255.0 * 0.5, fg.b / 255.0 * 0.5);
  368. cr->set_line_width(1.0);
  369. cr->move_to(0, 0);
  370. cr->line_to(width, 0);
  371. cr->stroke();
  372. // Only render text if minibuffer is active or a message is set
  373. if (!core_.minibuffer_manager().is_active() && core_.last_message().empty()) {
  374. return;
  375. }
  376. // Calculate minibuffer position (bottom line with padding)
  377. double minibuffer_y = height - line_height_ - PADDING_BOTTOM;
  378. double minibuffer_x = PADDING_LEFT;
  379. // Prepare minibuffer text
  380. std::string minibuffer_text;
  381. std::string prompt_part;
  382. std::string input_part;
  383. if (core_.minibuffer_manager().is_active()) {
  384. prompt_part = core_.minibuffer_manager().get_prompt();
  385. input_part = core_.minibuffer_manager().get_input_buffer();
  386. minibuffer_text = prompt_part + input_part;
  387. } else if (!core_.last_message().empty()) {
  388. minibuffer_text = core_.last_message();
  389. }
  390. // Render minibuffer text
  391. if (!minibuffer_text.empty()) {
  392. cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  393. auto layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
  394. layout->set_font_description(font_desc_);
  395. layout->set_text(minibuffer_text);
  396. // Apply face attributes for prompt if active
  397. if (core_.minibuffer_manager().is_active()) {
  398. Pango::AttrList attr_list;
  399. if (auto face = theme->get_face("minibuffer-prompt")) {
  400. apply_face_attributes(attr_list, *face, 0, static_cast<int>(prompt_part.length()));
  401. }
  402. layout->set_attributes(attr_list);
  403. }
  404. cr->move_to(minibuffer_x, minibuffer_y);
  405. layout->show_in_cairo_context(cr);
  406. }
  407. // Render minibuffer cursor if active and visible (assuming cursor_visible is managed by GtkEditor)
  408. bool cursor_visible_ = true; // Placeholder
  409. if (core_.minibuffer_manager().is_active() && cursor_visible_) {
  410. // Calculate cursor position in minibuffer
  411. auto layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
  412. layout->set_font_description(font_desc_);
  413. layout->set_text(minibuffer_text); // Measure full text
  414. Pango::Rectangle ink_rect, logical_rect;
  415. layout->get_pixel_extents(ink_rect, logical_rect);
  416. double cursor_x = minibuffer_x + logical_rect.get_width();
  417. // Draw minibuffer cursor
  418. cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  419. cr->rectangle(cursor_x, minibuffer_y, 2.0, line_height_);
  420. cr->fill();
  421. // Render completion overlay if applicable
  422. auto current_completion = core_.minibuffer_manager().get_current_completion();
  423. if (current_completion && input_part != *current_completion) {
  424. std::string completion_suffix = current_completion->substr(input_part.length());
  425. if (!completion_suffix.empty()) {
  426. // Calculate width of existing text to position suffix
  427. Pango::Rectangle ink_rect, logical_rect;
  428. layout->get_pixel_extents(ink_rect, logical_rect);
  429. double text_width = logical_rect.get_width();
  430. auto completion_layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
  431. completion_layout->set_font_description(font_desc_);
  432. completion_layout->set_text(completion_suffix); // Only draw suffix
  433. Pango::AttrList completion_attr_list;
  434. if (auto face = theme->get_face("minibuffer-completion")) {
  435. apply_face_attributes(completion_attr_list, *face, 0, static_cast<int>(completion_suffix.length()));
  436. } else {
  437. // Fallback: dimmed foreground
  438. Color dim_fg = fg;
  439. dim_fg.r = static_cast<unsigned char>(dim_fg.r * 0.7);
  440. dim_fg.g = static_cast<unsigned char>(dim_fg.g * 0.7);
  441. dim_fg.b = static_cast<unsigned char>(dim_fg.b * 0.7);
  442. auto attr = Pango::Attribute::create_attr_foreground(dim_fg.r * 257, dim_fg.g * 257, dim_fg.b * 257);
  443. attr.set_start_index(0);
  444. attr.set_end_index(static_cast<int>(completion_suffix.length()));
  445. completion_attr_list.insert(attr);
  446. }
  447. completion_layout->set_attributes(completion_attr_list);
  448. cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
  449. cr->move_to(minibuffer_x + text_width, minibuffer_y); // Position after text
  450. completion_layout->show_in_cairo_context(cr);
  451. }
  452. }
  453. }
  454. }
  455. bool GtkRenderer::get_minibuffer_coords(int& x, int& y, int& width, int& height) const {
  456. // These coordinates are relative to the main drawing area.
  457. // The minibuffer is always at the bottom of the main drawing area.
  458. width = main_drawing_area_.get_width();
  459. height = static_cast<int>(line_height_ + PADDING_TOP + PADDING_BOTTOM); // Approximate minibuffer height
  460. x = 0; // Starts at the left edge
  461. y = main_drawing_area_.get_height() - height; // Position from the bottom
  462. return true;
  463. }
  464. void GtkRenderer::invalidate_cache() {
  465. render_cache_.clear();
  466. spdlog::debug("GtkRenderer: Cache invalidated due to theme change.");
  467. }
  468. } // namespace lumacs