import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import { Box, EventBox } from 'resource:///com/github/Aylur/ags/widget.js'; import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { HasNotifs } from './base.js'; const { Gtk } = imports.gi; const MAX_OFFSET = 200; const OFFSCREEN = 300; const ANIM_DURATION = 500; const SLIDE_MIN_THRESHOLD = 10; const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;'; const SQUEEZED = 'margin-bottom: -70px; margin-top: -70px;'; const MAX_LEFT = ` margin-left: -${Number(MAX_OFFSET + OFFSCREEN)}px; margin-right: ${Number(MAX_OFFSET + OFFSCREEN)}px; `; const MAX_RIGHT = ` margin-left: ${Number(MAX_OFFSET + OFFSCREEN)}px; margin-right: -${Number(MAX_OFFSET + OFFSCREEN)}px; `; const slideLeft = `${TRANSITION} ${MAX_LEFT} margin-top: 0px; margin-bottom: 0px; opacity: 0;`; const squeezeLeft = `${TRANSITION} ${MAX_LEFT} ${SQUEEZED} opacity: 0;`; const slideRight = `${TRANSITION} ${MAX_RIGHT} margin-top: 0px; margin-bottom: 0px; opacity: 0;`; const squeezeRight = `${TRANSITION} ${MAX_RIGHT} ${SQUEEZED} opacity: 0;`; const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`; export default ({ id, slideIn = 'Left', command = () => {/**/}, ...props }) => { const widget = EventBox({ ...props, cursor: 'grab', on_hover: (self) => { if (!self.attribute.hovered) { self.attribute.hovered = true; } }, on_hover_lost: (self) => { if (self.attribute.hovered) { self.attribute.hovered = false; } }, attribute: { dragging: false, hovered: false, ready: false, id, /** @param {'Left'|'Right'} side */ slideAway: (side) => { // Slide away // @ts-expect-error widget.child.setCss(side === 'Left' ? slideLeft : slideRight); // Make it uninteractable widget.sensitive = false; timeout(ANIM_DURATION - 100, () => { // Reduce height after sliding away // @ts-expect-error widget.child?.setCss(side === 'Left' ? squeezeLeft : squeezeRight); timeout(ANIM_DURATION, () => { // Kill notif and update HasNotifs after anim is done command(); HasNotifs.value = Notifications .notifications.length > 0; // @ts-expect-error widget.get_parent()?.remove; }); }); }, }, }); const gesture = Gtk.GestureDrag.new(widget); widget.add(Box({ css: squeezeLeft, setup: (self) => { self // When dragging .hook(gesture, () => { let offset = gesture.get_offset()[1]; if (offset === 0) { return; } // Slide right if (offset > 0) { self.setCss(` margin-top: 0px; margin-bottom: 0px; opacity: 1; transition: none; margin-left: ${offset}px; margin-right: -${offset}px; `); } // Slide left else { offset = Math.abs(offset); self.setCss(` margin-top: 0px; margin-bottom: 0px; opacity: 1; transition: none; margin-right: ${offset}px; margin-left: -${offset}px; `); } // Put a threshold on if a click is actually dragging widget.attribute.dragging = Math.abs(offset) > SLIDE_MIN_THRESHOLD; widget.cursor = 'grabbing'; }, 'drag-update') // On drag end .hook(gesture, () => { // Make it slide in on init if (!widget.attribute.ready) { // Reverse of slideAway, so it started at squeeze, then we go to slide self.setCss(slideIn === 'Left' ? slideLeft : slideRight); timeout(ANIM_DURATION, () => { // Then we go to center self.setCss(defaultStyle); timeout(ANIM_DURATION, () => { widget.attribute.ready = true; }); }); return; } const offset = gesture.get_offset()[1]; // If crosses threshold after letting go, slide away if (Math.abs(offset) > MAX_OFFSET) { if (offset > 0) { widget.attribute.slideAway('Right'); } else { widget.attribute.slideAway('Left'); } } else { self.setCss(defaultStyle); widget.cursor = 'grab'; widget.attribute.dragging = false; } }, 'drag-end'); }, })); return widget; };