diff options
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js')
| -rw-r--r-- | raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js new file mode 100644 index 0000000..cc4af30 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/blur-my-shell@aunetx/components/popup/message_stacks.js @@ -0,0 +1,571 @@ +import GLib from 'gi://GLib'; + +const STACKED_PSEUDO_CLASSES = ['second-in-stack', 'lower-in-stack']; +const MESSAGE_CONTAINER_STYLE_CLASSES = ['message-list', 'message-view', 'message-notification-group']; +const INTERNAL_STYLE_CLASSES = ['bms-popup-blurred-widget', 'bms-popup-backgroundgroup']; +const INTERNAL_NAMES = ['bms-popup-blurred-widget', 'bms-popup-backgroundgroup']; + +export const PopupBlurMessageStacks = class PopupBlurMessageStacks { + constructor(connections) { + this.connections = connections; + this.containers = new Set(); + this.groups = new Set(); + this.messages = new Set(); + this.queued_actors = new WeakSet(); + this.watched_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + this.bms_clipped = new WeakSet(); + this.group_connections = new Map(); + this.update_ids = new Map(); + this.original_opacity = new WeakMap(); + this.original_message_opacity = new WeakMap(); + this.enabled = false; + } + + enable() { + this.enabled = true; + } + + track_container(container) { + if (!container || this.containers.has(container)) + return; + if (!this.watch_actor(container)) + return; + + this.containers.add(container); + this.scan(container); + + this.connect( + container, + 'child-added', + (_, child) => { + this.queue_scan(child); + this.queue_update_all(); + } + ); + + this.connect( + container, + 'child-removed', + () => this.queue_update_all() + ); + } + + queue_scan(actor) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor) || this.queued_actors.has(actor)) + return; + + this.queued_actors.add(actor); + this.scan(actor); + this.queued_actors.delete(actor); + } + + scan(actor, seen = new WeakSet()) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor) || seen.has(actor)) + return; + + seen.add(actor); + + if (this.has_any_style_class(actor, MESSAGE_CONTAINER_STYLE_CLASSES)) + this.track_container(actor); + + if (this.has_style_class(actor, 'message-notification-group')) + this.track_group(actor); + + if (this.has_style_class(actor, 'message')) + this.track_message(actor); + + this.get_children(actor).forEach(child => this.scan(child, seen)); + + const child = this.get_child(actor); + if (child) + this.scan(child, seen); + } + + track_message(message) { + if (this.is_internal_actor(message) || !this.watch_actor(message) || this.messages.has(message)) + return; + + this.messages.add(message); + this.update_message(message); + + this.connect( + message, + 'style-changed', + () => this.queue_update_message(message) + ); + + this.connect( + message, + 'notify::pseudo-class', + () => this.queue_update_message(message) + ); + + this.connect( + message, + 'notify::allocation', + () => this.queue_update_message(message) + ); + + this.connect( + message, + 'destroy', + () => { + this.cancel_update(message); + this.remove_stack_mask(message); + this.messages.delete(message); + } + ); + } + + track_group(group) { + if (!this.watch_actor(group) || this.groups.has(group)) + return; + + this.groups.add(group); + this.update_group_header(group); + + this.connect( + group, + 'notify::expanded', + () => this.update_all() + ); + + this.connect( + group, + 'notify::allocation', + () => this.update_group_header(group) + ); + + if (group.layout_manager) { + const lm = group.layout_manager; + const id = lm.connect('notify::expansion', () => { + this.update_group_header(group); + this.update_group_messages(group); + }); + this.group_connections.set(group, { lm, id }); + } + } + + untrack_group(group) { + if (this.group_connections.has(group)) { + const { lm, id } = this.group_connections.get(group); + lm.disconnect(id); + this.group_connections.delete(group); + } + this.groups.delete(group); + } + + update_all() { + this.groups.forEach(group => { + this.update_group_header(group); + this.update_group_messages(group); + }); + this.messages.forEach(message => { + if (!this.get_message_group(message)) + this.update_message(message); + }); + } + + update_group_header(group) { + if (!this.watch_actor(group)) + return; + + const header = group._headerBox; + if (!header) + return; + + const expansion = group.layout_manager?.expansion ?? (group.expanded ? 1 : 0); + const target_opacity = this.enabled ? Math.round(expansion * 255) : 255; + + header.opacity = target_opacity; + } + + get_group_messages(group) { + const list = []; + const find_messages = actor => { + if (this.has_style_class(actor, 'message')) { + list.push(actor); + return; + } + this.get_children(actor).forEach(find_messages); + }; + find_messages(group); + return list; + } + + update_group_messages(group) { + if (!this.enabled || !this.watch_actor(group)) + return; + + const expansion = group.layout_manager?.expansion ?? (group.expanded ? 1.0 : 0.0); + const messages = this.get_group_messages(group); + + messages.forEach((message, index) => { + this.update_message_with_expansion(message, index, expansion, group); + }); + } + + queue_update_all() { + this.messages.forEach(message => this.queue_update_message(message)); + } + + queue_update_message(message) { + if (this.update_ids.has(message)) + return; + + const id = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + this.update_ids.delete(message); + + if (this.enabled && this.messages.has(message)) + this.update_message(message); + + return GLib.SOURCE_REMOVE; + }); + + this.update_ids.set(message, id); + } + + update_message(message) { + if (!this.enabled) { + this.remove_stack_mask(message); + return; + } + + const group = this.get_message_group(message); + if (group) { + const expansion = group.layout_manager?.expansion ?? (group.expanded ? 1.0 : 0.0); + const index = this.get_message_index_in_group(message); + this.update_message_with_expansion(message, index >= 0 ? index : 0, expansion, group); + } else { + const is_expanded = this.is_in_expanded_group(message); + if (this.is_stacked(message) && !is_expanded) { + this.apply_stack_mask(message); + this.hide_message_content(message); + this.restore_message_opacity(message); + } else { + this.remove_stack_mask(message); + } + } + } + + update_message_with_expansion(message, index, expansion, group) { + if (!this.enabled || !this.watch_actor(message)) + return; + + const is_stacked = this.is_stacked(message) || index > 0; + + if (!is_stacked) { + this.remove_stack_mask(message); + return; + } + + if (expansion >= 0.99) { + this.remove_stack_mask(message); + return; + } + + if (expansion <= 0.01) { + this.apply_stack_mask(message); + this.hide_message_content(message); + this.restore_message_opacity(message); + return; + } + + // 0.01 < expansion < 0.99: Animating unravel / collapse + const height = message.height || message.get_height() || 0; + const width = message.width || message.get_width() || 0; + + if (height <= 0 || width <= 0) + return; + + const base_edge = (index === 1) ? 10 : (index === 2 ? 7 : 0); + const visible_edge = Math.round(base_edge + (height - base_edge) * expansion); + const clip_y = Math.max(0, height - visible_edge); + + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + + // Staggered opacity progress for cards so they smoothly fade in as they unravel + const stagger_start = Math.min(0.8, index * 0.04); + const progress = Math.min(1.0, Math.max(0.0, (expansion - stagger_start) / (1.0 - stagger_start))); + const target_alpha = Math.round(255 * progress); + + this.set_child_opacity(message, target_alpha); + + if (index >= 3) { + this.set_message_opacity(message, target_alpha); + } else { + this.restore_message_opacity(message); + } + } + + apply_stack_mask(message) { + if (!this.watch_actor(message)) + return; + + const height = message.height || message.get_height() || 0; + const width = message.width || message.get_width() || 0; + + if (height <= 0 || width <= 0) + return; + + const index = this.get_message_index_in_group(message); + + if (index === 1) { + // 2nd card: tight visible strip of 10px (matches exact 10px bottom offset below card 1) + const visible_edge = 10; + const clip_y = Math.max(0, height - visible_edge); + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + } else if (index === 2) { + // 3rd card: tight visible strip of 7px (matches exact 7px bottom offset below card 2) + const visible_edge = 7; + const clip_y = Math.max(0, height - visible_edge); + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + } else if (index >= 3) { + // 4th+ cards: hidden when collapsed so they don't stack behind card 3 + message.set_clip(0, height, width, 0); + this.bms_clipped.add(message); + } else { + // Fallback by pseudo class + const is_second = this.has_pseudo_class(message, 'second-in-stack'); + const visible_edge = is_second ? 10 : 6; + const clip_y = Math.max(0, height - visible_edge); + message.set_clip(0, clip_y, width, visible_edge); + this.bms_clipped.add(message); + } + } + + remove_stack_mask(message) { + if (!message || this.destroyed_actors.has(message)) + return; + + if (this.bms_clipped.has(message)) { + message.remove_clip(); + this.bms_clipped.delete(message); + } + + this.restore_message_content(message); + this.restore_message_opacity(message); + } + + set_child_opacity(message, opacity) { + const child = this.get_child(message); + if (!child) + return; + + if (!this.original_opacity.has(child)) + this.original_opacity.set(child, child.opacity ?? 255); + + child.opacity = opacity; + } + + hide_message_content(message) { + this.set_child_opacity(message, 0); + } + + restore_message_content(message) { + const child = this.get_child(message); + if (!child) + return; + + if (this.original_opacity.has(child)) { + child.opacity = this.original_opacity.get(child); + this.original_opacity.delete(child); + } + } + + set_message_opacity(message, opacity) { + if (!this.watch_actor(message)) + return; + + if (!this.original_message_opacity.has(message)) + this.original_message_opacity.set(message, message.opacity ?? 255); + + message.opacity = opacity; + } + + restore_message_opacity(message) { + if (!message || this.destroyed_actors.has(message)) + return; + + if (this.original_message_opacity.has(message)) { + message.opacity = this.original_message_opacity.get(message); + this.original_message_opacity.delete(message); + } + } + + get_message_index_in_group(message) { + const group = this.get_message_group(message); + if (!group) + return -1; + + const list = []; + const find_messages = actor => { + if (this.has_style_class(actor, 'message')) { + list.push(actor); + return; + } + actor.get_children().forEach(find_messages); + }; + find_messages(group); + return list.indexOf(message); + } + + has_pseudo_class(actor, pseudo_class) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + + if (actor.has_style_pseudo_class) + return actor.has_style_pseudo_class(pseudo_class); + + if (actor.get_style_pseudo_class) + return (actor.get_style_pseudo_class() ?? '').split(/\s+/).includes(pseudo_class); + + return false; + } + + get_message_group(message) { + let actor = message ? message.get_parent() : null; + + while (actor && !this.destroyed_actors.has(actor)) { + if (this.has_style_class(actor, 'message-notification-group')) + return actor; + actor = actor.get_parent(); + } + return null; + } + + is_in_expanded_group(message) { + let actor = message ? message.get_parent() : null; + + while (actor && !this.destroyed_actors.has(actor)) { + if ( + this.has_style_class(actor, 'message-notification-group') + && actor.expanded + ) + return true; + actor = actor.get_parent(); + } + + return false; + } + + is_stacked(message) { + if (!message || this.destroyed_actors.has(message)) + return false; + return STACKED_PSEUDO_CLASSES.some(pseudo_class => { + if (message.has_style_pseudo_class) + return message.has_style_pseudo_class(pseudo_class); + + if (message.get_style_pseudo_class) + return (message.get_style_pseudo_class() ?? '').split(/\s+/).includes(pseudo_class); + + return false; + }); + } + + has_any_style_class(actor, style_classes) { + return style_classes.some(style_class => this.has_style_class(actor, style_class)); + } + + is_internal_actor(actor) { + if (!actor) + return false; + + if (this.has_any_style_class(actor, INTERNAL_STYLE_CLASSES)) + return true; + + if (this.destroyed_actors.has(actor)) + return false; + + return INTERNAL_NAMES.includes(actor.name ?? actor.get_name()); + } + + has_style_class(actor, style_class) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + + if (actor.has_style_class_name) + return actor.has_style_class_name(style_class); + + if (actor.get_style_class_name) + return (actor.get_style_class_name() ?? '').split(/\s+/).includes(style_class); + + return false; + } + + watch_actor(actor) { + if (!actor || this.destroyed_actors.has(actor)) + return false; + if (this.watched_actors.has(actor)) + return true; + + this.connections.connect(actor, 'destroy', () => { + this.destroyed_actors.add(actor); + this.containers.delete(actor); + if (this.groups.has(actor)) { + this.untrack_group(actor); + } + this.messages.delete(actor); + this.cancel_update(actor); + }); + + this.watched_actors.add(actor); + return true; + } + + get_children(actor) { + if (this.is_internal_actor(actor) || !this.watch_actor(actor)) + return []; + + return actor.get_children(); + } + + get_child(actor) { + if (!this.watch_actor(actor)) + return null; + + return (actor.get_child ? actor.get_child() : actor.child) || null; + } + + cancel_update(message) { + const id = this.update_ids.get(message); + if (!id) + return; + + GLib.source_remove(id); + this.update_ids.delete(message); + } + + connect(actor, signal, handler) { + this.connections.connect(actor, signal, handler); + } + + disable() { + this.update_ids.forEach(id => GLib.source_remove(id)); + this.update_ids.clear(); + this.messages.forEach(message => this.remove_stack_mask(message)); + this.groups.forEach(group => { + if (group._headerBox) { + group._headerBox.opacity = 255; + } + this.untrack_group(group); + }); + this.group_connections.forEach(({ lm, id }) => { + lm.disconnect(id); + }); + this.group_connections.clear(); + this.messages.clear(); + this.groups.clear(); + this.containers.clear(); + this.watched_actors = new WeakSet(); + this.destroyed_actors = new WeakSet(); + this.bms_clipped = new WeakSet(); + this.original_opacity = new WeakMap(); + this.original_message_opacity = new WeakMap(); + this.enabled = false; + } +}; |