|
|
@@ -141,6 +141,62 @@ public:
|
|
|
|
|
|
private:
|
|
|
EditorCore* core_;
|
|
|
+
|
|
|
+ void 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
Glib::RefPtr<Gtk::Application> app_;
|
|
|
Gtk::Window* window_ = nullptr; // Store window pointer for widget access only (not lifetime management)
|
|
|
// Input Mode State
|
|
|
@@ -658,17 +714,14 @@ protected:
|
|
|
if (!core_ || !window) return;
|
|
|
|
|
|
const auto cursor = window->cursor();
|
|
|
+ const auto& buffer = window->buffer();
|
|
|
+ auto theme = core_->active_theme();
|
|
|
|
|
|
// 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 temporary layout for metrics if needed (using widget context is better but this works with cache)
|
|
|
- // Note: We use widget->create_pango_layout() for cached lines to persist them
|
|
|
-
|
|
|
// Ensure metrics are initialized
|
|
|
if (!font_initialized_) {
|
|
|
auto layout = widget->create_pango_layout("m");
|
|
|
@@ -698,25 +751,25 @@ protected:
|
|
|
int editor_lines = is_main_window ? std::max(0, visible_lines - 2) : 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 ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255);
|
|
|
- cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
|
|
|
|
|
|
- // Render visible lines using cache
|
|
|
- const auto& buffer = window->buffer();
|
|
|
+ // Render visible lines
|
|
|
auto [start_line, end_line] = window->visible_line_range();
|
|
|
int horizontal_offset = window->viewport().horizontal_offset;
|
|
|
|
|
|
- // Get or create window cache
|
|
|
- WindowCache& cache = render_cache_[window.get()];
|
|
|
- if (cache.lines.size() < static_cast<size_t>(editor_lines)) {
|
|
|
- cache.lines.resize(editor_lines);
|
|
|
- }
|
|
|
-
|
|
|
for (int screen_y = 0; screen_y < editor_lines; ++screen_y) {
|
|
|
if (start_line + screen_y >= end_line) break;
|
|
|
|
|
|
size_t buffer_line_idx = start_line + screen_y;
|
|
|
+ if (buffer_line_idx >= buffer.line_count()) break;
|
|
|
+
|
|
|
const auto& line_text = buffer.line(buffer_line_idx);
|
|
|
|
|
|
// Apply horizontal scrolling
|
|
|
@@ -725,61 +778,120 @@ protected:
|
|
|
visible_text = line_text.substr(horizontal_offset);
|
|
|
}
|
|
|
|
|
|
- // Check cache
|
|
|
- LineCache& line_cache = cache.lines[screen_y];
|
|
|
- if (!line_cache.layout || line_cache.text != visible_text) {
|
|
|
- // Update cache
|
|
|
- if (!line_cache.layout) {
|
|
|
- line_cache.layout = widget->create_pango_layout(visible_text);
|
|
|
- line_cache.layout->set_font_description(font_desc_);
|
|
|
- } else {
|
|
|
- line_cache.layout->set_text(visible_text);
|
|
|
+ // Create Layout
|
|
|
+ auto layout = widget->create_pango_layout(visible_text);
|
|
|
+ layout->set_font_description(font_desc_);
|
|
|
+
|
|
|
+ // 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 (theme) {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- line_cache.text = visible_text;
|
|
|
}
|
|
|
|
|
|
+ // 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
- line_cache.layout->show_in_cairo_context(cr);
|
|
|
- }
|
|
|
-
|
|
|
- // Render Cursor (reuse last layout if possible, or create temp)
|
|
|
- bool should_show_cursor = (window == core_->active_window()) && cursor_visible_;
|
|
|
- if (should_show_cursor &&
|
|
|
- cursor.line >= static_cast<size_t>(start_line) &&
|
|
|
- cursor.line < static_cast<size_t>(end_line)) {
|
|
|
|
|
|
- int screen_y = cursor.line - start_line;
|
|
|
- double cursor_y = PADDING_TOP + screen_y * line_height_;
|
|
|
- double cursor_screen_x = PADDING_LEFT + (static_cast<int>(cursor.column) - horizontal_offset) * char_width_;
|
|
|
+ cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
|
|
|
+ layout->show_in_cairo_context(cr);
|
|
|
|
|
|
- if (cursor_screen_x >= PADDING_LEFT && cursor_screen_x < (width - PADDING_RIGHT)) {
|
|
|
- size_t buffer_line_idx = cursor.line;
|
|
|
- const auto& cursor_line_text = buffer.line(buffer_line_idx);
|
|
|
- char cursor_char = (cursor.column < cursor_line_text.length()) ? cursor_line_text[cursor.column] : ' ';
|
|
|
-
|
|
|
- Color cursor_bg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255);
|
|
|
- cr->set_source_rgb(cursor_bg.r / 255.0, cursor_bg.g / 255.0, cursor_bg.b / 255.0);
|
|
|
- cr->rectangle(cursor_screen_x, cursor_y, char_width_, line_height_);
|
|
|
- cr->fill();
|
|
|
-
|
|
|
- if (cursor_char != '\0' && cursor_char != ' ') {
|
|
|
- Color cursor_fg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
|
|
|
- cr->set_source_rgb(cursor_fg.r / 255.0, cursor_fg.g / 255.0, cursor_fg.b / 255.0);
|
|
|
-
|
|
|
- // Just create a temp layout for the cursor char - it's small and changes often
|
|
|
- auto cursor_layout = Pango::Layout::create(cr);
|
|
|
- cursor_layout->set_font_description(font_desc_);
|
|
|
- cursor_layout->set_text(std::string(1, cursor_char));
|
|
|
-
|
|
|
- cr->move_to(cursor_screen_x, cursor_y);
|
|
|
- cursor_layout->show_in_cairo_context(cr);
|
|
|
- }
|
|
|
+ // Render Cursor
|
|
|
+ bool should_show_cursor = (window == core_->active_window()) && cursor_visible_;
|
|
|
+ if (should_show_cursor && 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 - 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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_);
|
|
|
+ // We should ideally copy attributes here too, but it's complex.
|
|
|
+ // Defaulting to base font is acceptable for the inverted character.
|
|
|
+
|
|
|
+ cr->move_to(cursor_screen_x, text_y);
|
|
|
+ cursor_layout->show_in_cairo_context(cr);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Use a temporary layout for modeline/minibuffer as they are dynamic and not part of the main text grid
|
|
|
auto temp_layout = Pango::Layout::create(cr);
|
|
|
temp_layout->set_font_description(font_desc_);
|
|
|
@@ -791,6 +903,7 @@ protected:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// Render modeline for a specific window
|
|
|
void render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
|
|
|
const Glib::RefPtr<Pango::Layout>& layout, std::shared_ptr<Window> window) {
|