repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/PopoutHost.qml
Rawpragma 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
}
}
}