| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- #include "lumacs/gtk_renderer.hpp"
- #include "lumacs/editor_core.hpp"
- #include "lumacs/minibuffer_manager.hpp" // For MinibufferManager and MinibufferMode
- #include <algorithm> // For std::max, std::min
- #include <spdlog/spdlog.h> // Added for debug logging
- namespace lumacs {
- GtkRenderer::GtkRenderer(EditorCore& core, Gtk::Widget& context_widget)
- : core_(core), context_widget_(context_widget) {
- initialize_font_metrics(); // Initialize font metrics once during construction
- }
- void GtkRenderer::initialize_font_metrics() {
- if (font_initialized_) return;
- // Use a minimal string for layout creation
- auto layout = context_widget_.create_pango_layout(" ");
- font_desc_ = Pango::FontDescription("Monospace 12");
- layout->set_font_description(font_desc_);
-
- 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;
-
- // Get character width more precisely using index_to_pos for a known monospace char
- layout->set_text("M"); // Use a single, representative character
- Pango::Rectangle pos0 = layout->index_to_pos(0); // Position at index 0 (before 'M')
- Pango::Rectangle pos1 = layout->index_to_pos(1); // Position at index 1 (after 'M')
- char_width_ = (double)(pos1.get_x() - pos0.get_x()) / PANGO_SCALE;
- spdlog::debug("GtkRenderer font metrics: line_height={}, char_width={}", line_height_, char_width_);
-
- font_initialized_ = true;
- }
- void GtkRenderer::apply_face_attributes(Pango::AttrList& attr_list, const FaceAttributes& face, int start_index, int end_index) {
- if (start_index >= end_index) return;
- // Foreground
- if (face.foreground) {
- auto attr = Pango::Attribute::create_attr_foreground(
- face.foreground->r * 257, face.foreground->g * 257, face.foreground->b * 257);
- attr.set_start_index(start_index);
- attr.set_end_index(end_index);
- attr_list.insert(attr);
- }
- // Background
- if (face.background) {
- auto attr = Pango::Attribute::create_attr_background(
- face.background->r * 257, face.background->g * 257, face.background->b * 257);
- attr.set_start_index(start_index);
- attr.set_end_index(end_index);
- attr_list.insert(attr);
- }
- // Font Family
- if (face.family) {
- auto attr = Pango::Attribute::create_attr_family(*face.family);
- attr.set_start_index(start_index);
- attr.set_end_index(end_index);
- attr_list.insert(attr);
- }
- // Weight
- if (face.weight) {
- Pango::Weight w = Pango::Weight::NORMAL;
- if (*face.weight == FontWeight::Bold) w = Pango::Weight::BOLD;
- else if (*face.weight == FontWeight::Light) w = Pango::Weight::LIGHT;
- auto attr = Pango::Attribute::create_attr_weight(w);
- attr.set_start_index(start_index);
- attr.set_end_index(end_index);
- attr_list.insert(attr);
- }
- // Slant/Style
- if (face.slant) {
- Pango::Style s = Pango::Style::NORMAL;
- if (*face.slant == FontSlant::Italic) s = Pango::Style::ITALIC;
- else if (*face.slant == FontSlant::Oblique) s = Pango::Style::OBLIQUE;
- auto attr = Pango::Attribute::create_attr_style(s);
- attr.set_start_index(start_index);
- attr.set_end_index(end_index);
- attr_list.insert(attr);
- }
- // Underline
- if (face.underline && *face.underline) {
- auto attr = Pango::Attribute::create_attr_underline(Pango::Underline::SINGLE);
- attr.set_start_index(start_index);
- attr.set_end_index(end_index);
- attr_list.insert(attr);
- }
- }
- std::optional<Position> GtkRenderer::resolve_screen_pos(std::shared_ptr<Window> window, double x, double y) {
- if (!window || line_height_ <= 0 || char_width_ <= 0) return std::nullopt;
- int row = static_cast<int>((y - PADDING_TOP) / line_height_);
- int col = static_cast<int>((x - PADDING_LEFT) / char_width_);
-
- if (row < 0) row = 0;
- if (col < 0) col = 0;
-
- auto viewport = window->viewport();
- size_t target_line = viewport.scroll_offset + row;
- size_t target_col = viewport.horizontal_offset + col;
-
- // Clamp to buffer bounds
- if (target_line >= window->buffer().line_count()) {
- target_line = window->buffer().line_count() - 1;
- }
-
- // Clamp column to line length
- size_t line_len = window->buffer().line(target_line).length();
- if (target_col > line_len) target_col = line_len;
-
- return Position{target_line, target_col};
- }
- void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
- std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
- // Safety check - don't draw if core has no active theme (during destruction)
- if (!core_.theme_manager().active_theme()) return;
- // Fill background of the entire drawing area
- auto theme = core_.theme_manager().active_theme();
- Color bg = theme->get_bg_color(ThemeElement::Background);
- cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
- cr->paint();
- // Get the active window from core to render it
- auto active_window = core_.active_window();
- if (!active_window) return;
- // Render the active window (main editor area)
- draw_window(cr, width, height, active_window, &main_drawing_area_, active_window_cache, cursor_visible_state);
- // Use a temporary layout for modeline/minibuffer as they are dynamic
- auto temp_layout = Pango::Layout::create(context_widget_.get_pango_context());
- temp_layout->set_font_description(font_desc_);
- // Render modeline
- render_modeline_for_window(cr, width, height, active_window, active_window_cache);
-
- // Render minibuffer
- render_minibuffer(cr, width, height);
- }
- void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
- std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
- std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
- if (!core_.theme_manager().active_theme() || !window || !widget) return;
- // Use a temporary layout that is properly initialized
- auto layout = Pango::Layout::create(widget->get_pango_context());
- layout->set_font_description(font_desc_);
- const auto cursor = window->cursor();
- const auto& buffer = window->buffer();
- auto theme = core_.theme_manager().active_theme();
- // Fill background
- Color bg = theme->get_bg_color(ThemeElement::Background);
- cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
- cr->paint();
- int content_width_px = width - static_cast<int>(PADDING_LEFT + PADDING_RIGHT);
- int content_height_px = height - static_cast<int>(PADDING_TOP + PADDING_BOTTOM);
- int visible_lines = static_cast<int>(content_height_px / line_height_);
- int visible_cols = static_cast<int>(content_width_px / char_width_);
- // Reserve space for modeline (1 line). Minibuffer is now separate.
- int editor_lines = std::max(0, visible_lines - 1);
- window->set_viewport_size(visible_cols, editor_lines);
- // Region/Mark Calculation
- std::optional<Range> selection_range;
- if (buffer.has_active_mark() && buffer.mark()) {
- selection_range = buffer.get_region(window->cursor());
- }
- // Get default foreground color from theme
- Color fg = theme->get_fg_color(ThemeElement::Normal);
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- // Render visible lines
- auto [start_line, end_line] = window->visible_line_range();
- int horizontal_offset = window->viewport().horizontal_offset;
-
- auto& window_cache = render_cache_[window.get()];
-
- for (int screen_y = 0; screen_y < editor_lines && start_line + screen_y < end_line; ++screen_y) {
- size_t buffer_line_idx = start_line + screen_y;
- if (buffer_line_idx >= buffer.line_count()) break; // Safety check
- const auto& line_text = buffer.line(buffer_line_idx);
- const auto& styles = buffer.get_line_styles(buffer_line_idx);
-
- // Apply horizontal scrolling
- std::string visible_text;
- if (horizontal_offset < static_cast<int>(line_text.length())) {
- visible_text = line_text.substr(horizontal_offset);
- }
-
- // Always set text for the layout before any further operations, especially cursor positioning
- layout->set_text(visible_text);
-
- double text_x = PADDING_LEFT;
- double text_y = PADDING_TOP + screen_y * line_height_;
-
- // Check for Region/Selection Intersection
- bool has_selection = false;
- int sel_start = 0, sel_end = 0;
-
- if (selection_range) {
- if (buffer_line_idx >= selection_range->start.line && buffer_line_idx <= selection_range->end.line) {
- has_selection = true;
- size_t s_col = (buffer_line_idx == selection_range->start.line) ? selection_range->start.column : 0;
- size_t e_col = (buffer_line_idx == selection_range->end.line) ? selection_range->end.column : line_text.length();
-
- sel_start = static_cast<int>(s_col) - horizontal_offset;
- sel_end = static_cast<int>(e_col) - horizontal_offset;
-
- sel_start = std::max(0, sel_start);
- sel_end = std::min(static_cast<int>(visible_text.length()), sel_end);
-
- if (sel_start >= sel_end) has_selection = false;
- }
- }
-
- bool use_cache = false;
- if (!has_selection && window_cache.count(buffer_line_idx)) {
- auto& entry = window_cache[buffer_line_idx];
- if (entry.text_content == visible_text) { // Simple validation
- cr->set_source(entry.surface, text_x, text_y);
- cr->paint();
- use_cache = true;
- }
- }
-
- if (!use_cache) {
- // Draw fresh
- // layout->set_text(visible_text); // Moved this line outside the if block
- Pango::AttrList attr_list;
-
- // 1. Apply Syntax Highlighting
- for (const auto& style : styles) {
- if (auto face = theme->get_face(style.attr.face_name)) {
- int start = static_cast<int>(style.range.start.column) - horizontal_offset;
- int end = static_cast<int>(style.range.end.column) - horizontal_offset;
-
- start = std::max(0, start);
- end = std::min(static_cast<int>(visible_text.length()), end);
-
- if (start < end) {
- apply_face_attributes(attr_list, *face, start, end);
- }
- }
- }
-
- // 2. Apply Region/Selection Highlight (if any)
- if (has_selection) {
- if (auto region_face = theme->get_face("region")) {
- apply_face_attributes(attr_list, *region_face, sel_start, sel_end);
- } else {
- auto attr = Pango::Attribute::create_attr_background(0x4444, 0x4444, 0x4444);
- attr.set_start_index(sel_start);
- attr.set_end_index(sel_end);
- attr_list.insert(attr);
- }
- }
-
- layout->set_attributes(attr_list);
-
- // Render
- if (!has_selection) {
- // Render to Cache surface first
- int surf_width = std::max(1, static_cast<int>(content_width_px));
-
- auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, surf_width, static_cast<int>(line_height_)); auto cr_surf = Cairo::Context::create(surface);
-
- // Clear surface
- cr_surf->set_operator(Cairo::Context::Operator::CLEAR);
- cr_surf->paint();
- cr_surf->set_operator(Cairo::Context::Operator::OVER);
-
- cr_surf->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- layout->show_in_cairo_context(cr_surf); // Render layout to surface
-
- // Store
- LineCacheEntry entry;
- entry.text_content = visible_text;
- entry.surface = surface;
- entry.styles = styles;
- window_cache[buffer_line_idx] = entry;
-
- // Blit to screen
- cr->set_source(surface, text_x, text_y);
- cr->paint();
-
- } else {
- // Render directly to screen (don't cache selected text)
- cr->move_to(text_x, text_y);
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- layout->show_in_cairo_context(cr);
- }
- }
-
- // Render Cursor (assuming `cursor_visible_` is managed by GtkEditor)
- bool cursor_visible_ = cursor_visible_state;
- bool is_active_window = (window == active_window_cache);
- if (is_active_window && cursor_visible_ && buffer_line_idx == cursor.line) {
- int cursor_idx = static_cast<int>(cursor.column) - horizontal_offset;
-
- spdlog::debug("Cursor debug: Logical Curs ({},{}) | H-Offset {} | VisTextLen {} | CursorIdx {}",
- cursor.line, cursor.column, horizontal_offset, visible_text.length(), cursor_idx);
- Pango::Rectangle pos;
- if (cursor_idx < 0) {
- // Out of view
- spdlog::debug("Cursor debug: Cursor out of view (left)");
- continue; // Don't draw if out of view
- } else if (cursor_idx > static_cast<int>(visible_text.length())) {
- // Past end of line (after last character)
- pos = layout->index_to_pos(visible_text.length());
- int diff = cursor_idx - static_cast<int>(visible_text.length());
- if (diff > 0) {
- pos.set_x(pos.get_x() + diff * char_width_ * PANGO_SCALE);
- }
- spdlog::debug("Cursor debug: Past end of line. PangoX={}, FinalPangoX={}", layout->index_to_pos(visible_text.length()).get_x(), pos.get_x());
- } else {
- pos = layout->index_to_pos(cursor_idx);
- spdlog::debug("Cursor debug: Within line. PangoX={}", pos.get_x());
- }
-
- double cursor_screen_x = PADDING_LEFT + (pos.get_x() / (double)PANGO_SCALE);
-
- spdlog::debug("Cursor debug: ScreenX={}, ScreenY={} | CharWidth={}, LineHeight={}",
- cursor_screen_x, text_y, char_width_, line_height_);
- if (cursor_screen_x >= PADDING_LEFT && cursor_screen_x < (width - PADDING_RIGHT)) {
- // Determine cursor width
- double cur_width = char_width_;
- if (cursor_idx < static_cast<int>(visible_text.length())) {
- Pango::Rectangle next_pos;
- next_pos = layout->index_to_pos(cursor_idx + 1);
- cur_width = (next_pos.get_x() - pos.get_x()) / (double)PANGO_SCALE;
- }
-
- // Draw Cursor Block
- Color cursor_bg = fg;
- cr->set_source_rgb(cursor_bg.r / 255.0, cursor_bg.g / 255.0, cursor_bg.b / 255.0);
- cr->rectangle(cursor_screen_x, text_y, cur_width, line_height_);
- cr->fill();
-
- // Draw Character Inverted
- if (cursor_idx < static_cast<int>(visible_text.length())) {
- char cursor_char = visible_text[cursor_idx];
- cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
-
- auto cursor_layout = widget->create_pango_layout(std::string(1, cursor_char));
- cursor_layout->set_font_description(font_desc_);
-
- cr->move_to(cursor_screen_x, text_y);
- cursor_layout->show_in_cairo_context(cr);
- }
- }
- }
- }
- // Refined Loop with Selection Logic:
- // (This replaces the loop in the file)
-
- // Since I cannot easily rewrite the whole loop with complex logic insertion in one go without potentially breaking,
- // I will rewrite the loop logic completely in the replacement string.
- } // Closing brace for GtkRenderer::draw_window method
- void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
- std::shared_ptr<Window> window,
- std::shared_ptr<Window> active_window_cache) {
- if (!core_.theme_manager().active_theme() || !window) return;
- // The logic for is_active needs to be passed from GtkEditor
- bool is_active = (window == active_window_cache);
-
- // Calculate modeline position (bottom of the drawing area)
- // Minibuffer is separate, so modeline is at the very bottom
- double modeline_y = height - line_height_ - PADDING_BOTTOM;
- double modeline_x = PADDING_LEFT;
-
- // Get theme colors
- auto theme = core_.theme_manager().active_theme();
- ThemeElement element = is_active ? ThemeElement::StatusLine : ThemeElement::StatusLineInactive;
-
- Color bg = theme->get_bg_color(element);
- Color fg = theme->get_fg_color(element);
-
- // Draw modeline background
- cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
- cr->rectangle(0, modeline_y, width, line_height_);
- cr->fill();
-
- // Build modeline content using ModelineManager
- auto content = core_.modeline_manager().generate_content(window, is_active);
-
- double x_offset = modeline_x;
- for (const auto& chunk : content) {
- Color chunk_fg = fg;
-
- // Resolve chunk face if needed
- if (!chunk.face_name.empty()) {
- if (auto face = theme->get_face(chunk.face_name)) {
- if (face->foreground) chunk_fg = *face->foreground;
- }
- }
-
- // Use a temporary layout for modeline chunk
- auto layout = Pango::Layout::create(context_widget_.get_pango_context());
- layout->set_font_description(font_desc_);
- layout->set_text(chunk.text);
-
- // Apply attributes
- Pango::AttrList attr_list;
- if (!chunk.face_name.empty()) {
- if (auto face = theme->get_face(chunk.face_name)) {
- apply_face_attributes(attr_list, *face, 0, static_cast<int>(chunk.text.length()));
- }
- }
- layout->set_attributes(attr_list);
- cr->set_source_rgb(chunk_fg.r / 255.0, chunk_fg.g / 255.0, chunk_fg.b / 255.0);
- cr->move_to(x_offset, modeline_y);
- layout->show_in_cairo_context(cr);
-
- // Advance
- int w, h;
- layout->get_pixel_size(w, h);
- x_offset += w;
- }
- }
- void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
- if (!core_.theme_manager().active_theme()) return;
- // Get theme colors
- auto theme = core_.theme_manager().active_theme();
- Color bg = theme->get_bg_color(ThemeElement::Background);
- Color fg = theme->get_fg_color(ThemeElement::Normal);
-
- // ALWAYS draw minibuffer background to prevent it from disappearing
- // Clear the ENTIRE surface first to avoid artifacts
- cr->set_source_rgb(bg.r / 255.0 * 0.9, bg.g / 255.0 * 0.9, bg.b / 255.0 * 0.9);
- cr->rectangle(0, 0, width, height); // Clear full dedicated area
- cr->fill();
-
- // Draw separator line above minibuffer
- cr->set_source_rgb(fg.r / 255.0 * 0.5, fg.g / 255.0 * 0.5, fg.b / 255.0 * 0.5);
- cr->set_line_width(1.0);
- cr->move_to(0, 0);
- cr->line_to(width, 0);
- cr->stroke();
- // Only render text if minibuffer is active or a message is set
- if (!core_.minibuffer_manager().is_active() && core_.last_message().empty()) {
- return;
- }
-
- // Calculate minibuffer position (bottom line with padding)
- double minibuffer_y = height - line_height_ - PADDING_BOTTOM;
- double minibuffer_x = PADDING_LEFT;
-
- // Prepare minibuffer text
- std::string minibuffer_text;
- std::string prompt_part;
- std::string input_part;
-
- if (core_.minibuffer_manager().is_active()) {
- prompt_part = core_.minibuffer_manager().get_prompt();
- input_part = core_.minibuffer_manager().get_input_buffer();
- minibuffer_text = prompt_part + input_part;
- } else if (!core_.last_message().empty()) {
- minibuffer_text = core_.last_message();
- }
-
- // Render minibuffer text
- if (!minibuffer_text.empty()) {
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- auto layout = Pango::Layout::create(context_widget_.get_pango_context());
- layout->set_font_description(font_desc_);
- layout->set_text(minibuffer_text);
-
- // Apply face attributes for prompt if active
- if (core_.minibuffer_manager().is_active()) {
- Pango::AttrList attr_list;
- if (auto face = theme->get_face("minibuffer-prompt")) {
- apply_face_attributes(attr_list, *face, 0, static_cast<int>(prompt_part.length()));
- }
- layout->set_attributes(attr_list);
- }
- cr->move_to(minibuffer_x, minibuffer_y);
- layout->show_in_cairo_context(cr);
- }
-
- // Render minibuffer cursor if active and visible (assuming cursor_visible is managed by GtkEditor)
- bool cursor_visible_ = true; // Placeholder - in real usage passed or managed
- if (core_.minibuffer_manager().is_active()) { // Assuming always visible or managed externally
- // Calculate cursor position in minibuffer
- // text is prompt + input. Cursor is relative to input start.
- size_t cursor_idx = prompt_part.length() + core_.minibuffer_manager().get_cursor_position();
-
- auto layout = Pango::Layout::create(context_widget_.get_pango_context());
- layout->set_font_description(font_desc_);
- layout->set_text(minibuffer_text);
-
- Pango::Rectangle pos = layout->index_to_pos(static_cast<int>(cursor_idx));
- double cursor_screen_x = minibuffer_x + (pos.get_x() / (double)PANGO_SCALE);
-
- // Draw Cursor Block
- // Use char_width_ as default, or measure actual char
- double cur_width = char_width_;
- // If not at end, measure actual char width
- if (cursor_idx < minibuffer_text.length()) {
- Pango::Rectangle next_pos = layout->index_to_pos(static_cast<int>(cursor_idx + 1));
- cur_width = (next_pos.get_x() - pos.get_x()) / (double)PANGO_SCALE;
- }
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- cr->rectangle(cursor_screen_x, minibuffer_y, cur_width, line_height_);
- cr->fill();
-
- // Draw Character Inverted (if not at end)
- if (cursor_idx < minibuffer_text.length()) {
- // Extract char at cursor
- // Note: simple indexing works for ASCII, but for UTF-8 need careful extraction.
- // Pango layout handles rendering, we just need to render that one char at the pos.
- // Simpler: Set color to BG and re-render the layout clipped?
- // Or just render single char?
- // Let's reuse the main editor approach: create layout for single char.
- // But we need the exact byte string for the character at cursor_idx (utf8 aware).
-
- // For now, let's rely on std::string indexing assuming ASCII for command/paths mostly,
- // or use Pango to iterate?
- // Glib::ustring is better for UTF8.
- // Let's assume standard char for now to fix the main visual bug.
- char cursor_char = minibuffer_text[cursor_idx];
-
- cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
- auto cursor_layout = context_widget_.create_pango_layout(std::string(1, cursor_char));
- cursor_layout->set_font_description(font_desc_);
- cr->move_to(cursor_screen_x, minibuffer_y);
- cursor_layout->show_in_cairo_context(cr);
- }
- // Render completion overlay if applicable
- auto current_completion = core_.minibuffer_manager().get_current_completion();
- if (current_completion && input_part != *current_completion) {
- // Safety check: ensure input_part length is valid for substring extraction
- if (input_part.length() <= current_completion->length()) {
- std::string completion_suffix = current_completion->substr(input_part.length());
- if (!completion_suffix.empty()) {
- // Position completion at the end of the input text (cursor might be in middle)
- // completion suffix appends to the END of input.
-
- // Calculate position of the end of input
- size_t input_end_idx = prompt_part.length() + input_part.length();
- Pango::Rectangle end_pos = layout->index_to_pos(static_cast<int>(input_end_idx));
- double completion_x = minibuffer_x + (end_pos.get_x() / (double)PANGO_SCALE);
- auto completion_layout = Pango::Layout::create(context_widget_.get_pango_context());
- completion_layout->set_font_description(font_desc_);
- completion_layout->set_text(completion_suffix); // Only draw suffix
-
- Pango::AttrList completion_attr_list;
- if (auto face = theme->get_face("minibuffer-completion")) {
- apply_face_attributes(completion_attr_list, *face, 0, static_cast<int>(completion_suffix.length()));
- } else {
- // Fallback: dimmed foreground
- Color dim_fg = fg;
- dim_fg.r = static_cast<unsigned char>(dim_fg.r * 0.7);
- dim_fg.g = static_cast<unsigned char>(dim_fg.g * 0.7);
- dim_fg.b = static_cast<unsigned char>(dim_fg.b * 0.7);
- auto attr = Pango::Attribute::create_attr_foreground(dim_fg.r * 257, dim_fg.g * 257, dim_fg.b * 257);
- attr.set_start_index(0);
- attr.set_end_index(static_cast<int>(completion_suffix.length()));
- completion_attr_list.insert(attr);
- }
- completion_layout->set_attributes(completion_attr_list);
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
- cr->move_to(completion_x, minibuffer_y);
- completion_layout->show_in_cairo_context(cr);
- }
- }
- }
- }
- }
- bool GtkRenderer::get_minibuffer_coords(int& x, int& y, int& width, int& height) const {
- // These coordinates are relative to the main drawing area.
- // The minibuffer is always at the bottom of the main drawing area.
- width = context_widget_.get_width();
- height = static_cast<int>(line_height_ + PADDING_TOP + PADDING_BOTTOM); // Approximate minibuffer height
- x = 0; // Starts at the left edge
- y = context_widget_.get_height() - height; // Position from the bottom
- return true;
- }
- void GtkRenderer::invalidate_cache() {
- render_cache_.clear();
- spdlog::debug("GtkRenderer: Cache invalidated due to theme change.");
- }
- } // namespace lumacs
|