Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/PopoutHost.qml

Raw
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window
import Quickshell
import Quickshell.Wayland

PanelWindow {
    id: root

    required property Theme theme
    required property SurfaceManager surfaces
    required property Niri niriService
    required property Time timeService
    property CalDav caldavService
    required property Resources resourceService
    required property Notifications notificationService
    required property Tray trayService
    required property Media mediaService
    required property Clipboard clipboardService
    required property Vault vaultService
    required property Launcher launcherService
    required property Audio audioService
    required property Brightness brightnessService
    required property Network networkService
    required property Bluetooth bluetoothService
    required property Power powerService
    required property Privilege privilegeService
    required property Lock lockService
    required property Noko nokoService
    required property var barWindow
    readonly property var contentSize: surfaces ? surfaces.popoutContentSize(surfaces.popoutName) : ({
            "width": 320,
            "height": 180
        })
    readonly property var popupAnchorRect: surfaces ? surfaces.popupWindowAnchorRect() : ({
            "x": 0,
            "y": 0,
            "width": 320,
            "height": 1
        })
    readonly property var cardFrame: surfaces ? surfaces.popupCardFrame(surfaces.popoutProgress) : ({
            "currentRect": {
                "x": 8,
                "y": 8,
                "width": 304,
                "height": 164
            },
            "opacity": 1
        })
    readonly property var currentRect: cardFrame.currentRect
    property bool awaitingScrollRestore: false
    readonly property string activeName: surfaces ? surfaces.popoutName : ""
    readonly property string removingName: surfaces ? surfaces.removingLoaderName : ""
    readonly property bool vertical: surfaces && surfaces.orientation === "vertical"
    // subtract the bar on whichever axis it occupies (top for horizontal, left
    // for vertical) so the popout surface covers only the free region.
    readonly property int surfaceWidth: surfaces && surfaces.screenWidth > 0 ? (root.vertical ? Math.max(1, surfaces.screenWidth - surfaces.barHeight) : surfaces.screenWidth) : contentSize.width
    readonly property int surfaceHeight: surfaces && surfaces.screenHeight > 0 ? (root.vertical ? surfaces.screenHeight : Math.max(1, surfaces.screenHeight - surfaces.barHeight)) : contentSize.height

    function panelScroll(item: var): Flickable {
        return item ? (item.scrollView || (item as Flickable)) : null;
    }

    function savePanelState(name) {
        const state = root.surfaces.panelStates[name];
        const item = root.panelScroll(activeLoader.item);
        if (!state || !item)
            return;
        const audio = activeLoader.item as AudioPanel;
        if (audio) {
            state.expandedSink = audio.expandedSink;
            state.expandedSource = audio.expandedSource;
        }
        const network = activeLoader.item as ConnectivityPanel;
        if (network)
            state.expandedSsid = network.expandedSsid;
        state.scrollY = item.contentY;
    }

    function restorePanelState() {
        const name = root.activeName;
        const item = root.panelScroll(activeLoader.item);
        const state = root.surfaces.panelStates[name];
        if (!item || !state)
            return;
        const audio = activeLoader.item as AudioPanel;
        if (audio) {
            audio.expandedSink = state.expandedSink;
            audio.expandedSource = state.expandedSource;
        }
        const network = activeLoader.item as ConnectivityPanel;
        if (network)
            network.expandedSsid = state.expandedSsid;
        item.contentY = Math.max(0, Math.min(state.scrollY, item.contentHeight - item.height));
    }

    Connections {
        target: root.resourceService
        function onAgentDispatched(): void {
            if (root.surfaces && root.surfaces.popoutName === "system")
                root.surfaces.closePopout("agent-dispatched");
        }
    }

    Connections {
        target: root.surfaces
        function onDismissing(name: string): void {
            root.savePanelState(name);
        }
    }

    Connections {
        target: root.contentItem.Window.window
        enabled: root.awaitingScrollRestore
        function onFrameSwapped() {
            root.restorePanelState();
            root.awaitingScrollRestore = false;
        }
    }

    function screenByName(name) {
        const want = String(name || "");
        const screens = Quickshell.screens;
        for (let i = 0; i < screens.length; i++)
            if (String(screens[i].name) === want)
                return screens[i];

        return root.barWindow ? root.barWindow.screen : null;
    }

    function contentComponent(name) {
        switch (String(name)) {
        case "clock":
            return clockContent;
        case "system":
            return systemContent;
        case "notifications":
            return notificationsContent;
        case "tray":
            return trayContent;
        case "media":
            return mediaContent;
        case "noko":
            return nokoContent;
        case "clipboard":
            return clipboardContent;
        case "vault":
            return vaultContent;
        case "launcher":
            return launcherContent;
        case "audio":
            return audioContent;
        case "brightness":
            return brightnessContent;
        case "connectivity":
            return connectivityContent;
        case "power":
            return powerContent;
        default:
            return helpContent;
        }
    }

    visible: surfaces && (surfaces.popoutOpen || surfaces.popoutClosing)
    screen: root.screenByName(root.surfaces ? root.surfaces.popoutOutput : "")
    implicitWidth: root.surfaceWidth
    implicitHeight: root.surfaceHeight
    color: "transparent"
    exclusionMode: ExclusionMode.Ignore
    WlrLayershell.layer: WlrLayer.Overlay
    WlrLayershell.namespace: "nuguland-popout"
    WlrLayershell.keyboardFocus: root.visible && (!root.privilegeService || !root.privilegeService.active) ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
    onVisibleChanged: {
        if (!visible && root.surfaces && root.surfaces.popoutOpen && !root.surfaces.popoutClosing)
            root.surfaces.closePopout("focus-grab-outside");
    }
    onClosed: {
        if (root.surfaces)
            root.surfaces.closePopout("popup-closed");
    }

    anchors {
        top: true
        left: true
        right: true
        bottom: true
    }

    Shortcut {
        sequence: "Escape"
        context: Qt.WindowShortcut
        onActivated: {
            if (root.surfaces)
                root.surfaces.closePopout("escape-key");
        }
    }

    Item {
        id: popoutInputRegion

        // origin sits just past the bar: below it (horizontal) or right of it
        // (vertical), so clicks on the bar itself are never captured here.
        x: root.vertical && root.surfaces ? root.surfaces.barHeight : 0
        y: root.vertical || !root.surfaces ? 0 : root.surfaces.barHeight
        width: Math.max(1, parent.width - x)
        height: Math.max(1, parent.height - y)
    }

    MouseArea {
        id: outsideDismissLayer

        x: popoutInputRegion.x
        y: popoutInputRegion.y
        width: popoutInputRegion.width
        height: popoutInputRegion.height
        acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
        onPressed: mouse => {
            // mouse.x/y are layer-local; the card rect is in root coords. The
            // layer origin is offset on both axes (below the top bar, or right
            // of the left bar), so convert both before the hit test.
            const rootX = mouse.x + outsideDismissLayer.x;
            const rootY = mouse.y + outsideDismissLayer.y;
            const inside = rootX >= card.x && rootX <= card.x + card.width && rootY >= card.y && rootY <= card.y + card.height;
            if (!inside && root.surfaces) {
                mouse.accepted = true;
                root.surfaces.closePopout("outside");
            }
        }
    }

    PopupSurface {
        id: card

        theme: root.theme
        padding: 0
        x: Math.round(root.currentRect.x)
        y: Math.round(root.currentRect.y)
        width: Math.round(root.currentRect.width)
        height: Math.round(root.currentRect.height)
        opacity: root.cardFrame.opacity
        transformOrigin: Item.Top
        focus: root.visible
        onVisibleChanged: {
            if (visible)
                forceActiveFocus();
        }
        Keys.onEscapePressed: event => {
            event.accepted = true;
            if (root.surfaces)
                root.surfaces.closePopout("escape-key");
        }

        Column {
            anchors.fill: parent
            anchors.margins: 16
            spacing: 10

            Item {
                id: loaderStack

                width: parent.width
                height: parent.height - y
                clip: true

                Loader {
                    id: removingLoader

                    anchors.fill: parent
                    active: root.removingName.length > 0
                    opacity: active ? Math.max(0, 1 - (root.surfaces ? root.surfaces.popoutProgress : 1)) : 0
                    sourceComponent: active ? root.contentComponent(root.removingName) : null

                    Behavior on opacity {
                        NumberAnimation {
                            duration: root.theme && root.theme.lowPowerMode ? 0 : 180
                            easing.type: Easing.OutCubic
                        }
                    }
                }

                Loader {
                    id: activeLoader

                    anchors.fill: parent
                    opacity: root.surfaces ? root.surfaces.popoutProgress : 1
                    sourceComponent: root.activeName.length > 0 ? root.contentComponent(root.activeName) : null
                    onLoaded: {
                        root.awaitingScrollRestore = true;
                        Qt.callLater(root.restorePanelState);
                    }

                    Behavior on opacity {
                        NumberAnimation {
                            duration: root.theme && root.theme.lowPowerMode ? 0 : 180
                            easing.type: Easing.OutCubic
                        }
                    }
                }
            }
        }

        Behavior on y {
            NumberAnimation {
                duration: root.theme && root.theme.lowPowerMode ? 0 : 130
                easing.type: Easing.InOutCubic
            }
        }

        Behavior on opacity {
            NumberAnimation {
                duration: root.theme && root.theme.lowPowerMode ? 0 : 90
                easing.type: Easing.InOutCubic
            }
        }
    }

    Component {
        id: clockContent

        ClockPanel {
            theme: root.theme
            timeService: root.timeService
            caldavService: root.caldavService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: systemContent

        SystemPanel {
            theme: root.theme
            resourceService: root.resourceService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: notificationsContent

        NotificationGroup {
            theme: root.theme
            notificationService: root.notificationService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: trayContent

        TrayPanel {
            theme: root.theme
            trayService: root.trayService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: mediaContent

        MediaPanel {
            theme: root.theme
            mediaService: root.mediaService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: nokoContent

        NokoPanel {
            theme: root.theme
            panelState: root.nokoService
        }
    }

    Component {
        id: clipboardContent

        ClipboardPanel {
            theme: root.theme
            clipboardService: root.clipboardService
            sessionState: root.surfaces.panelStates.clipboard
            onPasteRequested: id => root.clipboardService.paste(id)
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: vaultContent

        VaultPanel {
            theme: root.theme
            vaultService: root.vaultService
            width: parent ? parent.width : 0
            height: parent ? parent.height : 0
        }
    }

    Component {
        id: launcherContent

        LauncherPanel {
            theme: root.theme
            launcherService: root.launcherService
            sessionState: root.surfaces.panelStates.launcher
            surfaces: root.surfaces
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: audioContent

        AudioPanel {
            theme: root.theme
            audioService: root.audioService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: brightnessContent

        BrightnessPanel {
            theme: root.theme
            brightnessService: root.brightnessService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: connectivityContent

        ConnectivityPanel {
            theme: root.theme
            resourceService: root.resourceService
            networkService: root.networkService
            bluetoothService: root.bluetoothService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: powerContent

        PowerPanel {
            theme: root.theme
            powerService: root.powerService
            width: parent ? parent.width : 0
        }
    }

    Component {
        id: helpContent

        Column {
            spacing: 8
            width: parent ? parent.width : 0

            NuguText {
                theme: root.theme
                text: "no popout content."
                width: parent.width
                wrapMode: Text.WordWrap
            }
        }
    }

    mask: Region {
        item: popoutInputRegion
    }

    Behavior on implicitWidth {
        NumberAnimation {
            duration: root.theme && root.theme.lowPowerMode ? 0 : 180
            easing.type: Easing.OutCubic
        }
    }

    Behavior on implicitHeight {
        NumberAnimation {
            duration: root.theme && root.theme.lowPowerMode ? 0 : 180
            easing.type: Easing.OutCubic
        }
    }
}