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
This commit is contained in:
Jonas Dreßler 2020-04-10 14:54:11 +02:00 committed by Jonas Ådahl
parent 98df2dbd05
commit 280429bac8
5 changed files with 87 additions and 18 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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 */

View File

@ -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__ */

View File

@ -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;
}