messageList: Highlight expanded group

The new design requires that other messages and groups are faded when the user
has a group expanded. This introduces a new GLSL shader to provide the
desired effect. The new shader is used for the already existing scroll
fade and the previous one is removed. The two fades need to work together to
ensure that resulting fade looks good.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3012>
This commit is contained in:
Julian Sparber 2023-12-04 12:00:03 +01:00 committed by Marge Bot
parent a795dfb2ff
commit b4113b1ee2
3 changed files with 204 additions and 2 deletions

View File

@ -30,6 +30,8 @@
&:ltr {margin-right: $base_margin * 3; }
&:rtl {margin-left: $base_margin * 3;}
-st-vfade-offset: 68px;
.message {
margin-bottom: $base_padding * 2 !important;
}

View File

@ -819,7 +819,6 @@ class CalendarMessageList extends St.Widget {
this._messageView = new MessageList.MessageView();
this._scrollView = new St.ScrollView({
style_class: 'vfade',
overlay_scrollbars: true,
x_expand: true, y_expand: true,
child: this._messageView,

View File

@ -1,11 +1,13 @@
import Atk from 'gi://Atk';
import Clutter from 'gi://Clutter';
import Cogl from 'gi://Cogl';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Graphene from 'gi://Graphene';
import Meta from 'gi://Meta';
import Pango from 'gi://Pango';
import Shell from 'gi://Shell';
import St from 'gi://St';
import * as Main from './main.js';
@ -1486,6 +1488,7 @@ export const MessageView = GObject.registerClass({
super({
style_class: 'message-view',
layout_manager: new MessageViewLayout(overlay),
effect: new FadeEffect({name: 'highlight'}),
x_expand: true,
y_expand: true,
});
@ -1736,6 +1739,15 @@ export const MessageView = GObject.registerClass({
this.vadjustment.thaw_notify();
}
vfunc_style_changed() {
// This widget doesn't use the normal st scroll view fade effect because
// highlighting groups needs more control over the fade.
const fadeOffset = this.get_theme_node().get_length('-st-vfade-offset');
this.get_effect('highlight').fadeMargin = fadeOffset;
super.vfunc_style_changed();
}
_setupMpris() {
this._mediaSource.connectObject(
'player-added', (_, player) => this._addPlayer(player),
@ -1848,7 +1860,10 @@ export const MessageView = GObject.registerClass({
this.notify('expanded-group');
// Collapse the previously expanded group
await prevGroup?.collapse();
if (prevGroup) {
this._unhighlightGroup(prevGroup);
await prevGroup.collapse();
}
if (group) {
// Make sure that the overlay is the child below the expanded group
@ -1857,6 +1872,7 @@ export const MessageView = GObject.registerClass({
this._overlay.show();
this._scrollToExpandedGroup();
this._highlightGroup(group);
await group.expand();
} else {
this._overlay.hide();
@ -1867,4 +1883,189 @@ export const MessageView = GObject.registerClass({
collapse() {
this._setExpandedGroup(null).catch(logError);
}
_highlightGroup(group) {
const effect = this.get_effect('highlight');
effect.opacity = 0.0;
effect.highlightActor = group;
this.ease_property('@effects.highlight.opacity', 1.0, {
progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: MESSAGE_ANIMATION_TIME,
});
}
_unhighlightGroup() {
this.ease_property('@effects.highlight.opacity', 0.0, {
progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: MESSAGE_ANIMATION_TIME,
onStopped: () => {
const effect = this.get_effect('highlight');
effect.highlightActor = null;
},
});
}
});
const FadeEffect = GObject.registerClass({
Properties: {
'fade-margin': GObject.ParamSpec.float(
'fade-margin', null, null,
GObject.ParamFlags.READWRITE,
0, Infinity, 0),
'opacity': GObject.ParamSpec.float(
'opacity', null, null,
GObject.ParamFlags.READWRITE,
0, 1, 0),
'highlight-actor': GObject.ParamSpec.object(
'highlight-actor', null, null,
GObject.ParamFlags.READWRITE,
Clutter.Actor),
},
}, class FadeEffect extends Shell.GLSLEffect {
constructor(params) {
super(params);
this._heightLocation = this.get_uniform_location('height');
this._topFadePositionLocation = this.get_uniform_location('top_fade_position');
this._bottomFadePositionLocation = this.get_uniform_location('bottom_fade_position');
this._opacityLocation = this.get_uniform_location('opacity');
this._topEdgeFadeLocation = this.get_uniform_location('top_edge_fade');
this._bottomEdgeFadeLocation = this.get_uniform_location('bottom_edge_fade');
}
_updateEnabled() {
if (!this._vadjustment) {
this.enabled = false;
return;
}
const {upper, pageSize} = this._vadjustment;
this.enabled = (upper > pageSize && this._fadeMargin > 0.0) || this._highlightActor;
}
get highlightActor() {
return this._highlightActor;
}
set highlightActor(actor) {
if (this._highlightActor === actor)
return;
this._highlightActor = actor;
this.queue_repaint();
this._updateEnabled();
this.notify('highlight-actor');
}
get opacity() {
return this._opacity;
}
set opacity(opacity) {
if (this._opacity === opacity)
return;
this._opacity = opacity;
this.set_uniform_float(this._opacityLocation, 1, [opacity]);
this.queue_repaint();
this.notify('opacity');
}
get fadeMargin() {
return this._fadeMargin;
}
set fadeMargin(fadeMargin) {
if (this._fadeMargin === fadeMargin)
return;
this._fadeMargin = fadeMargin;
this.queue_repaint();
this.notify('fade-margin');
}
_vadjustmentChanged() {
const newAdj = this.actor.vadjustment;
if (this._vadjustment === newAdj)
return;
this._vadjustment?.disconnectObject(this);
this._vadjustment = newAdj;
this._vadjustment?.connectObject('changed', this._updateEnabled.bind(this));
this._updateEnabled();
}
vfunc_set_actor(actor) {
if (this.actor === actor)
return;
this.actor?.disconnectObject(this);
actor?.connectObject('notify::vadjustment', this._vadjustmentChanged.bind(this));
super.vfunc_set_actor(actor);
this._vadjustmentChanged();
}
vfunc_paint_target(node, paintContext) {
const {pageSize, upper, value} = this._vadjustment ?? [this.actor.height, this.actor.height, 0];
if (this._highlightActor) {
const position = this._highlightActor.apply_relative_transform_to_point(this.actor, new Graphene.Point3D());
this.set_uniform_float(this._topFadePositionLocation, 1, [position.y - value]);
this.set_uniform_float(this._bottomFadePositionLocation, 1, [(position.y + this._highlightActor.height - 1) - value]);
} else {
this.set_uniform_float(this._topFadePositionLocation, 1, [0.0]);
this.set_uniform_float(this._bottomFadePositionLocation, 1, [0.0]);
}
this.set_uniform_float(this._heightLocation, 1, [pageSize]);
this.set_uniform_float(this._topEdgeFadeLocation, 1, [Math.min(value, this._fadeMargin)]);
this.set_uniform_float(this._bottomEdgeFadeLocation, 1, [
pageSize - Math.min(upper - pageSize - value, this._fadeMargin),
]);
super.vfunc_paint_target(node, paintContext);
}
vfunc_build_pipeline() {
const dec = `uniform sampler2D tex; \n
uniform float height; \n
uniform float opacity; \n
uniform float top_edge_fade; \n
uniform float bottom_edge_fade; \n
uniform float top_fade_position; \n
uniform float bottom_fade_position; \n`;
const src = `cogl_color_out = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy)); \n
float fade_base = 300; \n
float fade_height = 400; \n
float y = height * cogl_tex_coord_in[0].y; \n
float ratio = 1.0; \n
if (y < top_fade_position && top_fade_position > 0.0) { \n
float edge1 = top_fade_position - fade_height; \n
float edge2 = top_fade_position; \n
ratio = (smoothstep (edge1, edge2 + fade_base, y) - 1.0) * opacity + 1.0; \n
} else if (y > bottom_fade_position && bottom_fade_position < height) { \n
float edge1 = bottom_fade_position + fade_height; \n
float edge2 = bottom_fade_position; \n
ratio = (smoothstep (edge1, edge2 - fade_base, y) - 1.0) * opacity + 1.0; \n
} \n
if (top_edge_fade > 0.0) \n
ratio *= smoothstep (0.0, top_edge_fade, y); \n
if (bottom_edge_fade > 0.0 && bottom_edge_fade < height) \n
ratio *= smoothstep (height, bottom_edge_fade, y); \n
cogl_color_out *= ratio; \n`;
this.add_glsl_snippet(Cogl.SnippetHook.FRAGMENT, dec, src, true);
}
});