[gtk/wip/textview: 18/18] textview: port textdisplay to gskpango



commit 6171b08a050f8a5496faaa59ee4e484ece77f915
Author: Christian Hergert <chergert redhat com>
Date:   Sat Jul 20 18:25:51 2019 -0700

    textview: port textdisplay to gskpango

 gtk/gskpango.c              | 493 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtktextdisplayprivate.h |   4 +
 gtk/gtktextview.c           |   2 +-
 3 files changed, 498 insertions(+), 1 deletion(-)
---
diff --git a/gtk/gskpango.c b/gtk/gskpango.c
index 8e1c2e3f70..ff793d1865 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,348 @@ 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);
+
+          gsk_pango_renderer_set_state (crenderer, SELECTED);
+         pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
+                                           line, 
+                                           line_rect.x,
+                                           baseline);
+        }
+      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);
+            }
+        
+         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);
+
+              /* 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 (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;
+                  graphene_rect_t bounds;
+
+                  nonlayout_width =
+                    line_display->left_margin + screen_width -
+                    PANGO_PIXELS (line_rect.x) - PANGO_PIXELS (line_rect.width);
+
+                  bounds.origin.x = PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width);
+                  bounds.origin.y = selection_y;
+                  bounds.size.width = nonlayout_width;
+                  bounds.size.height = selection_height;
+
+                  gtk_snapshot_append_color (crenderer->snapshot, selection, &bounds);
+                }
+            }
+         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)))
+           {
+              GdkRGBA cursor_color;
+              graphene_rect_t bounds = {
+                .origin.x = line_display->x_offset + line_display->block_cursor.x,
+                .origin.y = line_display->block_cursor.y + line_display->top_margin,
+                .size.width = line_display->block_cursor.width,
+                .size.height = line_display->block_cursor.height,
+              };
+
+              /* 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);
+
+              gtk_snapshot_append_color (crenderer->snapshot, &cursor_color, &bounds);
+
+              /* draw text under the cursor if any */
+              if (!line_display->cursor_at_line_end)
+                {
+                  gsk_pango_renderer_set_state (crenderer, CURSOR);
+                  gtk_snapshot_push_clip (crenderer->snapshot, &bounds);
+                 pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer),
+                                                   line,
+                                                   line_rect.x,
+                                                   baseline);
+                  gtk_snapshot_pop (crenderer->snapshot);
+                }
+           }
+        }
+
+      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 (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_snapshot_render_insertion_cursor (crenderer->snapshot, context,
+                                                        line_display->x_offset, offset_y + 
line_display->top_margin,
+                                                        line_display->layout, index, dir);
+                }
+            }
+        } /* 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]