Compare commits

...

2 Commits

Author SHA1 Message Date
Owen W. Taylor
10e2ce1c6f GtkListBase and GtkColumnView: gtk_snapshot_push_scroll_offset()
Use gtk_snapshot_push_scroll_offset() to approve the scrolling
appearance of GtkListBase subclasses and GtkColumnView.
2024-07-23 13:17:29 -04:00
Owen W. Taylor
dc295ba9a4 [WIP] Jiggle free scrolling for fractional scales
When GTK is rendering at a fractional scale, as we scroll
in integer application pixels, scrolled content lines up
differently with the device grid, and renders differently.
In particular, the snapping that the GL renderer does to
the device grid causes lines to jiggle up and down as
you scroll.

Add gtk_snapshot_push_scroll_offset() which offsets children
in a way that animates nicely by rounding the offset to the
device grid, and use that to implement jiggle-free scrolling
for GtkTextView and GtkViewport. This also provides smooth
device pixel scrolling when the window has integer scale
factor like 200%.
2024-07-22 17:12:53 -04:00
6 changed files with 195 additions and 19 deletions

View File

@@ -37,6 +37,7 @@
#include "gtkscrollable.h"
#include "gtkscrollinfoprivate.h"
#include "gtksizerequest.h"
#include "gtksnapshotprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
@@ -548,6 +549,27 @@ gtk_column_view_hide (GtkWidget *widget)
GTK_WIDGET_CLASS (gtk_column_view_parent_class)->hide (widget);
}
static void
gtk_column_view_snapshot(GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkColumnView *self = GTK_COLUMN_VIEW (widget);
double dx;
dx = gtk_adjustment_get_value (self->hadjustment);
/* add a translation to the child nodes in a way that will look good
* when animating */
gtk_snapshot_push_scroll_offset (snapshot,
gtk_widget_get_surface (widget),
-dx, 0);
/* We need to undo the (less good looking) offset we add to children */
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT ((int)dx, 0));
GTK_WIDGET_CLASS (gtk_column_view_parent_class)->snapshot (widget, snapshot);
gtk_snapshot_pop (snapshot);
}
static void
gtk_column_view_activate_cb (GtkListView *listview,
guint pos,
@@ -795,6 +817,7 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
widget_class->unroot = gtk_column_view_unroot;
widget_class->show = gtk_column_view_show;
widget_class->hide = gtk_column_view_hide;
widget_class->snapshot = gtk_column_view_snapshot;
gobject_class->dispose = gtk_column_view_dispose;
gobject_class->finalize = gtk_column_view_finalize;

View File

@@ -36,7 +36,7 @@
#include "gtkscrollable.h"
#include "gtkscrollinfoprivate.h"
#include "gtksingleselection.h"
#include "gtksnapshot.h"
#include "gtksnapshotprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
@@ -960,6 +960,29 @@ gtk_list_base_set_focus_child (GtkWidget *widget,
}
}
static void
gtk_list_base_snapshot(GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkListBase *self = GTK_LIST_BASE (widget);
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
double dx, dy;
dx = gtk_adjustment_get_value (priv->adjustment[OPPOSITE_ORIENTATION (priv->orientation)]);
dy = gtk_adjustment_get_value (priv->adjustment[priv->orientation]);
/* add a translation to the child nodes in a way that will look good
* when animating */
gtk_snapshot_push_scroll_offset (snapshot,
gtk_widget_get_surface (widget),
-dx, -dy);
/* We need to undo the (less good looking) offset we add to children */
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT ((int)dx, (int)dy));
GTK_WIDGET_CLASS (gtk_list_base_parent_class)->snapshot (widget, snapshot);
gtk_snapshot_pop (snapshot);
}
static void
gtk_list_base_select_item_action (GtkWidget *widget,
const char *action_name,
@@ -1218,6 +1241,7 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
widget_class->focus = gtk_list_base_focus;
widget_class->grab_focus = gtk_list_base_grab_focus;
widget_class->set_focus_child = gtk_list_base_set_focus_child;
widget_class->snapshot = gtk_list_base_snapshot;
gobject_class->dispose = gtk_list_base_dispose;
gobject_class->get_property = gtk_list_base_get_property;

View File

@@ -825,6 +825,68 @@ gtk_snapshot_ensure_identity (GtkSnapshot *snapshot)
gtk_snapshot_autopush_transform (snapshot);
}
/**
* gtk_snapshot_push_scroll_offset
* @snapshot: a `GtkSnapshot`
* @target_surface: the surface that the snapshot will be rendered onto
* **FIXME**: this goes away if we add a proper render node for this.
* @scroll_x: amount to translate child nodes by
* @scroll_y: amount to translate child nodes by
*
* Adds a translation to child nodes of the render tree that is handled
* in an optimal way for animation, as during scrolling.
*/
void
gtk_snapshot_push_scroll_offset (GtkSnapshot *snapshot,
GdkSurface *target_surface,
double scroll_x,
double scroll_y)
{
const GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
graphene_point_t offset;
if (target_surface &&
gsk_transform_get_category (state->transform) >= GSK_TRANSFORM_CATEGORY_2D_AFFINE)
{
double device_scale;
double device_x, device_y;
float scale_x, scale_y, dx, dy;
gsk_transform_to_affine (state->transform, &scale_x, &scale_y, &dx, &dy);
device_scale = gdk_surface_get_scale (target_surface);
scale_x *= device_scale;
scale_y *= device_scale;
/* We want the difference between one offset and another to be an
* integral number of device pixels to avoid scrolling changing
* appearance, but we don't want the offsets themselves to be
* an integral number of device pixels, because that can result
* in a glyph being positioned, say, at 11.0001 in one frame
* and then at 110.9999 in another frame once we've scrolled
* by 100 pixels, and those get rounded differently by the
* glyph code, which uses floorf(position). 0.125 (1/8th) is
* because there are 4 phases.
*/
device_x = 0.125 + round (scroll_x * scale_y + dx);
device_y = 0.125 + round (scroll_y * scale_y + dy);
offset.x = (device_x - dx) / scale_x;
offset.y = (device_y - dy) / scale_y;
}
else
{
offset.x = scroll_x;
offset.y = scroll_y;
}
state = gtk_snapshot_push_state (snapshot,
state->transform,
gtk_snapshot_collect_default,
NULL);
gtk_snapshot_translate (snapshot, &offset);
}
/**
* gtk_snapshot_push_repeat:
* @snapshot: a `GtkSnapshot`

View File

@@ -37,5 +37,10 @@ GskRenderNode * gtk_snapshot_pop_collect (GtkSnapshot
void gtk_snapshot_push_subsurface (GtkSnapshot *snapshot,
GdkSubsurface *subsurface);
void gtk_snapshot_push_scroll_offset (GtkSnapshot *snapshot,
GdkSurface *target_surface,
double scroll_x,
double scroll_y);
G_END_DECLS

View File

@@ -52,6 +52,7 @@
#include "gtkscrollable.h"
#include "gtksettings.h"
#include "gtksnapshot.h"
#include "gtksnapshotprivate.h"
#include "gtktextiterprivate.h"
#include "gtktexthandleprivate.h"
#include "gtktextviewchildprivate.h"
@@ -242,6 +243,9 @@ struct _GtkTextViewPrivate
*/
int yoffset;
/* Same thing, but with full double resolution*/
double xoffset_unrounded, yoffset_unrounded;
/* Width and height of the buffer */
int width;
int height;
@@ -6024,6 +6028,17 @@ gtk_text_view_motion (GtkEventController *controller,
gtk_text_view_unobscure_mouse_cursor (GTK_TEXT_VIEW (user_data));
}
static void
push_snapshot_scroll_offset (GtkTextView *text_view,
GtkSnapshot *snapshot)
{
GtkTextViewPrivate *priv = text_view->priv;
gtk_snapshot_push_scroll_offset (snapshot,
gtk_widget_get_surface (GTK_WIDGET (text_view)),
-priv->xoffset_unrounded, -priv->yoffset_unrounded);
}
static void
gtk_text_view_paint (GtkWidget *widget,
GtkSnapshot *snapshot)
@@ -6051,8 +6066,7 @@ gtk_text_view_paint (GtkWidget *widget,
g_assert_not_reached ();
}
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
push_snapshot_scroll_offset (text_view, snapshot);
gtk_text_layout_snapshot (priv->layout,
widget,
@@ -6066,7 +6080,7 @@ gtk_text_view_paint (GtkWidget *widget,
priv->selection_style_changed,
priv->cursor_alpha);
gtk_snapshot_restore (snapshot);
gtk_snapshot_pop (snapshot);
priv->selection_style_changed = FALSE;
}
@@ -6096,30 +6110,32 @@ draw_text (GtkWidget *widget,
SCREEN_WIDTH (widget),
SCREEN_HEIGHT (widget)));
push_snapshot_scroll_offset (text_view, snapshot);
style = gtk_css_node_get_style (text_view->priv->text_window->css_node);
gtk_css_boxes_init_border_box (&boxes, style,
-priv->xoffset, -priv->yoffset - priv->top_margin,
0, 0 - priv->top_margin,
MAX (SCREEN_WIDTH (text_view), priv->width),
MAX (SCREEN_HEIGHT (text_view), priv->height));
gtk_css_style_snapshot_background (&boxes, snapshot);
gtk_css_style_snapshot_border (&boxes, snapshot);
gtk_snapshot_pop (snapshot);
if (GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer != NULL)
{
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
push_snapshot_scroll_offset (text_view, snapshot);
GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer (text_view, GTK_TEXT_VIEW_LAYER_BELOW_TEXT, snapshot);
gtk_snapshot_restore (snapshot);
gtk_snapshot_pop (snapshot);
}
gtk_text_view_paint (widget, snapshot);
if (GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer != NULL)
{
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
push_snapshot_scroll_offset (text_view, snapshot);
GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer (text_view, GTK_TEXT_VIEW_LAYER_ABOVE_TEXT, snapshot);
gtk_snapshot_restore (snapshot);
gtk_snapshot_pop (snapshot);
}
gtk_snapshot_pop (snapshot);
@@ -6129,12 +6145,33 @@ draw_text (GtkWidget *widget,
}
static inline void
snapshot_text_view_child (GtkWidget *widget,
snapshot_text_view_child (GtkTextView *text_view,
GtkTextViewChild *child,
GtkSnapshot *snapshot)
GtkSnapshot *snapshot,
gboolean use_xoffset,
gboolean use_yoffset)
{
if (child != NULL)
gtk_widget_snapshot_child (widget, GTK_WIDGET (child), snapshot);
{
GtkTextViewPrivate *priv = text_view->priv;
gtk_snapshot_push_scroll_offset (snapshot,
gtk_widget_get_surface (GTK_WIDGET (text_view)),
use_xoffset ? - priv->xoffset_unrounded : 0,
use_yoffset ? - priv->yoffset_unrounded : 0);
/* Reverse the offset we applied to the children during allocation, since
* we are handling the offset via gtk_snapshot_push_scroll_offset()
*/
gtk_snapshot_translate (snapshot,
&GRAPHENE_POINT_INIT (
use_xoffset ? priv->xoffset : 0,
use_yoffset ? priv->yoffset : 0
));
gtk_widget_snapshot_child (GTK_WIDGET (text_view), GTK_WIDGET (child), snapshot);
gtk_snapshot_pop (snapshot);
}
}
static void
@@ -6149,17 +6186,19 @@ gtk_text_view_snapshot (GtkWidget *widget,
draw_text (widget, snapshot);
snapshot_text_view_child (widget, priv->left_child, snapshot);
snapshot_text_view_child (widget, priv->right_child, snapshot);
snapshot_text_view_child (widget, priv->top_child, snapshot);
snapshot_text_view_child (widget, priv->bottom_child, snapshot);
snapshot_text_view_child (widget, priv->center_child, snapshot);
snapshot_text_view_child (text_view, priv->left_child, snapshot, FALSE, TRUE);
snapshot_text_view_child (text_view, priv->right_child, snapshot, FALSE, TRUE);
snapshot_text_view_child (text_view, priv->top_child, snapshot, TRUE, FALSE);
snapshot_text_view_child (text_view, priv->bottom_child, snapshot, TRUE, FALSE);
snapshot_text_view_child (text_view, priv->center_child, snapshot, FALSE, FALSE);
push_snapshot_scroll_offset (text_view, snapshot);
for (iter = priv->anchored_children.head; iter; iter = iter->next)
{
const AnchoredChild *vc = iter->data;
gtk_widget_snapshot_child (widget, vc->widget, snapshot);
}
gtk_snapshot_pop (snapshot);
}
/**
@@ -8668,11 +8707,13 @@ gtk_text_view_value_changed (GtkAdjustment *adjustment,
{
dx = priv->xoffset - (int)gtk_adjustment_get_value (adjustment);
priv->xoffset = (int)gtk_adjustment_get_value (adjustment) - priv->left_padding;
priv->xoffset_unrounded = gtk_adjustment_get_value (adjustment) - priv->left_padding;
}
else if (adjustment == priv->vadjustment)
{
dy = priv->yoffset - (int)gtk_adjustment_get_value (adjustment) + priv->top_margin ;
priv->yoffset -= dy;
priv->yoffset_unrounded = gtk_adjustment_get_value (adjustment) - priv->top_margin;
if (priv->layout)
{

View File

@@ -31,6 +31,7 @@
#include "gtkprivate.h"
#include "gtkscrollable.h"
#include "gtkscrollinfoprivate.h"
#include "gtksnapshotprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkbuildable.h"
@@ -197,6 +198,25 @@ gtk_viewport_measure (GtkWidget *widget,
NULL, NULL);
}
static void
gtk_viewport_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkViewport *viewport = GTK_VIEWPORT (widget);
double offset_x = - gtk_adjustment_get_value (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL]);
double offset_y = - gtk_adjustment_get_value (viewport->adjustment[GTK_ORIENTATION_VERTICAL]);
/* add a translation to the child nodes in a way that will look good
* when animating */
gtk_snapshot_push_scroll_offset (snapshot,
gtk_widget_get_surface (widget),
offset_x, offset_y);
/* We need to undo the (less good looking) offset we add to children */
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- (int)offset_x, - (int)offset_y));
GTK_WIDGET_CLASS (gtk_viewport_parent_class)->snapshot (widget, snapshot);
gtk_snapshot_pop (snapshot);
}
static void
gtk_viewport_compute_expand (GtkWidget *widget,
gboolean *hexpand,
@@ -300,6 +320,7 @@ gtk_viewport_class_init (GtkViewportClass *class)
widget_class->size_allocate = gtk_viewport_size_allocate;
widget_class->measure = gtk_viewport_measure;
widget_class->snapshot = gtk_viewport_snapshot;
widget_class->root = gtk_viewport_root;
widget_class->unroot = gtk_viewport_unroot;
widget_class->compute_expand = gtk_viewport_compute_expand;