|
@@ -177,129 +177,183 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
|
|
|
Color fg = theme->get_fg_color(ThemeElement::Normal);
|
|
Color fg = theme->get_fg_color(ThemeElement::Normal);
|
|
|
cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
|
|
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;
|
|
|
|
|
-
|
|
|
|
|
- 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);
|
|
|
|
|
-
|
|
|
|
|
- // Apply horizontal scrolling
|
|
|
|
|
- std::string visible_text;
|
|
|
|
|
- if (horizontal_offset < static_cast<int>(line_text.length())) {
|
|
|
|
|
- visible_text = line_text.substr(horizontal_offset);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Render visible lines
|
|
|
|
|
+ auto [start_line, end_line] = window->visible_line_range();
|
|
|
|
|
+ int horizontal_offset = window->viewport().horizontal_offset;
|
|
|
|
|
|
|
|
- layout->set_text(visible_text);
|
|
|
|
|
-
|
|
|
|
|
- // Create Attribute List
|
|
|
|
|
- Pango::AttrList attr_list;
|
|
|
|
|
-
|
|
|
|
|
- // 1. Apply Syntax Highlighting
|
|
|
|
|
- const auto& styles = buffer.get_line_styles(buffer_line_idx);
|
|
|
|
|
- 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);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 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);
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 2. Apply Region/Selection Highlight
|
|
|
|
|
- if (selection_range) {
|
|
|
|
|
- if (buffer_line_idx >= selection_range->start.line && buffer_line_idx <= selection_range->end.line) {
|
|
|
|
|
- size_t sel_start_col = (buffer_line_idx == selection_range->start.line) ? selection_range->start.column : 0;
|
|
|
|
|
- size_t sel_end_col = (buffer_line_idx == selection_range->end.line) ? selection_range->end.column : line_text.length();
|
|
|
|
|
-
|
|
|
|
|
- int start = static_cast<int>(sel_start_col) - horizontal_offset;
|
|
|
|
|
- int end = static_cast<int>(sel_end_col) - horizontal_offset;
|
|
|
|
|
-
|
|
|
|
|
- start = std::max(0, start);
|
|
|
|
|
- end = std::min(static_cast<int>(visible_text.length()), end);
|
|
|
|
|
-
|
|
|
|
|
- if (start < end) {
|
|
|
|
|
- if (auto region_face = theme->get_face("region")) {
|
|
|
|
|
- apply_face_attributes(attr_list, *region_face, start, end);
|
|
|
|
|
- } else {
|
|
|
|
|
- // Fallback: Standard selection blue/gray
|
|
|
|
|
- auto attr = Pango::Attribute::create_attr_background(0x4444, 0x4444, 0x4444);
|
|
|
|
|
- attr.set_start_index(start);
|
|
|
|
|
- attr.set_end_index(end);
|
|
|
|
|
- attr_list.insert(attr);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- layout->set_attributes(attr_list);
|
|
|
|
|
-
|
|
|
|
|
- // Render text at proper position
|
|
|
|
|
- double text_x = PADDING_LEFT;
|
|
|
|
|
- double text_y = PADDING_TOP + screen_y * line_height_;
|
|
|
|
|
- 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;
|
|
|
|
|
-
|
|
|
|
|
- Pango::Rectangle pos;
|
|
|
|
|
- if (cursor_idx < 0) {
|
|
|
|
|
- // Out of view
|
|
|
|
|
- } else if (cursor_idx > static_cast<int>(visible_text.length())) {
|
|
|
|
|
- // Past end of line
|
|
|
|
|
- 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);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- pos = layout->index_to_pos(cursor_idx);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- double cursor_screen_x = PADDING_LEFT + (pos.get_x() / (double)PANGO_SCALE);
|
|
|
|
|
-
|
|
|
|
|
- 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;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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);
|
|
|
|
|
+ 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;
|
|
|
|
|
|
|
|
- // 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();
|
|
|
|
|
|
|
+ Pango::Rectangle pos;
|
|
|
|
|
+ if (cursor_idx < 0) {
|
|
|
|
|
+ // Out of view
|
|
|
|
|
+ } else if (cursor_idx > static_cast<int>(visible_text.length())) {
|
|
|
|
|
+ // Past end of line
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pos = layout->index_to_pos(cursor_idx);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ double cursor_screen_x = PADDING_LEFT + (pos.get_x() / (double)PANGO_SCALE);
|
|
|
|
|
|
|
|
- // 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);
|
|
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- auto cursor_layout = widget->create_pango_layout(std::string(1, cursor_char));
|
|
|
|
|
- cursor_layout->set_font_description(font_desc_);
|
|
|
|
|
|
|
+ // 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();
|
|
|
|
|
|
|
|
- cr->move_to(cursor_screen_x, text_y);
|
|
|
|
|
- cursor_layout->show_in_cairo_context(cr);
|
|
|
|
|
|
|
+ // 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
|
|
} // Closing brace for GtkRenderer::draw_window method
|
|
|
|
|
|
|
|
|
|
|