[gtk/wip/textview] Port textview to render nodes
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/textview] Port textview to render nodes
- Date: Sun, 21 Jul 2019 00:11:37 +0000 (UTC)
commit 4c5f7be446047ca638d393bb78b52103fbb28ab7
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Jul 20 17:09:26 2019 -0700
Port textview to render nodes
Make GtkTextView use the GskPangoRenderer.
gtk/gskpango.c | 603 ++++++++++++++++++++++++++++++++++++++++++++
gtk/gtktextdisplayprivate.h | 4 +
gtk/gtktextview.c | 2 +-
3 files changed, 608 insertions(+), 1 deletion(-)
---
diff --git a/gtk/gskpango.c b/gtk/gskpango.c
index 8e1c2e3f70..24708bcdf0 100644
--- a/gtk/gskpango.c
+++ b/gtk/gskpango.c
@@ -23,6 +23,11 @@
#include "gsk/gskrendernodeprivate.h"
#include "gskpango.h"
#include "gtksnapshotprivate.h"
+#include "gtkstylecontextprivate.h"
+#include "gtktextlayoutprivate.h"
+#include "gtktextdisplayprivate.h"
+#include "gtktextviewprivate.h"
+#include "gtkwidgetprivate.h"
#include <math.h>
@@ -43,10 +48,15 @@ struct _GskPangoRenderer
{
PangoRenderer parent_instance;
+ GtkWidget *widget;
GtkSnapshot *snapshot;
GdkRGBA fg_color;
graphene_rect_t bounds;
+ GdkRGBA *error_color; /* Error underline color for this widget */
+
+ int state;
+
/* house-keeping options */
gboolean is_cached_renderer;
};
@@ -56,8 +66,21 @@ struct _GskPangoRendererClass
PangoRendererClass parent_class;
};
+enum {
+ NORMAL,
+ SELECTED,
+ CURSOR
+};
+
G_DEFINE_TYPE (GskPangoRenderer, gsk_pango_renderer, PANGO_TYPE_RENDERER)
+static void
+gsk_pango_renderer_set_state (GskPangoRenderer *crenderer,
+ int state)
+{
+ crenderer->state = state;
+}
+
static void
get_color (GskPangoRenderer *crenderer,
PangoRenderPart part,
@@ -336,6 +359,123 @@ gsk_pango_renderer_draw_shape (PangoRenderer *renderer,
cairo_destroy (cr);
}
+static void
+text_renderer_set_rgba (GskPangoRenderer *crenderer,
+ PangoRenderPart part,
+ const GdkRGBA *rgba)
+{
+ PangoRenderer *renderer = PANGO_RENDERER (crenderer);
+ PangoColor color = { 0, };
+ guint16 alpha;
+
+ if (rgba)
+ {
+ color.red = (guint16)(rgba->red * 65535);
+ color.green = (guint16)(rgba->green * 65535);
+ color.blue = (guint16)(rgba->blue * 65535);
+ alpha = (guint16)(rgba->alpha * 65535);
+ pango_renderer_set_color (renderer, part, &color);
+ pango_renderer_set_alpha (renderer, part, alpha);
+ }
+ else
+ {
+ pango_renderer_set_color (renderer, part, NULL);
+ pango_renderer_set_alpha (renderer, part, 0);
+ }
+}
+
+static GtkTextAppearance *
+get_item_appearance (PangoItem *item)
+{
+ GSList *tmp_list = item->analysis.extra_attrs;
+
+ while (tmp_list)
+ {
+ PangoAttribute *attr = tmp_list->data;
+
+ if (attr->klass->type == gtk_text_attr_appearance_type)
+ return &((GtkTextAttrAppearance *)attr)->appearance;
+
+ tmp_list = tmp_list->next;
+ }
+
+ return NULL;
+}
+
+static void
+gsk_pango_renderer_prepare_run (PangoRenderer *renderer,
+ PangoLayoutRun *run)
+{
+ GtkStyleContext *context;
+ GskPangoRenderer *crenderer = GSK_PANGO_RENDERER (renderer);
+ GdkRGBA *bg_rgba = NULL;
+ GdkRGBA *fg_rgba = NULL;
+ GtkTextAppearance *appearance;
+
+ PANGO_RENDERER_CLASS (gsk_pango_renderer_parent_class)->prepare_run (renderer, run);
+
+ appearance = get_item_appearance (run->item);
+
+ if (appearance == NULL)
+ return;
+
+ context = gtk_widget_get_style_context (crenderer->widget);
+
+ if (appearance->draw_bg && crenderer->state == NORMAL)
+ bg_rgba = appearance->bg_rgba;
+ else
+ bg_rgba = NULL;
+
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba);
+
+ if (crenderer->state == SELECTED)
+ {
+ GtkCssNode *selection_node;
+
+ selection_node = gtk_text_view_get_selection_node ((GtkTextView *)crenderer->widget);
+ gtk_style_context_save_to_node (context, selection_node);
+
+ gtk_style_context_get (context,
+ "color", &fg_rgba,
+ NULL);
+
+ gtk_style_context_restore (context);
+ }
+ else if (crenderer->state == CURSOR && gtk_widget_has_focus (crenderer->widget))
+ {
+ gtk_style_context_get (context,
+ "background-color", &fg_rgba,
+ NULL);
+ }
+ else
+ fg_rgba = appearance->fg_rgba;
+
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_FOREGROUND, fg_rgba);
+
+ if (appearance->strikethrough_rgba)
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_STRIKETHROUGH, appearance->strikethrough_rgba);
+ else
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_STRIKETHROUGH, fg_rgba);
+
+ if (appearance->underline_rgba)
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_UNDERLINE, appearance->underline_rgba);
+ else if (appearance->underline == PANGO_UNDERLINE_ERROR)
+ {
+ if (!crenderer->error_color)
+ {
+ static const GdkRGBA red = { 1, 0, 0, 1 };
+ crenderer->error_color = gdk_rgba_copy (&red);
+ }
+
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_UNDERLINE, crenderer->error_color);
+ }
+ else
+ text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_UNDERLINE, fg_rgba);
+
+ if (fg_rgba != appearance->fg_rgba)
+ gdk_rgba_free (fg_rgba);
+}
+
static void
gsk_pango_renderer_init (GskPangoRenderer *renderer G_GNUC_UNUSED)
{
@@ -352,6 +492,7 @@ gsk_pango_renderer_class_init (GskPangoRendererClass *klass)
renderer_class->draw_trapezoid = gsk_pango_renderer_draw_trapezoid;
renderer_class->draw_error_underline = gsk_pango_renderer_draw_error_underline;
renderer_class->draw_shape = gsk_pango_renderer_draw_shape;
+ renderer_class->prepare_run = gsk_pango_renderer_prepare_run;
}
static GskPangoRenderer *cached_renderer = NULL; /* MT-safe */
@@ -385,8 +526,15 @@ release_renderer (GskPangoRenderer *renderer)
{
if (G_LIKELY (renderer->is_cached_renderer))
{
+ renderer->widget = NULL;
renderer->snapshot = NULL;
+ if (renderer->error_color)
+ {
+ gdk_rgba_free (renderer->error_color);
+ renderer->error_color = NULL;
+ }
+
G_UNLOCK (cached_renderer);
}
else
@@ -426,3 +574,458 @@ gtk_snapshot_append_layout (GtkSnapshot *snapshot,
release_renderer (crenderer);
}
+
+static void
+render_para (GskPangoRenderer *crenderer,
+ int offset_y,
+ GtkTextLineDisplay *line_display,
+ int selection_start_index,
+ int selection_end_index)
+{
+ GtkStyleContext *context;
+ PangoLayout *layout = line_display->layout;
+ int byte_offset = 0;
+ PangoLayoutIter *iter;
+ int screen_width;
+ GdkRGBA *selection;
+ gboolean first = TRUE;
+ GtkCssNode *selection_node;
+ graphene_point_t point = { 0, offset_y };
+
+ iter = pango_layout_get_iter (layout);
+ screen_width = line_display->total_width;
+
+ context = gtk_widget_get_style_context (crenderer->widget);
+ selection_node = gtk_text_view_get_selection_node ((GtkTextView*)crenderer->widget);
+ gtk_style_context_save_to_node (context, selection_node);
+
+ gtk_style_context_get (context, "background-color", &selection, NULL);
+
+ gtk_style_context_restore (context);
+
+ gtk_snapshot_save (crenderer->snapshot);
+ gtk_snapshot_translate (crenderer->snapshot, &point);
+
+ do
+ {
+ PangoLayoutLine *line = pango_layout_iter_get_line_readonly (iter);
+ int selection_y, selection_height;
+ int first_y, last_y;
+ PangoRectangle line_rect;
+ int baseline;
+ gboolean at_last_line;
+
+ pango_layout_iter_get_line_extents (iter, NULL, &line_rect);
+ baseline = pango_layout_iter_get_baseline (iter);
+ pango_layout_iter_get_line_yrange (iter, &first_y, &last_y);
+
+ /* Adjust for margins */
+
+ line_rect.x += line_display->x_offset * PANGO_SCALE;
+ line_rect.y += line_display->top_margin * PANGO_SCALE;
+ baseline += line_display->top_margin * PANGO_SCALE;
+
+ /* Selection is the height of the line, plus top/bottom
+ * margin if we're the first/last line
+ */
+ selection_y = PANGO_PIXELS (first_y) + line_display->top_margin;
+ selection_height = PANGO_PIXELS (last_y) - PANGO_PIXELS (first_y);
+
+ if (first)
+ {
+ selection_y -= line_display->top_margin;
+ selection_height += line_display->top_margin;
+ }
+
+ at_last_line = pango_layout_iter_at_last_line (iter);
+ if (at_last_line)
+ selection_height += line_display->bottom_margin;
+
+ first = FALSE;
+
+ if (selection_start_index < byte_offset &&
+ selection_end_index > line->length + byte_offset) /* All selected */
+ {
+ graphene_rect_t bounds = {
+ .origin.x = line_display->left_margin,
+ .origin.y = selection_y,
+ .size.width = screen_width,
+ .size.height = selection_height,
+ };
+
+ gtk_snapshot_append_color (crenderer->snapshot, selection, &bounds);
+
+#if 0
+ gtk_snapshot_render_background (crenderer->snapshot, context,
+ line_display->left_margin, selection_y,
+ screen_width, selection_height);
+#endif
+
+ gsk_pango_renderer_set_state (crenderer, SELECTED);
+ pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
+ line,
+ line_rect.x,
+ baseline);
+
+#if 0
+
+ cairo_t *cr = crenderer->cr;
+
+ cairo_save (cr);
+ gdk_cairo_set_source_rgba (cr, selection);
+ cairo_rectangle (cr,
+ line_display->left_margin, selection_y,
+ screen_width, selection_height);
+ cairo_fill (cr);
+ cairo_restore(cr);
+#endif
+ }
+ else
+ {
+ if (line_display->pg_bg_rgba)
+ {
+ graphene_rect_t bounds = {
+ .origin.x = line_display->left_margin,
+ .origin.y = selection_y,
+ .size.width = screen_width,
+ .size.height = selection_height,
+ };
+
+ gtk_snapshot_append_color (crenderer->snapshot,
+ line_display->pg_bg_rgba,
+ &bounds);
+
+#if 0
+
+ cairo_t *cr = crenderer->cr;
+
+ cairo_save (cr);
+
+ gdk_cairo_set_source_rgba (crenderer->cr, line_display->pg_bg_rgba);
+ cairo_rectangle (cr,
+ line_display->left_margin, selection_y,
+ screen_width, selection_height);
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+#endif
+ }
+
+ gsk_pango_renderer_set_state (crenderer, NORMAL);
+ pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
+ line,
+ line_rect.x,
+ baseline);
+
+ /* Check if some part of the line is selected; the newline
+ * that is after line->length for the last line of the
+ * paragraph counts as part of the line for this
+ */
+ if ((selection_start_index < byte_offset + line->length ||
+ (selection_start_index == byte_offset + line->length && pango_layout_iter_at_last_line
(iter))) &&
+ selection_end_index > byte_offset)
+ {
+ gint *ranges;
+ gint n_ranges, i;
+
+ pango_layout_line_get_x_ranges (line, selection_start_index, selection_end_index, &ranges,
&n_ranges);
+
+ gsk_pango_renderer_set_state (crenderer, SELECTED);
+
+ for (i=0; i < n_ranges; i++)
+ {
+ graphene_rect_t rect;
+ gint x = line_display->x_offset;
+ gint y = selection_y;
+ gint height = selection_height;
+
+ rect.origin.x = x + PANGO_PIXELS (ranges[2*i]);
+ rect.origin.y = y;
+ rect.size.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]);
+ rect.size.height = height;
+
+ gtk_snapshot_append_color (crenderer->snapshot, selection, &rect);
+
+ gtk_snapshot_push_clip (crenderer->snapshot, &rect);
+ pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
+ line,
+ line_rect.x,
+ baseline);
+ gtk_snapshot_pop (crenderer->snapshot);
+ }
+
+ g_free (ranges);
+
+#if 0
+
+ cairo_t *cr = crenderer->cr;
+ cairo_region_t *clip_region = get_selected_clip (crenderer, layout, line,
+ line_display->x_offset,
+ selection_y,
+ selection_height,
+ selection_start_index, selection_end_index);
+
+ cairo_save (cr);
+ gdk_cairo_region (cr, clip_region);
+ cairo_clip (cr);
+ cairo_region_destroy (clip_region);
+
+ gdk_cairo_set_source_rgba (cr, selection);
+ cairo_rectangle (cr,
+ PANGO_PIXELS (line_rect.x),
+ selection_y,
+ PANGO_PIXELS (line_rect.width),
+ selection_height);
+ cairo_fill (cr);
+#endif
+
+#if 0
+ cairo_restore (cr);
+#endif
+
+ /* Paint in the ends of the line */
+ if (line_rect.x > line_display->left_margin * PANGO_SCALE &&
+ ((line_display->direction == GTK_TEXT_DIR_LTR && selection_start_index < byte_offset) ||
+ (line_display->direction == GTK_TEXT_DIR_RTL && selection_end_index > byte_offset +
line->length)))
+ {
+ graphene_rect_t bounds = {
+ .origin.x = line_display->left_margin,
+ .origin.y = selection_y,
+ .size.width = PANGO_PIXELS (line_rect.x) - line_display->left_margin,
+ .size.height = selection_height,
+ };
+
+ gtk_snapshot_append_color (crenderer->snapshot, selection, &bounds);
+
+#if 0
+ cairo_save (cr);
+
+ gdk_cairo_set_source_rgba (cr, selection);
+ cairo_rectangle (cr,
+ line_display->left_margin,
+ selection_y,
+ PANGO_PIXELS (line_rect.x) - line_display->left_margin,
+ selection_height);
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+#endif
+ }
+
+ if (line_rect.x + line_rect.width <
+ (screen_width + line_display->left_margin) * PANGO_SCALE &&
+ ((line_display->direction == GTK_TEXT_DIR_LTR && selection_end_index > byte_offset +
line->length) ||
+ (line_display->direction == GTK_TEXT_DIR_RTL && selection_start_index < byte_offset)))
+ {
+ int nonlayout_width;
+
+ nonlayout_width =
+ line_display->left_margin + screen_width -
+ PANGO_PIXELS (line_rect.x) - PANGO_PIXELS (line_rect.width);
+
+ {
+ graphene_rect_t bounds = {
+ .origin.x = PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width),
+ .origin.y = selection_y,
+ .size.width = nonlayout_width,
+ .size.height = selection_height,
+ };
+
+ gtk_snapshot_append_color (crenderer->snapshot, selection, &bounds);
+ }
+
+#if 0
+ cairo_save (cr);
+
+ gdk_cairo_set_source_rgba (cr, selection);
+ cairo_rectangle (cr,
+ PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width),
+ selection_y,
+ nonlayout_width,
+ selection_height);
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+#endif
+ }
+ }
+ else if (line_display->has_block_cursor &&
+ gtk_widget_has_focus (crenderer->widget) &&
+ byte_offset <= line_display->insert_index &&
+ (line_display->insert_index < byte_offset + line->length ||
+ (at_last_line && line_display->insert_index == byte_offset + line->length)))
+ {
+#if 0
+ GdkRectangle cursor_rect;
+ GdkRGBA cursor_color;
+ cairo_t *cr = crenderer->cr;
+
+ /* we draw text using base color on filled cursor rectangle of cursor color
+ * (normally white on black) */
+ _gtk_style_context_get_cursor_color (context, &cursor_color, NULL);
+
+ cursor_rect.x = line_display->x_offset + line_display->block_cursor.x;
+ cursor_rect.y = line_display->block_cursor.y + line_display->top_margin;
+ cursor_rect.width = line_display->block_cursor.width;
+ cursor_rect.height = line_display->block_cursor.height;
+
+ cairo_save (cr);
+
+ gdk_cairo_rectangle (cr, &cursor_rect);
+ cairo_clip (cr);
+
+ gdk_cairo_set_source_rgba (cr, &cursor_color);
+ cairo_paint (cr);
+
+ /* draw text under the cursor if any */
+ if (!line_display->cursor_at_line_end)
+ {
+ GdkRGBA *color;
+
+ gtk_style_context_get (context, "background-color", &color, NULL);
+
+ gdk_cairo_set_source_rgba (cr, color);
+
+ gsk_pango_renderer_set_state (crenderer, CURSOR);
+
+ pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
+ line,
+ line_rect.x,
+ baseline);
+ gdk_rgba_free (color);
+ }
+
+ cairo_restore (cr);
+#endif
+ }
+ }
+
+ byte_offset += line->length;
+ }
+ while (pango_layout_iter_next_line (iter));
+
+ gtk_snapshot_restore (crenderer->snapshot);
+
+ gdk_rgba_free (selection);
+ pango_layout_iter_free (iter);
+}
+
+void
+gtk_text_layout_snapshot2 (GtkTextLayout *layout,
+ GtkWidget *widget,
+ GtkSnapshot *snapshot,
+ const GdkRectangle *clip)
+{
+ GskPangoRenderer *crenderer;
+ GtkStyleContext *context;
+ gint offset_y;
+ GtkTextIter selection_start, selection_end;
+ gboolean have_selection;
+ GSList *line_list;
+ GSList *tmp_list;
+ GdkRGBA color;
+
+ g_return_if_fail (GTK_IS_TEXT_LAYOUT (layout));
+ g_return_if_fail (layout->default_style != NULL);
+ g_return_if_fail (layout->buffer != NULL);
+ g_return_if_fail (snapshot != NULL);
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_color (context, &color);
+
+ line_list = gtk_text_layout_get_lines (layout, clip->y, clip->y + clip->height, &offset_y);
+
+ if (line_list == NULL)
+ return; /* nothing on the screen */
+
+ crenderer = acquire_renderer ();
+
+ crenderer->widget = widget;
+ crenderer->snapshot = snapshot;
+ crenderer->fg_color = color;
+
+ gtk_text_layout_wrap_loop_start (layout);
+
+ have_selection = gtk_text_buffer_get_selection_bounds (layout->buffer,
+ &selection_start,
+ &selection_end);
+
+ tmp_list = line_list;
+ while (tmp_list != NULL)
+ {
+ GtkTextLine *line = tmp_list->data;
+ GtkTextLineDisplay *line_display;
+ gint selection_start_index = -1;
+ gint selection_end_index = -1;
+
+ line_display = gtk_text_layout_get_line_display (layout, line, FALSE);
+
+ if (line_display->height > 0)
+ {
+ g_assert (line_display->layout != NULL);
+
+ if (have_selection)
+ {
+ GtkTextIter line_start, line_end;
+ gint byte_count;
+
+ gtk_text_layout_get_iter_at_line (layout, &line_start, line, 0);
+ line_end = line_start;
+ if (!gtk_text_iter_ends_line (&line_end))
+ gtk_text_iter_forward_to_line_end (&line_end);
+ byte_count = gtk_text_iter_get_visible_line_index (&line_end);
+
+ if (gtk_text_iter_compare (&selection_start, &line_end) <= 0 &&
+ gtk_text_iter_compare (&selection_end, &line_start) >= 0)
+ {
+ if (gtk_text_iter_compare (&selection_start, &line_start) >= 0)
+ selection_start_index = gtk_text_iter_get_visible_line_index (&selection_start);
+ else
+ selection_start_index = -1;
+
+ if (gtk_text_iter_compare (&selection_end, &line_end) <= 0)
+ selection_end_index = gtk_text_iter_get_visible_line_index (&selection_end);
+ else
+ selection_end_index = byte_count + 1; /* + 1 to flag past-the-end */
+ }
+ }
+
+ render_para (crenderer, offset_y, line_display,
+ selection_start_index, selection_end_index);
+
+ /* We paint the cursors last, because they overlap another chunk
+ * and need to appear on top.
+ */
+#if 0
+ if (line_display->cursors != NULL)
+ {
+ int i;
+
+ for (i = 0; i < line_display->cursors->len; i++)
+ {
+ int index;
+ PangoDirection dir;
+
+ index = g_array_index(line_display->cursors, int, i);
+ dir = (line_display->direction == GTK_TEXT_DIR_RTL) ? PANGO_DIRECTION_RTL :
PANGO_DIRECTION_LTR;
+ gtk_render_insertion_cursor (context, cr,
+ line_display->x_offset, line_display->top_margin,
+ line_display->layout, index, dir);
+ }
+ }
+#endif
+ } /* line_display->height > 0 */
+
+ offset_y += line_display->height;
+
+ gtk_text_layout_free_line_display (layout, line_display);
+
+ tmp_list = tmp_list->next;
+ }
+
+ gtk_text_layout_wrap_loop_end (layout);
+
+ g_slist_free (line_list);
+
+ release_renderer (crenderer);
+}
diff --git a/gtk/gtktextdisplayprivate.h b/gtk/gtktextdisplayprivate.h
index e4385e5b47..1e3dc0ddfb 100644
--- a/gtk/gtktextdisplayprivate.h
+++ b/gtk/gtktextdisplayprivate.h
@@ -94,6 +94,10 @@ void gtk_text_layout_snapshot (GtkTextLayout *layout,
GtkWidget *widget,
GtkSnapshot *snapshot,
const GdkRectangle *clip);
+void gtk_text_layout_snapshot2 (GtkTextLayout *layout,
+ GtkWidget *widget,
+ GtkSnapshot *snapshot,
+ const GdkRectangle *clip);
G_END_DECLS
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index baaf8e4bff..d2d30dd632 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -5357,7 +5357,7 @@ gtk_text_view_paint (GtkWidget *widget,
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
- gtk_text_layout_snapshot (priv->layout,
+ gtk_text_layout_snapshot2 (priv->layout,
widget,
snapshot,
&(GdkRectangle) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]