From 280429bac862b4fdbc3c094c0c034ce58642864e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Fri, 10 Apr 2020 14:54:11 +0200 Subject: [PATCH] clutter: Add private API to support resource scale affecting layout For ClutterText, the resource scale the text is drawn with affects the size of the allocation: ClutterText will choose a font scale based on the resource scale, and that font scale can lead to a slight difference in size compared to the unscaled font. We currently handle that by queuing a relayout inside the "resource-scale-changed" signal handler. This solution is a bit problematic though since it will take one more allocation cycle until the allocation is actually updated after a scale-change, so the actor is painted using the wrong allocation for one frame. Also the current solution can lead to relayout loops in a few cases, for example if a ClutterText is located near the edge on a 1x scaled monitor and is moved to intersect a 2x scaled monitor: Now the resource scale will change to 2 and a new allocation box is calculated; if this allocation box is slightly smaller than the old one because of the new font scale, the allocation won't intersect the 2x scaled monitor again and the resource scale switches back to 1. Now the allocation gets larger again and intersects the 2x scaled monitor again. This commit introduces a way to properly support those actors: In case an actors resource scale might affect its allocation, it should call the private function clutter_actor_queue_immediate_relayout(). This will make sure the actor gets a relayout before the upcoming paint happens afte every resource scale change. Also potential relayout loops can be handled by the actors themselves using a "phase" argument that's passed to implementations of the calculate_resource_scale() vfunc. The new API is private because resource scales are not meant to be used in a way where the scale affects the allocation. With ClutterText and the current behavior of Pango, that can't be avoid though, so we need it anyway. https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1276 --- clutter/clutter/clutter-actor-private.h | 5 ++- clutter/clutter/clutter-actor.c | 57 +++++++++++++++++++------ clutter/clutter/clutter-actor.h | 2 + clutter/clutter/clutter-stage-private.h | 2 + clutter/clutter/clutter-stage.c | 39 +++++++++++++++-- 5 files changed, 87 insertions(+), 18 deletions(-) diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h index d1adcd38f..16ab012b6 100644 --- a/clutter/clutter/clutter-actor-private.h +++ b/clutter/clutter/clutter-actor-private.h @@ -288,7 +288,10 @@ float clutter_actor_get_real_resource_scale ClutterPaintNode * clutter_actor_create_texture_paint_node (ClutterActor *self, CoglTexture *texture); -void clutter_actor_update_stage_views (ClutterActor *self); +void clutter_actor_update_stage_views (ClutterActor *self, + int phase); + +void clutter_actor_queue_immediate_relayout (ClutterActor *self); G_END_DECLS diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index e6a963139..86ffe2f1f 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -5903,6 +5903,25 @@ clutter_actor_real_has_overlaps (ClutterActor *self) return TRUE; } +static float +clutter_actor_real_calculate_resource_scale (ClutterActor *self, + int phase) +{ + ClutterActorPrivate *priv = self->priv; + GList *l; + float new_resource_scale = -1.f; + + for (l = priv->stage_views; l; l = l->next) + { + ClutterStageView *view = l->data; + + new_resource_scale = MAX (clutter_stage_view_get_scale (view), + new_resource_scale); + } + + return new_resource_scale; +} + static void clutter_actor_real_destroy (ClutterActor *actor) { @@ -5988,6 +6007,7 @@ clutter_actor_class_init (ClutterActorClass *klass) klass->get_accessible = clutter_actor_real_get_accessible; klass->get_paint_volume = clutter_actor_real_get_paint_volume; klass->has_overlaps = clutter_actor_real_has_overlaps; + klass->calculate_resource_scale = clutter_actor_real_calculate_resource_scale; klass->paint = clutter_actor_real_paint; klass->destroy = clutter_actor_real_destroy; @@ -16131,20 +16151,14 @@ out: } static void -update_resource_scale (ClutterActor *self) +update_resource_scale (ClutterActor *self, + int phase) { ClutterActorPrivate *priv = self->priv; - GList *l; - float new_resource_scale = -1.f; - float old_resource_scale; + float new_resource_scale, old_resource_scale; - for (l = priv->stage_views; l; l = l->next) - { - ClutterStageView *view = l->data; - - new_resource_scale = MAX (clutter_stage_view_get_scale (view), - new_resource_scale); - } + new_resource_scale = + CLUTTER_ACTOR_GET_CLASS (self)->calculate_resource_scale (self, phase); if (priv->resource_scale == new_resource_scale) return; @@ -16168,7 +16182,8 @@ update_resource_scale (ClutterActor *self) } void -clutter_actor_update_stage_views (ClutterActor *self) +clutter_actor_update_stage_views (ClutterActor *self, + gboolean use_max_scale) { ClutterActorPrivate *priv = self->priv; ClutterActor *child; @@ -16181,12 +16196,12 @@ clutter_actor_update_stage_views (ClutterActor *self) return; update_stage_views (self); - update_resource_scale (self); + update_resource_scale (self, use_max_scale); priv->needs_update_stage_views = FALSE; for (child = priv->first_child; child; child = child->priv->next_sibling) - clutter_actor_update_stage_views (child); + clutter_actor_update_stage_views (child, use_max_scale); } /** @@ -19710,3 +19725,17 @@ clutter_actor_has_accessible (ClutterActor *actor) return TRUE; } + +void +clutter_actor_queue_immediate_relayout (ClutterActor *self) +{ + ClutterStage *stage; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + + clutter_actor_queue_relayout (self); + + stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); + if (stage) + clutter_stage_set_actor_needs_immediate_relayout (stage); +} diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h index 9f795f6d3..67d327b5b 100644 --- a/clutter/clutter/clutter-actor.h +++ b/clutter/clutter/clutter-actor.h @@ -297,6 +297,8 @@ struct _ClutterActorClass ClutterTouchEvent *event); gboolean (* has_accessible) (ClutterActor *self); void (* resource_scale_changed) (ClutterActor *self); + float (* calculate_resource_scale) (ClutterActor *self, + int phase); /*< private >*/ /* padding for future expansion */ diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index 5d785b644..2ce0cd05b 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -136,6 +136,8 @@ void clutter_stage_queue_actor_relayout (ClutterStage *stage, GList * clutter_stage_get_views_for_rect (ClutterStage *stage, const graphene_rect_t *rect); +void clutter_stage_set_actor_needs_immediate_relayout (ClutterStage *stage); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 99d218441..8d3fc8b80 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -146,6 +146,7 @@ struct _ClutterStagePrivate guint min_size_changed : 1; guint motion_events_enabled : 1; guint stage_was_relayout : 1; + guint actor_needs_immediate_relayout : 1; }; enum @@ -1354,8 +1355,34 @@ static void update_actor_stage_views (ClutterStage *stage) { ClutterActor *actor = CLUTTER_ACTOR (stage); + ClutterStagePrivate *priv = stage->priv; + int phase; - clutter_actor_update_stage_views (actor); + COGL_TRACE_BEGIN_SCOPED (ClutterStageUpdateActorStageViews, + "Actor stage-views"); + + /* If an actor needs an immediate relayout because its resource scale + * changed, we give it another chance to allocate correctly before + * the paint. + * + * We're doing the whole thing twice and pass the phase to + * clutter_actor_update_stage_views() to allow actors to detect loops: + * If the resource scale changes again after the relayout, the new + * allocation of an actor probably moved the actor onto another stage + * view, so if an actor sees phase == 1, it can choose a "final" scale. + */ + for (phase = 0; phase < 2; phase++) + { + clutter_actor_update_stage_views (actor, phase); + + if (!priv->actor_needs_immediate_relayout) + break; + + priv->actor_needs_immediate_relayout = FALSE; + _clutter_stage_maybe_relayout (actor); + } + + g_warn_if_fail (!priv->actor_needs_immediate_relayout); } /** @@ -1405,9 +1432,7 @@ _clutter_stage_do_update (ClutterStage *stage) if (stage_was_relayout) pointers = _clutter_stage_check_updated_pointers (stage); - COGL_TRACE_BEGIN (ClutterStageUpdateActorStageViews, "Actor stage-views"); update_actor_stage_views (stage); - COGL_TRACE_END (ClutterStageUpdateActorStageViews); COGL_TRACE_BEGIN (ClutterStagePaint, "Paint"); @@ -4115,3 +4140,11 @@ clutter_stage_get_views_for_rect (ClutterStage *stage, return views_for_rect; } + +void +clutter_stage_set_actor_needs_immediate_relayout (ClutterStage *stage) +{ + ClutterStagePrivate *priv = stage->priv; + + priv->actor_needs_immediate_relayout = TRUE; +}