import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { Box, EventBox, Overlay } from 'resource:///com/github/Aylur/ags/widget.js'; const { Gtk } = imports.gi; const MAX_OFFSET = 200; const OFFSCREEN = 500; const ANIM_DURATION = 500; const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease, opacity ${ANIM_DURATION}ms ease;`; /** * @typedef {import('types/widgets/overlay').OverlayProps} OverlayProps * @typedef {import('types/widgets/overlay').default} Overlay * * @param {OverlayProps & { * setup?: function(Overlay):void * }} o */ export default ({ attribute = {}, setup = () => {/**/}, ...props }) => { const widget = EventBox(); const gesture = Gtk.GestureDrag.new(widget); // Have empty PlayerBox to define the size of the widget const emptyPlayer = Box({ class_name: 'player', attribute: { empty: true }, }); const content = Overlay({ ...props, attribute: { ...attribute, dragging: false, list: () => content.get_children() // @ts-expect-error .filter((ch) => !ch.attribute?.empty), /** @param {Overlay} playerW */ includesWidget: (playerW) => { return Array.from(content.attribute.list()) .find((w) => w === playerW); }, showTopOnly: () => Array.from(content.attribute.list()) .forEach((over) => { over.visible = over === content.attribute.list().at(-1); }), /** @param {import('types/widgets/centerbox').default} player */ moveToTop: (player) => { player.visible = true; content.reorder_overlay(player, -1); timeout(ANIM_DURATION, () => { content.attribute.showTopOnly(); }); }, }, child: emptyPlayer, setup: (self) => { setup(self); self .hook(gesture, /** * @param {Overlay} overlay * @param {number} realGesture */ (overlay, realGesture) => { if (realGesture) { Array.from(overlay.attribute.list()) .forEach((over) => { over.visible = true; }); } else { overlay.attribute.showTopOnly(); } // Don't allow gesture when only one player if (overlay.attribute.list().length <= 1) { return; } overlay.attribute.dragging = true; let offset = gesture.get_offset()[1]; const playerBox = overlay.attribute.list().at(-1); if (!offset) { return; } // Slide right if (offset >= 0) { playerBox.setCss(` margin-left: ${offset}px; margin-right: -${offset}px; ${playerBox.attribute.bgStyle} `); } // Slide left else { offset = Math.abs(offset); playerBox.setCss(` margin-left: -${offset}px; margin-right: ${offset}px; ${playerBox.attribute.bgStyle} `); } }, 'drag-update') .hook(gesture, /** @param {Overlay} overlay */ (overlay) => { // Don't allow gesture when only one player if (overlay.attribute.list().length <= 1) { return; } overlay.attribute.dragging = false; const offset = gesture.get_offset()[1]; const playerBox = overlay.attribute.list().at(-1); // If crosses threshold after letting go, slide away if (offset && Math.abs(offset) > MAX_OFFSET) { // Disable inputs during animation widget.sensitive = false; // Slide away right if (offset >= 0) { playerBox.setCss(` ${TRANSITION} margin-left: ${OFFSCREEN}px; margin-right: -${OFFSCREEN}px; opacity: 0.7; ${playerBox.attribute.bgStyle} `); } // Slide away left else { playerBox.setCss(` ${TRANSITION} margin-left: -${OFFSCREEN}px; margin-right: ${OFFSCREEN}px; opacity: 0.7; ${playerBox.attribute.bgStyle} `); } timeout(ANIM_DURATION, () => { // Put the player in the back after anim overlay.reorder_overlay(playerBox, 0); // Recenter player playerBox.setCss(playerBox.attribute.bgStyle); widget.sensitive = true; overlay.attribute.showTopOnly(); }); } else { // Recenter with transition for animation playerBox.setCss(`${TRANSITION} ${playerBox.attribute.bgStyle}`); } }, 'drag-end'); }, }); widget.add(content); return widget; };