From 45ebe47d944d0168c90fa826e74f9a51c63c7be5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 21 Jul 2019 11:49:13 -0700 Subject: [PATCH 1/3] textview: Stop exporting private apis These headers are no longer 'semi-public, but private and uninstalled, so exporting these functions does not do any good. --- gtk/gtktextlayoutprivate.h | 70 +------------------------------------- gtk/gtktextsegment.h | 1 - 2 files changed, 1 insertion(+), 70 deletions(-) diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h index ee7c5759b4..4a44731373 100644 --- a/gtk/gtktextlayoutprivate.h +++ b/gtk/gtktextlayoutprivate.h @@ -246,215 +246,147 @@ struct _GtkTextLineDisplay extern G_GNUC_INTERNAL PangoAttrType gtk_text_attr_appearance_type; #endif -GDK_AVAILABLE_IN_ALL GType gtk_text_layout_get_type (void) G_GNUC_CONST; -GDK_AVAILABLE_IN_ALL GtkTextLayout* gtk_text_layout_new (void); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_buffer (GtkTextLayout *layout, GtkTextBuffer *buffer); -GDK_AVAILABLE_IN_ALL GtkTextBuffer *gtk_text_layout_get_buffer (GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_default_style (GtkTextLayout *layout, GtkTextAttributes *values); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_contexts (GtkTextLayout *layout, PangoContext *ltr_context, PangoContext *rtl_context); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_cursor_direction (GtkTextLayout *layout, GtkTextDirection direction); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_overwrite_mode (GtkTextLayout *layout, gboolean overwrite); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_keyboard_direction (GtkTextLayout *layout, GtkTextDirection keyboard_dir); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_default_style_changed (GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_screen_width (GtkTextLayout *layout, gint width); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_preedit_string (GtkTextLayout *layout, const gchar *preedit_string, PangoAttrList *preedit_attrs, gint cursor_pos); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_set_cursor_visible (GtkTextLayout *layout, gboolean cursor_visible); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_get_cursor_visible (GtkTextLayout *layout); -/* Getting the size or the lines potentially results in a call to - * recompute, which is pretty massively expensive. Thus it should - * basically only be done in an idle handler. - * - * Long-term, we would really like to be able to do these without - * a full recompute so they may get cheaper over time. - */ -GDK_AVAILABLE_IN_ALL void gtk_text_layout_get_size (GtkTextLayout *layout, gint *width, gint *height); -GDK_AVAILABLE_IN_ALL GSList* gtk_text_layout_get_lines (GtkTextLayout *layout, - /* [top_y, bottom_y) */ gint top_y, gint bottom_y, gint *first_line_y); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_wrap_loop_start (GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_wrap_loop_end (GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL GtkTextLineDisplay* gtk_text_layout_get_line_display (GtkTextLayout *layout, GtkTextLine *line, gboolean size_only); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_free_line_display (GtkTextLayout *layout, GtkTextLineDisplay *display); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_get_line_at_y (GtkTextLayout *layout, GtkTextIter *target_iter, gint y, gint *line_top); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_get_iter_at_pixel (GtkTextLayout *layout, GtkTextIter *iter, gint x, gint y); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_get_iter_at_position (GtkTextLayout *layout, GtkTextIter *iter, gint *trailing, gint x, gint y); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_invalidate (GtkTextLayout *layout, const GtkTextIter *start, const GtkTextIter *end); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_invalidate_cursors(GtkTextLayout *layout, const GtkTextIter *start, const GtkTextIter *end); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_free_line_data (GtkTextLayout *layout, GtkTextLine *line, GtkTextLineData *line_data); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_is_valid (GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_validate_yrange (GtkTextLayout *layout, GtkTextIter *anchor_line, gint y0_, gint y1_); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_validate (GtkTextLayout *layout, gint max_pixels); -/* This function should return the passed-in line data, - * OR remove the existing line data from the line, and - * return a NEW line data after adding it to the line. - * That is, invariant after calling the callback is that - * there should be exactly one line data for this view - * stored on the btree line. - */ -GDK_AVAILABLE_IN_ALL GtkTextLineData* gtk_text_layout_wrap (GtkTextLayout *layout, GtkTextLine *line, - GtkTextLineData *line_data); /* may be NULL */ -GDK_AVAILABLE_IN_ALL + GtkTextLineData *line_data); void gtk_text_layout_changed (GtkTextLayout *layout, gint y, gint old_height, gint new_height); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_cursors_changed (GtkTextLayout *layout, gint y, gint old_height, gint new_height); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_get_iter_location (GtkTextLayout *layout, const GtkTextIter *iter, GdkRectangle *rect); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_get_line_yrange (GtkTextLayout *layout, const GtkTextIter *iter, gint *y, gint *height); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_get_cursor_locations (GtkTextLayout *layout, GtkTextIter *iter, GdkRectangle *strong_pos, GdkRectangle *weak_pos); gboolean _gtk_text_layout_get_block_cursor (GtkTextLayout *layout, GdkRectangle *pos); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_clamp_iter_to_vrange (GtkTextLayout *layout, GtkTextIter *iter, gint top, gint bottom); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_move_iter_to_line_end (GtkTextLayout *layout, GtkTextIter *iter, gint direction); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_move_iter_to_previous_line (GtkTextLayout *layout, GtkTextIter *iter); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_move_iter_to_next_line (GtkTextLayout *layout, GtkTextIter *iter); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_move_iter_to_x (GtkTextLayout *layout, GtkTextIter *iter, gint x); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_move_iter_visually (GtkTextLayout *layout, GtkTextIter *iter, gint count); -GDK_AVAILABLE_IN_ALL gboolean gtk_text_layout_iter_starts_line (GtkTextLayout *layout, const GtkTextIter *iter); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_get_iter_at_line (GtkTextLayout *layout, GtkTextIter *iter, GtkTextLine *line, gint byte_offset); -/* Don't use these. Use gtk_text_view_add_child_at_anchor(). - * These functions are defined in gtktextchild.c, but here - * since they are semi-public and require GtkTextLayout to - * be declared. - */ -GDK_AVAILABLE_IN_ALL void gtk_text_child_anchor_register_child (GtkTextChildAnchor *anchor, GtkWidget *child, GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_child_anchor_unregister_child (GtkTextChildAnchor *anchor, GtkWidget *child); -GDK_AVAILABLE_IN_ALL void gtk_text_child_anchor_queue_resize (GtkTextChildAnchor *anchor, GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_anchored_child_set_layout (GtkWidget *child, GtkTextLayout *layout); -GDK_AVAILABLE_IN_ALL void gtk_text_layout_spew (GtkTextLayout *layout); G_END_DECLS diff --git a/gtk/gtktextsegment.h b/gtk/gtktextsegment.h index 039d96e348..690edba4fe 100644 --- a/gtk/gtktextsegment.h +++ b/gtk/gtktextsegment.h @@ -150,7 +150,6 @@ struct _GtkTextLineSegment { }; -GDK_AVAILABLE_IN_ALL GtkTextLineSegment *gtk_text_line_segment_split (const GtkTextIter *iter); GtkTextLineSegment *_gtk_char_segment_new (const gchar *text, From 4ff9163c47924a3ecb023a95b0b06eedaf3ec8d7 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sat, 20 Jul 2019 18:25:51 -0700 Subject: [PATCH 2/3] textview: port GtkTextView to GskPangoRenderer This removes the use of GtkTextDisplay (a PangoRenderer) to use the GskPangoRender which generates render nodes. Part of this means improving the GskPangoRenderer to support the necessary features for displaying a GtkTextView. Primarily, this is a merging of GtkTextDisplay features into GskPangoRender. Additionally, GtkTextDisplay was removed to allow for gtk_text_layout_snapshot() to be implemented elsewhere. --- gtk/gskpango.c | 190 ++++++-- gtk/gskpango.h | 54 ++- gtk/gtktextdisplay.c | 932 ------------------------------------ gtk/gtktextdisplayprivate.h | 101 ---- gtk/gtktextlayout.c | 337 +++++++++++++ gtk/gtktextlayoutprivate.h | 15 +- gtk/gtktextutil.c | 2 +- gtk/gtktextview.c | 1 - gtk/meson.build | 1 - 9 files changed, 550 insertions(+), 1083 deletions(-) delete mode 100644 gtk/gtktextdisplay.c delete mode 100644 gtk/gtktextdisplayprivate.h diff --git a/gtk/gskpango.c b/gtk/gskpango.c index 8e1c2e3f70..d34d4057a3 100644 --- a/gtk/gskpango.c +++ b/gtk/gskpango.c @@ -23,41 +23,26 @@ #include "gsk/gskrendernodeprivate.h" #include "gskpango.h" #include "gtksnapshotprivate.h" +#include "gtkstylecontextprivate.h" +#include "gtktextlayoutprivate.h" +#include "gtktextviewprivate.h" #include #include #include -#define GSK_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_PANGO_RENDERER, GskPangoRendererClass)) -#define GSK_IS_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_PANGO_RENDERER)) -#define GSK_PANGO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_PANGO_RENDERER, GskPangoRendererClass)) - -/* - * This is a PangoRenderer implementation that translates all the draw calls to - * gsk render nodes, using the GtkSnapshot helper class. Glyphs are translated - * to text nodes, all other draw calls fall back to cairo nodes. - */ - -struct _GskPangoRenderer -{ - PangoRenderer parent_instance; - - GtkSnapshot *snapshot; - GdkRGBA fg_color; - graphene_rect_t bounds; - - /* house-keeping options */ - gboolean is_cached_renderer; -}; - -struct _GskPangoRendererClass -{ - PangoRendererClass parent_class; -}; - G_DEFINE_TYPE (GskPangoRenderer, gsk_pango_renderer, PANGO_TYPE_RENDERER) +void +gsk_pango_renderer_set_state (GskPangoRenderer *crenderer, + GskPangoRendererState state) +{ + g_return_if_fail (GSK_IS_PANGO_RENDERER (crenderer)); + + crenderer->state = state; +} + static void get_color (GskPangoRenderer *crenderer, PangoRenderPart part, @@ -158,15 +143,14 @@ gsk_pango_renderer_draw_rectangle (PangoRenderer *renderer, { GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer); GdkRGBA rgba; - graphene_rect_t bounds; get_color (crenderer, part, &rgba); - - graphene_rect_init (&bounds, - (double)x / PANGO_SCALE, (double)y / PANGO_SCALE, - (double)width / PANGO_SCALE, (double)height / PANGO_SCALE); - - gtk_snapshot_append_color (crenderer->snapshot, &rgba, &bounds); + gtk_snapshot_append_color (crenderer->snapshot, + &rgba, + &GRAPHENE_RECT_INIT ((double)x / PANGO_SCALE, + (double)y / PANGO_SCALE, + (double)width / PANGO_SCALE, + (double)height / PANGO_SCALE)); } static void @@ -336,6 +320,124 @@ 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 == GSK_PANGO_RENDERER_NORMAL) + bg_rgba = appearance->bg_rgba; + else + bg_rgba = NULL; + + text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba); + + if (crenderer->state == GSK_PANGO_RENDERER_SELECTED && + GTK_IS_TEXT_VIEW (crenderer->widget)) + { + 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 == GSK_PANGO_RENDERER_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,13 +454,14 @@ 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 */ G_LOCK_DEFINE_STATIC (cached_renderer); -static GskPangoRenderer * -acquire_renderer (void) +GskPangoRenderer * +gsk_pango_renderer_acquire (void) { GskPangoRenderer *renderer; @@ -380,13 +483,20 @@ acquire_renderer (void) return renderer; } -static void -release_renderer (GskPangoRenderer *renderer) +void +gsk_pango_renderer_release (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 @@ -414,7 +524,7 @@ gtk_snapshot_append_layout (GtkSnapshot *snapshot, g_return_if_fail (snapshot != NULL); g_return_if_fail (PANGO_IS_LAYOUT (layout)); - crenderer = acquire_renderer (); + crenderer = gsk_pango_renderer_acquire (); crenderer->snapshot = snapshot; crenderer->fg_color = *color; @@ -424,5 +534,5 @@ gtk_snapshot_append_layout (GtkSnapshot *snapshot, pango_renderer_draw_layout (PANGO_RENDERER (crenderer), layout, 0, 0); - release_renderer (crenderer); + gsk_pango_renderer_release (crenderer); } diff --git a/gtk/gskpango.h b/gtk/gskpango.h index a97fdebea4..33ebff422a 100644 --- a/gtk/gskpango.h +++ b/gtk/gskpango.h @@ -24,15 +24,57 @@ G_BEGIN_DECLS -#define GSK_TYPE_PANGO_RENDERER (gsk_pango_renderer_get_type ()) +#define GSK_TYPE_PANGO_RENDERER (gsk_pango_renderer_get_type ()) +#define GSK_PANGO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_PANGO_RENDERER, GskPangoRenderer)) +#define GSK_IS_PANGO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_PANGO_RENDERER)) +#define GSK_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_PANGO_RENDERER, GskPangoRendererClass)) +#define GSK_IS_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_PANGO_RENDERER)) +#define GSK_PANGO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_PANGO_RENDERER, GskPangoRendererClass)) -#define GSK_PANGO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_PANGO_RENDERER, GskPangoRenderer)) -#define GSK_IS_PANGO_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_PANGO_RENDERER)) +typedef struct _GskPangoRenderer GskPangoRenderer; +typedef struct _GskPangoRendererClass GskPangoRendererClass; -typedef struct _GskPangoRenderer GskPangoRenderer; -typedef struct _GskPangoRendererClass GskPangoRendererClass; +typedef enum +{ + GSK_PANGO_RENDERER_NORMAL, + GSK_PANGO_RENDERER_SELECTED, + GSK_PANGO_RENDERER_CURSOR +} GskPangoRendererState; -GType gsk_pango_renderer_get_type (void) G_GNUC_CONST; +/* + * This is a PangoRenderer implementation that translates all the draw calls to + * gsk render nodes, using the GtkSnapshot helper class. Glyphs are translated + * to text nodes, all other draw calls fall back to cairo nodes. + */ + +struct _GskPangoRenderer +{ + PangoRenderer parent_instance; + + GtkWidget *widget; + GtkSnapshot *snapshot; + GdkRGBA fg_color; + graphene_rect_t bounds; + + /* Error underline color for this widget */ + GdkRGBA *error_color; + + GskPangoRendererState state; + + /* house-keeping options */ + guint is_cached_renderer : 1; +}; + +struct _GskPangoRendererClass +{ + PangoRendererClass parent_class; +}; + +GType gsk_pango_renderer_get_type (void) G_GNUC_CONST; +void gsk_pango_renderer_set_state (GskPangoRenderer *crenderer, + GskPangoRendererState state); +GskPangoRenderer *gsk_pango_renderer_acquire (void); +void gsk_pango_renderer_release (GskPangoRenderer *crenderer); G_END_DECLS diff --git a/gtk/gtktextdisplay.c b/gtk/gtktextdisplay.c deleted file mode 100644 index 9c2dae9e14..0000000000 --- a/gtk/gtktextdisplay.c +++ /dev/null @@ -1,932 +0,0 @@ -/* gtktextdisplay.c - display layed-out text - * - * Copyright (c) 1992-1994 The Regents of the University of California. - * Copyright (c) 1994-1997 Sun Microsystems, Inc. - * Copyright (c) 2000 Red Hat, Inc. - * Tk->Gtk port by Havoc Pennington - * - * This file can be used under your choice of two licenses, the LGPL - * and the original Tk license. - * - * LGPL: - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see .Free - * - * Original Tk license: - * - * This software is copyrighted by the Regents of the University of - * California, Sun Microsystems, Inc., and other parties. The - * following terms apply to all files associated with the software - * unless explicitly disclaimed in individual files. - * - * The authors hereby grant permission to use, copy, modify, - * distribute, and license this software and its documentation for any - * purpose, provided that existing copyright notices are retained in - * all copies and that this notice is included verbatim in any - * distributions. No written agreement, license, or royalty fee is - * required for any of the authorized uses. Modifications to this - * software may be copyrighted by their authors and need not follow - * the licensing terms described here, provided that the new terms are - * clearly indicated on the first page of each file where they apply. - * - * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY - * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL - * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, - * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND - * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, - * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE - * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - * - * GOVERNMENT USE: If you are acquiring this software on behalf of the - * U.S. government, the Government shall have only "Restricted Rights" - * in the software and related documentation as defined in the Federal - * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you - * are acquiring the software on behalf of the Department of Defense, - * the software shall be classified as "Commercial Computer Software" - * and the Government shall have only "Restricted Rights" as defined - * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the - * foregoing, the authors grant the U.S. Government and others acting - * in its behalf permission to use and distribute the software in - * accordance with the terms specified in this license. - * - */ -/* - * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS - * file for a list of people on the GTK+ Team. See the ChangeLog - * files for a list of changes. These files are distributed with - * GTK+ at ftp://ftp.gtk.org/pub/gtk/. - */ - -#include "config.h" -#include "gtktextdisplayprivate.h" -#include "gtktextviewprivate.h" -#include "gtkwidgetprivate.h" -#include "gtkstylecontextprivate.h" -#include "gtkintl.h" -#include - -#define GTK_TYPE_TEXT_RENDERER (_gtk_text_renderer_get_type()) -#define GTK_TEXT_RENDERER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_TEXT_RENDERER, GtkTextRenderer)) -#define GTK_IS_TEXT_RENDERER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_TEXT_RENDERER)) -#define GTK_TEXT_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TEXT_RENDERER, GtkTextRendererClass)) -#define GTK_IS_TEXT_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TEXT_RENDERER)) -#define GTK_TEXT_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TEXT_RENDERER, GtkTextRendererClass)) - -typedef struct _GtkTextRenderer GtkTextRenderer; -typedef struct _GtkTextRendererClass GtkTextRendererClass; - -enum { - NORMAL, - SELECTED, - CURSOR -}; - -struct _GtkTextRenderer -{ - PangoRenderer parent_instance; - - GtkWidget *widget; - cairo_t *cr; - - GdkRGBA *error_color; /* Error underline color for this widget */ - GList *widgets; /* widgets encountered when drawing */ - - guint state : 2; -}; - -struct _GtkTextRendererClass -{ - PangoRendererClass parent_class; -}; - -GType _gtk_text_renderer_get_type (void); - -G_DEFINE_TYPE (GtkTextRenderer, _gtk_text_renderer, PANGO_TYPE_RENDERER) - -static void -text_renderer_set_rgba (GtkTextRenderer *text_renderer, - PangoRenderPart part, - const GdkRGBA *rgba) -{ - PangoRenderer *renderer = PANGO_RENDERER (text_renderer); - 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 -gtk_text_renderer_prepare_run (PangoRenderer *renderer, - PangoLayoutRun *run) -{ - GtkStyleContext *context; - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - GdkRGBA *bg_rgba = NULL; - GdkRGBA *fg_rgba = NULL; - GtkTextAppearance *appearance; - - PANGO_RENDERER_CLASS (_gtk_text_renderer_parent_class)->prepare_run (renderer, run); - - appearance = get_item_appearance (run->item); - g_assert (appearance != NULL); - - context = gtk_widget_get_style_context (text_renderer->widget); - - if (appearance->draw_bg && text_renderer->state == NORMAL) - bg_rgba = appearance->bg_rgba; - else - bg_rgba = NULL; - - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba); - - if (text_renderer->state == SELECTED) - { - GtkCssNode *selection_node; - - selection_node = gtk_text_view_get_selection_node ((GtkTextView *)text_renderer->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 (text_renderer->state == CURSOR && gtk_widget_has_focus (text_renderer->widget)) - { - gtk_style_context_get (context, - "background-color", &fg_rgba, - NULL); - } - else - fg_rgba = appearance->fg_rgba; - - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_FOREGROUND, fg_rgba); - - if (appearance->strikethrough_rgba) - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_STRIKETHROUGH, appearance->strikethrough_rgba); - else - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_STRIKETHROUGH, fg_rgba); - - if (appearance->underline_rgba) - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, appearance->underline_rgba); - else if (appearance->underline == PANGO_UNDERLINE_ERROR) - { - if (!text_renderer->error_color) - { - static const GdkRGBA red = { 1, 0, 0, 1 }; - text_renderer->error_color = gdk_rgba_copy (&red); - } - - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, text_renderer->error_color); - } - else - text_renderer_set_rgba (text_renderer, PANGO_RENDER_PART_UNDERLINE, fg_rgba); - - if (fg_rgba != appearance->fg_rgba) - gdk_rgba_free (fg_rgba); -} - -static void -set_color (GtkTextRenderer *text_renderer, - PangoRenderPart part) -{ - PangoColor *color; - GdkRGBA rgba; - guint16 alpha; - - cairo_save (text_renderer->cr); - - color = pango_renderer_get_color (PANGO_RENDERER (text_renderer), part); - alpha = pango_renderer_get_alpha (PANGO_RENDERER (text_renderer), part); - if (color) - { - rgba.red = color->red / 65535.; - rgba.green = color->green / 65535.; - rgba.blue = color->blue / 65535.; - rgba.alpha = alpha / 65535.; - gdk_cairo_set_source_rgba (text_renderer->cr, &rgba); - } -} - -static void -unset_color (GtkTextRenderer *text_renderer) -{ - cairo_restore (text_renderer->cr); -} - -static void -gtk_text_renderer_draw_glyphs (PangoRenderer *renderer, - PangoFont *font, - PangoGlyphString *glyphs, - int x, - int y) -{ - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - - set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); - - cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE); - pango_cairo_show_glyph_string (text_renderer->cr, font, glyphs); - - unset_color (text_renderer); -} - -static void -gtk_text_renderer_draw_glyph_item (PangoRenderer *renderer, - const char *text, - PangoGlyphItem *glyph_item, - int x, - int y) -{ - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - - set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); - - cairo_move_to (text_renderer->cr, (double)x / PANGO_SCALE, (double)y / PANGO_SCALE); - pango_cairo_show_glyph_item (text_renderer->cr, text, glyph_item); - - unset_color (text_renderer); -} - -static void -gtk_text_renderer_draw_rectangle (PangoRenderer *renderer, - PangoRenderPart part, - int x, - int y, - int width, - int height) -{ - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - - set_color (text_renderer, part); - - cairo_rectangle (text_renderer->cr, - (double)x / PANGO_SCALE, (double)y / PANGO_SCALE, - (double)width / PANGO_SCALE, (double)height / PANGO_SCALE); - cairo_fill (text_renderer->cr); - - unset_color (text_renderer); -} - -static void -gtk_text_renderer_draw_trapezoid (PangoRenderer *renderer, - PangoRenderPart part, - double y1_, - double x11, - double x21, - double y2, - double x12, - double x22) -{ - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - cairo_t *cr; - cairo_matrix_t matrix; - - set_color (text_renderer, part); - - cr = text_renderer->cr; - - cairo_get_matrix (cr, &matrix); - matrix.xx = matrix.yy = 1.0; - matrix.xy = matrix.yx = 0.0; - cairo_set_matrix (cr, &matrix); - - cairo_move_to (cr, x11, y1_); - cairo_line_to (cr, x21, y1_); - cairo_line_to (cr, x22, y2); - cairo_line_to (cr, x12, y2); - cairo_close_path (cr); - - cairo_fill (cr); - - unset_color (text_renderer); -} - -static void -gtk_text_renderer_draw_error_underline (PangoRenderer *renderer, - int x, - int y, - int width, - int height) -{ - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - - set_color (text_renderer, PANGO_RENDER_PART_UNDERLINE); - - pango_cairo_show_error_underline (text_renderer->cr, - (double)x / PANGO_SCALE, (double)y / PANGO_SCALE, - (double)width / PANGO_SCALE, (double)height / PANGO_SCALE); - - unset_color (text_renderer); -} - -static void -gtk_text_renderer_draw_shape (PangoRenderer *renderer, - PangoAttrShape *attr, - int x, - int y) -{ - GtkTextRenderer *text_renderer = GTK_TEXT_RENDERER (renderer); - - if (attr->data == NULL) - { - /* This happens if we have an empty widget anchor. Draw - * something empty-looking. - */ - GdkRectangle shape_rect; - cairo_t *cr; - - shape_rect.x = PANGO_PIXELS (x); - shape_rect.y = PANGO_PIXELS (y + attr->logical_rect.y); - shape_rect.width = PANGO_PIXELS (x + attr->logical_rect.width) - shape_rect.x; - shape_rect.height = PANGO_PIXELS (y + attr->logical_rect.y + attr->logical_rect.height) - shape_rect.y; - - set_color (text_renderer, PANGO_RENDER_PART_FOREGROUND); - - cr = text_renderer->cr; - - cairo_set_line_width (cr, 1.0); - - cairo_rectangle (cr, - shape_rect.x + 0.5, shape_rect.y + 0.5, - shape_rect.width - 1, shape_rect.height - 1); - cairo_move_to (cr, shape_rect.x + 0.5, shape_rect.y + 0.5); - cairo_line_to (cr, - shape_rect.x + shape_rect.width - 0.5, - shape_rect.y + shape_rect.height - 0.5); - cairo_move_to (cr, shape_rect.x + 0.5, - shape_rect.y + shape_rect.height - 0.5); - cairo_line_to (cr, shape_rect.x + shape_rect.width - 0.5, - shape_rect.y + 0.5); - - cairo_stroke (cr); - - unset_color (text_renderer); - } - else if (GDK_IS_TEXTURE (attr->data)) - { - cairo_t *cr = text_renderer->cr; - GdkTexture *texture = GDK_TEXTURE (attr->data); - cairo_surface_t *surface; - - surface = gdk_texture_download_surface (texture); - - cairo_save (cr); - - cairo_set_source_surface (cr, surface, - PANGO_PIXELS (x), - PANGO_PIXELS (y) - gdk_texture_get_height (texture)); - cairo_paint (cr); - - cairo_restore (cr); - - cairo_surface_destroy (surface); - } - else if (GTK_IS_WIDGET (attr->data)) - { - GtkWidget *widget; - - widget = GTK_WIDGET (attr->data); - - text_renderer->widgets = g_list_prepend (text_renderer->widgets, - g_object_ref (widget)); - } - else - g_assert_not_reached (); /* not a texture or widget */ -} - -static void -gtk_text_renderer_finalize (GObject *object) -{ - G_OBJECT_CLASS (_gtk_text_renderer_parent_class)->finalize (object); -} - -static void -_gtk_text_renderer_init (GtkTextRenderer *renderer) -{ -} - -static void -_gtk_text_renderer_class_init (GtkTextRendererClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass); - - renderer_class->prepare_run = gtk_text_renderer_prepare_run; - renderer_class->draw_glyphs = gtk_text_renderer_draw_glyphs; - renderer_class->draw_glyph_item = gtk_text_renderer_draw_glyph_item; - renderer_class->draw_rectangle = gtk_text_renderer_draw_rectangle; - renderer_class->draw_trapezoid = gtk_text_renderer_draw_trapezoid; - renderer_class->draw_error_underline = gtk_text_renderer_draw_error_underline; - renderer_class->draw_shape = gtk_text_renderer_draw_shape; - - object_class->finalize = gtk_text_renderer_finalize; -} - -static void -text_renderer_set_state (GtkTextRenderer *text_renderer, - int state) -{ - text_renderer->state = state; -} - -static void -text_renderer_begin (GtkTextRenderer *text_renderer, - GtkWidget *widget, - cairo_t *cr) -{ - GtkStyleContext *context; - GdkRGBA color; - GtkCssNode *text_node; - - text_renderer->widget = widget; - text_renderer->cr = cr; - - context = gtk_widget_get_style_context (widget); - - text_node = gtk_text_view_get_text_node ((GtkTextView *)widget); - gtk_style_context_save_to_node (context, text_node); - - gtk_style_context_get_color (context, &color); - - cairo_save (cr); - - gdk_cairo_set_source_rgba (cr, &color); -} - -/* Returns a GSList of (referenced) widgets encountered while drawing. - */ -static void -text_renderer_end (GtkTextRenderer *text_renderer) -{ - GtkStyleContext *context; - - cairo_restore (text_renderer->cr); - - context = gtk_widget_get_style_context (text_renderer->widget); - - gtk_style_context_restore (context); - - text_renderer->widget = NULL; - text_renderer->cr = NULL; - - if (text_renderer->error_color) - { - gdk_rgba_free (text_renderer->error_color); - text_renderer->error_color = NULL; - } -} - -static cairo_region_t * -get_selected_clip (GtkTextRenderer *text_renderer, - PangoLayout *layout, - PangoLayoutLine *line, - int x, - int y, - int height, - int start_index, - int end_index) -{ - gint *ranges; - gint n_ranges, i; - cairo_region_t *clip_region = cairo_region_create (); - - pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges); - - for (i=0; i < n_ranges; i++) - { - GdkRectangle rect; - - rect.x = x + PANGO_PIXELS (ranges[2*i]); - rect.y = y; - rect.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]); - rect.height = height; - - cairo_region_union_rectangle (clip_region, &rect); - } - - g_free (ranges); - return clip_region; -} - -static void -render_para (GtkTextRenderer *text_renderer, - 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; - - iter = pango_layout_get_iter (layout); - screen_width = line_display->total_width; - - context = gtk_widget_get_style_context (text_renderer->widget); - selection_node = gtk_text_view_get_selection_node ((GtkTextView*)text_renderer->widget); - gtk_style_context_save_to_node (context, selection_node); - - gtk_style_context_get (context, "background-color", &selection, NULL); - - gtk_style_context_restore (context); - - 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 */ - { - cairo_t *cr = text_renderer->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); - - text_renderer_set_state (text_renderer, SELECTED); - pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), - line, - line_rect.x, - baseline); - } - else - { - if (line_display->pg_bg_rgba_set) - { - cairo_t *cr = text_renderer->cr; - - cairo_save (cr); - - gdk_cairo_set_source_rgba (text_renderer->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); - } - - text_renderer_set_state (text_renderer, NORMAL); - pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), - 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) - { - cairo_t *cr = text_renderer->cr; - cairo_region_t *clip_region = get_selected_clip (text_renderer, 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); - - text_renderer_set_state (text_renderer, SELECTED); - pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), - line, - line_rect.x, - baseline); - - cairo_restore (cr); - - /* 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))) - { - 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); - } - - 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); - - 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); - } - } - else if (line_display->has_block_cursor && - gtk_widget_has_focus (text_renderer->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))) - { - GdkRectangle cursor_rect; - GdkRGBA cursor_color; - cairo_t *cr = text_renderer->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); - - text_renderer_set_state (text_renderer, CURSOR); - - pango_renderer_draw_layout_line (PANGO_RENDERER (text_renderer), - line, - line_rect.x, - baseline); - gdk_rgba_free (color); - } - - cairo_restore (cr); - } - } - - byte_offset += line->length; - } - while (pango_layout_iter_next_line (iter)); - - gdk_rgba_free (selection); - pango_layout_iter_free (iter); -} - -static GtkTextRenderer * -get_text_renderer (void) -{ - static GtkTextRenderer *text_renderer = NULL; - - if (!text_renderer) - text_renderer = g_object_new (GTK_TYPE_TEXT_RENDERER, NULL); - - return text_renderer; -} - -void -gtk_text_layout_snapshot (GtkTextLayout *layout, - GtkWidget *widget, - GtkSnapshot *snapshot, - const GdkRectangle *clip) -{ - GtkStyleContext *context; - gint offset_y; - GtkTextRenderer *text_renderer; - GtkTextIter selection_start, selection_end; - gboolean have_selection; - GSList *line_list; - GSList *tmp_list; - cairo_t *cr; - - 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); - - 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 */ - - cr = gtk_snapshot_append_cairo (snapshot, - &GRAPHENE_RECT_INIT(clip->x, clip->y, clip->width, clip->height)); - text_renderer = get_text_renderer (); - text_renderer_begin (text_renderer, widget, cr); - - /* text_renderer_begin/end does cairo_save/restore */ - cairo_translate (cr, 0, offset_y); - - 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) - { - GtkTextLineDisplay *line_display; - gint selection_start_index = -1; - gint selection_end_index = -1; - - GtkTextLine *line = tmp_list->data; - - 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 (text_renderer, 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_render_insertion_cursor (context, cr, - line_display->x_offset, line_display->top_margin, - line_display->layout, index, dir); - } - } - } /* line_display->height > 0 */ - - cairo_translate (cr, 0, line_display->height); - gtk_text_layout_free_line_display (layout, line_display); - - tmp_list = tmp_list->next; - } - - gtk_text_layout_wrap_loop_end (layout); - text_renderer_end (text_renderer); - - g_slist_free (line_list); - - cairo_destroy (cr); -} diff --git a/gtk/gtktextdisplayprivate.h b/gtk/gtktextdisplayprivate.h deleted file mode 100644 index e4385e5b47..0000000000 --- a/gtk/gtktextdisplayprivate.h +++ /dev/null @@ -1,101 +0,0 @@ -/* gtktextdisplay.c - display layed-out text - * - * Copyright (c) 1992-1994 The Regents of the University of California. - * Copyright (c) 1994-1997 Sun Microsystems, Inc. - * Copyright (c) 2000 Red Hat, Inc. - * Tk->Gtk port by Havoc Pennington - * - * This file can be used under your choice of two licenses, the LGPL - * and the original Tk license. - * - * LGPL: - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Original Tk license: - * - * This software is copyrighted by the Regents of the University of - * California, Sun Microsystems, Inc., and other parties. The - * following terms apply to all files associated with the software - * unless explicitly disclaimed in individual files. - * - * The authors hereby grant permission to use, copy, modify, - * distribute, and license this software and its documentation for any - * purpose, provided that existing copyright notices are retained in - * all copies and that this notice is included verbatim in any - * distributions. No written agreement, license, or royalty fee is - * required for any of the authorized uses. Modifications to this - * software may be copyrighted by their authors and need not follow - * the licensing terms described here, provided that the new terms are - * clearly indicated on the first page of each file where they apply. - * - * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY - * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL - * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, - * OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND - * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, - * AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE - * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - * - * GOVERNMENT USE: If you are acquiring this software on behalf of the - * U.S. government, the Government shall have only "Restricted Rights" - * in the software and related documentation as defined in the Federal - * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you - * are acquiring the software on behalf of the Department of Defense, - * the software shall be classified as "Commercial Computer Software" - * and the Government shall have only "Restricted Rights" as defined - * in Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the - * foregoing, the authors grant the U.S. Government and others acting - * in its behalf permission to use and distribute the software in - * accordance with the terms specified in this license. - * - */ -/* - * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS - * file for a list of people on the GTK+ Team. See the ChangeLog - * files for a list of changes. These files are distributed with - * GTK+ at ftp://ftp.gtk.org/pub/gtk/. - */ - -#ifndef __GTK_TEXT_DISPLAY_PRIVATE_H__ -#define __GTK_TEXT_DISPLAY_PRIVATE_H__ - -#include "gtktextlayoutprivate.h" - -G_BEGIN_DECLS - -/* A semi-public header intended for use by code that also - * uses GtkTextLayout - */ - -/* The drawable should be pre-initialized to your preferred background. - * widget - Widget to grab some style info from - * snapshot - Snapshot to render to, matrix set so that (0, 0) - * is the top left of the layout - * clip - visible area - */ -void gtk_text_layout_snapshot (GtkTextLayout *layout, - GtkWidget *widget, - GtkSnapshot *snapshot, - const GdkRectangle *clip); - - -G_END_DECLS - -#endif /* __GTK_TEXT_DISPLAY_PRIVATE_H__ */ diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c index 79785773d1..508aede412 100644 --- a/gtk/gtktextlayout.c +++ b/gtk/gtktextlayout.c @@ -77,12 +77,16 @@ #include "config.h" #include "gtkmarshalers.h" +#include "gtkstylecontextprivate.h" #include "gtktextlayoutprivate.h" #include "gtktextbtree.h" #include "gtktextbufferprivate.h" #include "gtktextiterprivate.h" #include "gtktextutil.h" +#include "gskpango.h" #include "gtkintl.h" +#include "gtkwidgetprivate.h" +#include "gtktextviewprivate.h" #include #include @@ -3785,3 +3789,336 @@ gtk_text_layout_buffer_delete_range (GtkTextBuffer *textbuffer, gtk_text_layout_update_cursor_line (layout); } + +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 }; + + g_return_if_fail (GTK_IS_TEXT_VIEW (crenderer->widget)); + + 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 */ + { + gtk_snapshot_append_color (crenderer->snapshot, + selection, + &GRAPHENE_RECT_INIT (line_display->left_margin, + selection_y, + screen_width, + selection_height)); + gsk_pango_renderer_set_state (crenderer, GSK_PANGO_RENDERER_SELECTED); + pango_renderer_draw_layout_line (PANGO_RENDERER (crenderer), + line, + line_rect.x, + baseline); + } + else + { + if (line_display->pg_bg_rgba_set) + gtk_snapshot_append_color (crenderer->snapshot, + &line_display->pg_bg_rgba, + &GRAPHENE_RECT_INIT (line_display->left_margin, + selection_y, + screen_width, + selection_height)); + + gsk_pango_renderer_set_state (crenderer, GSK_PANGO_RENDERER_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 = NULL; + 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, GSK_PANGO_RENDERER_SELECTED); + + for (i = 0; i < n_ranges; i++) + { + graphene_rect_t bounds; + + bounds.origin.x = line_display->x_offset + PANGO_PIXELS (ranges[2*i]); + bounds.origin.y = selection_y; + bounds.size.width = PANGO_PIXELS (ranges[2*i + 1]) - PANGO_PIXELS (ranges[2*i]); + bounds.size.height = selection_height; + + gtk_snapshot_append_color (crenderer->snapshot, selection, &bounds); + 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); + } + + 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))) + gtk_snapshot_append_color (crenderer->snapshot, + selection, + &GRAPHENE_RECT_INIT (line_display->left_margin, + selection_y, + PANGO_PIXELS (line_rect.x) - line_display->left_margin, + selection_height)); + + 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 = line_display->left_margin + + screen_width + - PANGO_PIXELS (line_rect.x) + - PANGO_PIXELS (line_rect.width); + gtk_snapshot_append_color (crenderer->snapshot, + selection, + &GRAPHENE_RECT_INIT (PANGO_PIXELS (line_rect.x) + PANGO_PIXELS (line_rect.width), + selection_y, + nonlayout_width, + selection_height)); + } + } + 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, GSK_PANGO_RENDERER_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_snapshot (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 = gsk_pango_renderer_acquire (); + + crenderer->widget = widget; + crenderer->snapshot = snapshot; + crenderer->fg_color = color; + + graphene_rect_init (&crenderer->bounds, + clip->x, + clip->y, + clip->width, + clip->height); + + 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); + + gsk_pango_renderer_release (crenderer); +} diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h index 4a44731373..7ba6627b04 100644 --- a/gtk/gtktextlayoutprivate.h +++ b/gtk/gtktextlayoutprivate.h @@ -113,6 +113,14 @@ struct _GtkTextLayout gint width; gint height; + /* Pixel offsets from the left and from the top to be used when we + * draw; these allow us to create left/top margins. We don't need + * anything special for bottom/right margins, because those don't + * affect drawing. + */ + /* gint left_edge; */ + /* gint top_edge; */ + GtkTextBuffer *buffer; gint left_padding; @@ -137,7 +145,7 @@ struct _GtkTextLayout /* Whether we are allowed to wrap right now */ gint wrap_loop_count; - + /* Whether to show the insertion cursor */ guint cursor_visible : 1; @@ -389,6 +397,11 @@ void gtk_text_anchored_child_set_layout (GtkWidget *child, void gtk_text_layout_spew (GtkTextLayout *layout); +void gtk_text_layout_snapshot (GtkTextLayout *layout, + GtkWidget *widget, + GtkSnapshot *snapshot, + const GdkRectangle *clip); + G_END_DECLS #endif /* __GTK_TEXT_LAYOUT_PRIVATE_H__ */ diff --git a/gtk/gtktextutil.c b/gtk/gtktextutil.c index 19c2f7982d..e806c6a205 100644 --- a/gtk/gtktextutil.c +++ b/gtk/gtktextutil.c @@ -27,8 +27,8 @@ #include "gtktextview.h" #include "gtktextutil.h" -#include "gtktextdisplayprivate.h" #include "gtktextbuffer.h" +#include "gtktextlayoutprivate.h" #include "gtkmenuitem.h" #include "gtkintl.h" diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index baaf8e4bff..73c8521455 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -41,7 +41,6 @@ #include "gtkrenderbackgroundprivate.h" #include "gtkseparatormenuitem.h" #include "gtksettings.h" -#include "gtktextdisplayprivate.h" #include "gtktextiterprivate.h" #include "gtkimmulticontext.h" #include "gtkprivate.h" diff --git a/gtk/meson.build b/gtk/meson.build index 428e1e49ab..22fe73077f 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -366,7 +366,6 @@ gtk_public_sources = files([ 'gtktextattributes.c', 'gtktextbuffer.c', 'gtktextchild.c', - 'gtktextdisplay.c', 'gtktexthandle.c', 'gtktextiter.c', 'gtktextlayout.c', From 064ad424326ebdee48bd6a1d13da87924c9ad6fd Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 21 Jul 2019 13:06:23 -0700 Subject: [PATCH 3/3] text view: Smooth cursor blinking Fade the text cursor in and out, instead of abruptly turning it on and off. --- gtk/gtktextlayout.c | 14 ++- gtk/gtktextlayoutprivate.h | 3 +- gtk/gtktextutil.c | 2 +- gtk/gtktextview.c | 185 +++++++++++++++++++++---------------- 4 files changed, 121 insertions(+), 83 deletions(-) diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c index 508aede412..acf003912e 100644 --- a/gtk/gtktextlayout.c +++ b/gtk/gtktextlayout.c @@ -3795,7 +3795,8 @@ render_para (GskPangoRenderer *crenderer, int offset_y, GtkTextLineDisplay *line_display, int selection_start_index, - int selection_end_index) + int selection_end_index, + float cursor_alpha) { GtkStyleContext *context; PangoLayout *layout = line_display->layout; @@ -3972,6 +3973,7 @@ render_para (GskPangoRenderer *crenderer, * (normally white on black) */ _gtk_style_context_get_cursor_color (context, &cursor_color, NULL); + gtk_snapshot_push_opacity (crenderer->snapshot, cursor_alpha); gtk_snapshot_append_color (crenderer->snapshot, &cursor_color, &bounds); /* draw text under the cursor if any */ @@ -3985,6 +3987,7 @@ render_para (GskPangoRenderer *crenderer, baseline); gtk_snapshot_pop (crenderer->snapshot); } + gtk_snapshot_pop (crenderer->snapshot); } } @@ -4002,7 +4005,8 @@ void gtk_text_layout_snapshot (GtkTextLayout *layout, GtkWidget *widget, GtkSnapshot *snapshot, - const GdkRectangle *clip) + const GdkRectangle *clip, + float cursor_alpha) { GskPangoRenderer *crenderer; GtkStyleContext *context; @@ -4085,7 +4089,8 @@ gtk_text_layout_snapshot (GtkTextLayout *layout, } render_para (crenderer, offset_y, line_display, - selection_start_index, selection_end_index); + selection_start_index, selection_end_index, + cursor_alpha); /* We paint the cursors last, because they overlap another chunk * and need to appear on top. @@ -4094,6 +4099,7 @@ gtk_text_layout_snapshot (GtkTextLayout *layout, { int i; + gtk_snapshot_push_opacity (crenderer->snapshot, cursor_alpha); for (i = 0; i < line_display->cursors->len; i++) { int index; @@ -4106,6 +4112,8 @@ gtk_text_layout_snapshot (GtkTextLayout *layout, line_display->x_offset, offset_y + line_display->top_margin, line_display->layout, index, dir); } + + gtk_snapshot_pop (crenderer->snapshot); } } /* line_display->height > 0 */ diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h index 7ba6627b04..7aff2074ff 100644 --- a/gtk/gtktextlayoutprivate.h +++ b/gtk/gtktextlayoutprivate.h @@ -400,7 +400,8 @@ void gtk_text_layout_spew (GtkTextLayout *layout); void gtk_text_layout_snapshot (GtkTextLayout *layout, GtkWidget *widget, GtkSnapshot *snapshot, - const GdkRectangle *clip); + const GdkRectangle *clip, + float cursor_alpha); G_END_DECLS diff --git a/gtk/gtktextutil.c b/gtk/gtktextutil.c index e806c6a205..2c3c43d8bc 100644 --- a/gtk/gtktextutil.c +++ b/gtk/gtktextutil.c @@ -335,7 +335,7 @@ gtk_text_util_create_rich_drag_icon (GtkWidget *widget, snapshot = gtk_snapshot_new (); - gtk_text_layout_snapshot (layout, widget, snapshot, &(GdkRectangle) { 0, 0, layout_width, layout_height }); + gtk_text_layout_snapshot (layout, widget, snapshot, &(GdkRectangle) { 0, 0, layout_width, layout_height }, 1.0); g_object_unref (layout); g_object_unref (new_buffer); diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 73c8521455..4b30b8d653 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -209,7 +209,10 @@ struct _GtkTextViewPrivate GtkTextMark *first_para_mark; /* Mark at the beginning of the first onscreen paragraph */ gint first_para_pixels; /* Offset of top of screen in the first onscreen paragraph */ - guint blink_timeout; + guint64 blink_start_time; + guint blink_tick; + float cursor_alpha; + guint scroll_timeout; guint first_validate_idle; /* Idle to revalidate onscreen portion, runs before resize */ @@ -5346,12 +5349,6 @@ gtk_text_view_paint (GtkWidget *widget, g_warning (G_STRLOC ": somehow some text lines were modified or scrolling occurred since the last validation of lines on the screen - may be a text widget bug."); g_assert_not_reached (); } - -#if 0 - printf ("painting %d,%d %d x %d\n", - area->x, area->y, - area->width, area->height); -#endif gtk_snapshot_save (snapshot); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset)); @@ -5364,7 +5361,8 @@ gtk_text_view_paint (GtkWidget *widget, priv->yoffset, gtk_widget_get_width (widget), gtk_widget_get_height (widget) - }); + }, + priv->cursor_alpha); gtk_snapshot_restore (snapshot); } @@ -5661,15 +5659,80 @@ get_cursor_blink_timeout (GtkTextView *text_view) * Blink! */ -static gint -blink_cb (gpointer data) +typedef struct { + guint64 start; + guint64 end; +} BlinkData; + +static gboolean blink_cb (GtkWidget *widget, + GdkFrameClock *clock, + gpointer user_data); + + +static void +add_blink_timeout (GtkTextView *self) +{ + GtkTextViewPrivate *priv = self->priv; + BlinkData *data; + int blink_time; + + priv->blink_start_time = g_get_monotonic_time (); + priv->cursor_alpha = 1.0; + + blink_time = get_cursor_time (self); + + data = g_new (BlinkData, 1); + data->start = priv->blink_start_time; + data->end = data->start + blink_time * 1000; + + priv->blink_tick = gtk_widget_add_tick_callback (GTK_WIDGET (self), + blink_cb, + data, + g_free); +} + +static void +remove_blink_timeout (GtkTextView *self) +{ + GtkTextViewPrivate *priv = self->priv; + + if (priv->blink_tick) + { + gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->blink_tick); + priv->blink_tick = 0; + } +} + +static float +blink_alpha (float phase) +{ + /* keep it simple, and split the blink cycle evenly + * into visible, fading out, invisible, fading in + */ + if (phase < 0.25) + return 1; + else if (phase < 0.5) + return 1 - 4 * (phase - 0.25); + else if (phase < 0.75) + return 0; + else + return 4 * (phase - 0.75); +} + +static gboolean +blink_cb (GtkWidget *widget, + GdkFrameClock *clock, + gpointer user_data) { GtkTextView *text_view; GtkTextViewPrivate *priv; - gboolean visible; gint blink_timeout; + gint blink_time; + guint64 now; + float phase; + BlinkData *data = user_data; - text_view = GTK_TEXT_VIEW (data); + text_view = GTK_TEXT_VIEW (widget); priv = text_view->priv; if (!gtk_widget_has_focus (GTK_WIDGET (text_view))) @@ -5677,7 +5740,6 @@ blink_cb (gpointer data) g_warning ("GtkTextView - did not receive a focus-out.\n" "If you handle this event, you must return\n" "GDK_EVENT_PROPAGATE so the text view gets the event as well"); - gtk_text_view_check_cursor_blink (text_view); return FALSE; @@ -5686,47 +5748,41 @@ blink_cb (gpointer data) g_assert (priv->layout); g_assert (cursor_visible (text_view)); - visible = gtk_text_layout_get_cursor_visible (priv->layout); - blink_timeout = get_cursor_blink_timeout (text_view); - if (priv->blink_time > 1000 * blink_timeout && - blink_timeout < G_MAXINT/1000) + blink_time = get_cursor_time (text_view); + + now = g_get_monotonic_time (); + + if (now > priv->blink_start_time + blink_timeout * 1000000) { /* we've blinked enough without the user doing anything, stop blinking */ - visible = 0; - priv->blink_timeout = 0; - } - else if (visible) - { - priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - text_view); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - } - else - { - priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - text_view); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - priv->blink_time += get_cursor_time (text_view); + priv->cursor_alpha = 1.0; + remove_blink_timeout (text_view); + gtk_widget_queue_draw (widget); + + return G_SOURCE_REMOVE; } - gtk_text_layout_set_cursor_visible (priv->layout, !visible); + phase = (now - data->start) / (float) (data->end - data->start); - /* Remove ourselves */ - return FALSE; + priv->cursor_alpha = blink_alpha (phase); + + if (now >= data->end) + { + data->start = data->end; + data->end = data->start + blink_time * 1000; + } + + gtk_widget_queue_draw (widget); + + return G_SOURCE_CONTINUE; } static void gtk_text_view_stop_cursor_blink (GtkTextView *text_view) { - if (text_view->priv->blink_timeout) - { - g_source_remove (text_view->priv->blink_timeout); - text_view->priv->blink_timeout = 0; - } + remove_blink_timeout (text_view); } static void @@ -5734,52 +5790,25 @@ gtk_text_view_check_cursor_blink (GtkTextView *text_view) { GtkTextViewPrivate *priv = text_view->priv; - if (priv->layout != NULL && - cursor_visible (text_view) && - gtk_widget_has_focus (GTK_WIDGET (text_view))) + if (cursor_blinks (text_view)) { - if (cursor_blinks (text_view)) - { - if (priv->blink_timeout == 0) - { - gtk_text_layout_set_cursor_visible (priv->layout, TRUE); - - priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - text_view); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - } - } - else - { - gtk_text_view_stop_cursor_blink (text_view); - gtk_text_layout_set_cursor_visible (priv->layout, TRUE); - } + if (!priv->blink_tick) + add_blink_timeout (text_view); } else { - gtk_text_view_stop_cursor_blink (text_view); - gtk_text_layout_set_cursor_visible (priv->layout, FALSE); + if (priv->blink_tick) + remove_blink_timeout (text_view); } } static void gtk_text_view_pend_cursor_blink (GtkTextView *text_view) { - GtkTextViewPrivate *priv = text_view->priv; - - if (priv->layout != NULL && - cursor_visible (text_view) && - gtk_widget_has_focus (GTK_WIDGET (text_view)) && - cursor_blinks (text_view)) + if (cursor_blinks (text_view)) { - gtk_text_view_stop_cursor_blink (text_view); - gtk_text_layout_set_cursor_visible (priv->layout, TRUE); - - priv->blink_timeout = g_timeout_add (get_cursor_time (text_view) * CURSOR_PEND_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - text_view); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); + remove_blink_timeout (text_view); + add_blink_timeout (text_view); } } @@ -5788,7 +5817,7 @@ gtk_text_view_reset_blink_time (GtkTextView *text_view) { GtkTextViewPrivate *priv = text_view->priv; - priv->blink_time = 0; + priv->blink_start_time = g_get_monotonic_time (); }