Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/Osd.qml

Raw
import QtQuick

Item {
    id: root

    property bool visibleState: false
    property bool expired: true
    property string currentEvent: ""
    property string currentValue: ""
    property string currentRole: "info"
    property int restartCount: 0
    readonly property int lifetimeMs: 1200
    readonly property var supportedEvents: ["volume", "microphone", "brightness", "media", "workspace", "screenshot", "color", "error"]

    function roleFor(event, value) {
        const name = String(event);
        const text = String(value);
        if (name === "error")
            return "error";

        if (name === "microphone" && (text === "muted" || text === "active"))
            return "warning";

        if (name === "volume" || name === "brightness")
            return "ui.accent";

        return "info";
    }

    function isNumeric(value) {
        const text = String(value).replace("%", "").trim();
        return text.length > 0 && isFinite(Number(text));
    }

    // 0..1 for the meter-style events (volume/brightness/microphone) when the
    // value is a real number; -1 otherwise (mute toggles, media, color, error)
    // so the bar is hidden rather than drawn empty.
    function normalizedValue(event, value) {
        const name = String(event);
        if (name !== "volume" && name !== "brightness" && name !== "microphone")
            return -1;
        if (!isNumeric(value))
            return -1;
        return Math.max(0, Math.min(1, Number(String(value).replace("%", "")) / 100));
    }

    // freedesktop icon name for the event; resolved to a path at render time
    // via Quickshell.iconPath(). All names verified present in the Adwaita theme.
    function iconName(event, value) {
        const name = String(event);
        const text = String(value);
        const muted = text === "muted" || (isNumeric(text) && Number(text.replace("%", "")) <= 0);
        if (name === "volume")
            return muted ? "audio-volume-muted" : (Number(text.replace("%", "")) < 34 ? "audio-volume-low" : (Number(text.replace("%", "")) < 67 ? "audio-volume-medium" : "audio-volume-high"));
        if (name === "microphone")
            return muted ? "microphone-sensitivity-muted" : "microphone-sensitivity-high";
        if (name === "brightness")
            return "display-brightness-symbolic";
        if (name === "media")
            return text.indexOf("pause") >= 0 ? "media-playback-pause" : "media-playback-start";
        if (name === "screenshot")
            return "applets-screenshooter";
        if (name === "color")
            return "color-picker";
        if (name === "error")
            return "dialog-error";
        return "dialog-information";
    }

    function displayLabel(event) {
        const name = String(event);
        const labels = {
            "volume": "Volume",
            "microphone": "Microphone",
            "brightness": "Brightness",
            "media": "Media",
            "screenshot": "Screenshot",
            "color": "Color",
            "error": "Error"
        };
        return labels[name] || (name.length > 0 ? name : "OSD");
    }

    // right-aligned glance value: "72%" for meters, a word for toggles, the
    // raw payload (media event / colour hex) otherwise. Errors render their
    // message in the body line instead, so the corner stays empty.
    function displayValue(event, value) {
        const name = String(event);
        const text = String(value);
        const normalized = normalizedValue(name, text);
        if (normalized >= 0)
            return `${Math.round(normalized * 100)}%`;
        if (text === "muted")
            return "muted";
        if (text === "unmuted")
            return "on";
        if (name === "error")
            return "";
        return text;
    }

    function iconRole(event, value) {
        const name = String(event);
        const text = String(value);
        if (name === "volume")
            return Number(text) <= 0 ? "audio-muted" : "audio-volume";

        if (name === "microphone")
            return text === "muted" ? "microphone-muted" : "microphone-active";

        if (name === "brightness")
            return "brightness";

        if (name === "screenshot")
            return "screenshot";

        if (name === "color")
            return "color-picker";

        if (name === "error")
            return "danger";

        return name.length > 0 ? name : "info";
    }

    function numericText(event, value) {
        const normalized = normalizedValue(event, value);
        return normalized >= 0 ? `${Math.round(normalized * 100)}` : "";
    }

    function valueIndicator(event, value) {
        const name = String(event);
        const text = String(value);
        const normalized = normalizedValue(name, text);
        const hasProgress = normalized >= 0;
        return {
            "source": "Nuguland OsdValueIndicator.qml plus Nuguland OSD sliders",
            "layout": "icon-left-value-right",
            "iconRole": iconRole(name, text),
            "iconName": iconName(name, text),
            "displayLabel": displayLabel(name),
            "displayValue": displayValue(name, text),
            "value": text,
            "normalizedValue": hasProgress ? normalized : null,
            "progressBar": {
                "visible": hasProgress,
                "from": 0,
                "to": 1,
                "value": hasProgress ? normalized : 0,
                "role": currentRole
            },
            "numericText": {
                "text": numericText(name, text),
                "odometerMotion": hasProgress,
                "hardSeparator": hasProgress ? "%" : ""
            },
            "slideFromEdge": {
                "edge": "right",
                "hiddenOffset": "width+sidebar-gap",
                "visibleOffset": 0
            },
            "wheelActions": name === "volume" ? ["volume-up", "volume-down"] : name === "brightness" ? ["brightness-up", "brightness-down"] : [],
            "protectionMessage": {
                "visible": name === "error",
                "role": "error",
                "text": name === "error" ? text : ""
            },
            "hardSeparator": hasProgress ? "%" : ""
        };
    }

    function stateObject() {
        return {
            "visible": visibleState,
            "expired": expired,
            "event": currentEvent,
            "value": currentValue,
            "role": currentRole,
            "iconName": iconName(currentEvent, currentValue),
            "displayLabel": displayLabel(currentEvent),
            "displayValue": displayValue(currentEvent, currentValue),
            "lifetimeMs": lifetimeMs,
            "restartCount": restartCount,
            "supportedEvents": supportedEvents,
            "surface": "masked-overlay",
            "blocksInput": false,
            "reservesSpace": false,
            "valueIndicator": valueIndicator(currentEvent, currentValue)
        };
    }

    function stateJson() {
        return JSON.stringify(stateObject());
    }

    function trigger(event, value) {
        const name = supportedEvents.indexOf(String(event)) >= 0 ? String(event) : "error";
        currentEvent = name;
        currentValue = String(value);
        currentRole = roleFor(name, currentValue);
        visibleState = true;
        expired = false;
        restartCount += 1;
        expiryTimer.restart();
        return stateJson();
    }

    function expire() {
        expiryTimer.stop();
        visibleState = false;
        expired = true;
        return stateJson();
    }

    Timer {
        id: expiryTimer

        interval: root.lifetimeMs
        repeat: false
        onTriggered: root.expire()
    }
}