Files
noctalia-shell/Modules/MainScreen/MainScreen.qml
T

597 lines
16 KiB
QML

import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services.UI
import "Backgrounds" as Backgrounds
// All panels
import qs.Modules.Bar
import qs.Modules.Panels.Audio
import qs.Modules.Panels.Battery
import qs.Modules.Panels.Bluetooth
import qs.Modules.Panels.Calendar
import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.Launcher
import qs.Modules.Panels.NotificationHistory
import qs.Modules.Panels.SessionMenu
import qs.Modules.Panels.Settings
import qs.Modules.Panels.SetupWizard
import qs.Modules.Panels.Tray
import qs.Modules.Panels.Wallpaper
import qs.Modules.Panels.WiFi
/**
* MainScreen - Single PanelWindow per screen that manages all panels and the bar
*/
PanelWindow {
id: root
// Expose panels as readonly property aliases
readonly property alias audioPanel: audioPanel
readonly property alias batteryPanel: batteryPanel
readonly property alias bluetoothPanel: bluetoothPanel
readonly property alias calendarPanel: calendarPanel
readonly property alias controlCenterPanel: controlCenterPanel
readonly property alias launcherPanel: launcherPanel
readonly property alias notificationHistoryPanel: notificationHistoryPanel
readonly property alias sessionMenuPanel: sessionMenuPanel
readonly property alias settingsPanel: settingsPanel
readonly property alias setupWizardPanel: setupWizardPanel
readonly property alias trayDrawerPanel: trayDrawerPanel
readonly property alias trayMenuPanel: trayMenuPanel
readonly property alias wallpaperPanel: wallpaperPanel
readonly property alias wifiPanel: wifiPanel
Component.onCompleted: {
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y)
}
// Wayland
// Always use Exclusive keyboard focus when a panel is open
// This ensures all keyboard shortcuts work reliably (Escape, etc.)
// The centralized shortcuts in this window handle delegation to panels
WlrLayershell.keyboardFocus: root.isPanelOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "noctalia-screen-" + (screen?.name || "unknown")
WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone handles that
anchors {
top: true
bottom: true
left: true
right: true
}
// Desktop dimming when panels are open
property bool dimDesktop: Settings.data.general.dimDesktop
property bool isPanelOpen: (PanelService.openedPanel !== null) && (PanelService.openedPanel.screen === screen)
property bool isPanelClosing: (PanelService.openedPanel !== null) && PanelService.openedPanel.isClosing
color: {
if (dimDesktop && isPanelOpen) {
return Qt.alpha(Color.mShadow, 0.8)
}
return Color.transparent
}
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.OutQuad
}
}
// Fully reactive mask system, make everything click-through except bar and open panels
mask: Region {
id: clickableMask
// Cover entire window (everything is masked/click-through)
x: 0
y: 0
width: root.width
height: root.height
intersection: Intersection.Xor
// Direct binding to Variants.instances plus additional regions
regions: panelRegions.instances.concat([barMaskRegion, backgroundMaskRegion])
// Bar region - subtract bar area from mask
Region {
id: barMaskRegion
x: barPlaceholder.x
y: barPlaceholder.y
width: barPlaceholder.width
height: barPlaceholder.height
intersection: Intersection.Subtract
}
// Background region for click-to-close - reactive sizing
Region {
id: backgroundMaskRegion
x: 0
y: 0
width: root.isPanelOpen && !isPanelClosing ? root.width : 0
height: root.isPanelOpen && !isPanelClosing ? root.height : 0
intersection: Intersection.Subtract
}
}
// Container for all UI elements
Item {
id: container
width: root.width
height: root.height
// Unified backgrounds container / unified shadow system
// Renders all bar and panel backgrounds as ShapePaths within a single Shape
// This allows the shadow effect to apply to all backgrounds in one render pass
Backgrounds.AllBackgrounds {
id: unifiedBackgrounds
anchors.fill: parent
bar: barPlaceholder.barItem || null
windowRoot: root
z: 0 // Behind all content
}
// Background MouseArea for closing panels when clicking outside
// Active whenever a panel is open - the mask ensures it only receives clicks when panel is open
MouseArea {
anchors.fill: parent
enabled: root.isPanelOpen
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (PanelService.openedPanel) {
PanelService.openedPanel.close()
}
}
z: 0 // Behind panels and bar
}
// ---------------------------------------
// All panels always exist
// ---------------------------------------
AudioPanel {
id: audioPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "audioPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(audioPanel)
}
}
BatteryPanel {
id: batteryPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "batteryPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(batteryPanel)
}
}
BluetoothPanel {
id: bluetoothPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "bluetoothPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(bluetoothPanel)
}
}
ControlCenterPanel {
id: controlCenterPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "controlCenterPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(controlCenterPanel)
}
}
CalendarPanel {
id: calendarPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "calendarPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(calendarPanel)
}
}
Launcher {
id: launcherPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "launcherPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(launcherPanel)
}
}
NotificationHistoryPanel {
id: notificationHistoryPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "notificationHistoryPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(notificationHistoryPanel)
}
}
SessionMenu {
id: sessionMenuPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "sessionMenuPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(sessionMenuPanel)
}
}
SettingsPanel {
id: settingsPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "settingsPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(settingsPanel)
}
}
SetupWizard {
id: setupWizardPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "setupWizardPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(setupWizardPanel)
}
}
TrayDrawerPanel {
id: trayDrawerPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "trayDrawerPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(trayDrawerPanel)
}
}
TrayMenuPanel {
id: trayMenuPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "trayMenuPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(trayMenuPanel)
}
}
WallpaperPanel {
id: wallpaperPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "wallpaperPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(wallpaperPanel)
}
}
WiFiPanel {
id: wifiPanel
screen: root.screen
z: 50
Component.onCompleted: {
objectName = "wifiPanel-" + (screen?.name || "unknown")
PanelService.registerPanel(wifiPanel)
}
}
// Bar placeholder - just for background positioning (actual bar content is in BarContentWindow)
Item {
id: barPlaceholder
// Expose self as barItem for AllBackgrounds compatibility
readonly property var barItem: barPlaceholder
// Screen reference
property ShellScreen screen: root.screen
// Bar positioning properties (match Bar.qml logic)
readonly property string barPosition: Settings.data.bar.position || "top"
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property bool barFloating: Settings.data.bar.floating || false
readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
// Expose bar dimensions directly on this Item for BarBackground
// Use screen dimensions directly
x: {
if (barPosition === "right")
return screen.width - Style.barHeight - barMarginH
return barMarginH
}
y: {
if (barPosition === "bottom")
return screen.height - Style.barHeight - barMarginV
return barMarginV
}
width: {
if (barIsVertical) {
return Style.barHeight + 1
}
return screen.width - barMarginH * 2
}
height: {
if (barIsVertical) {
return screen.height - barMarginV * 2
}
return Style.barHeight
}
// Corner states (same as Bar.qml)
readonly property int topLeftCornerState: {
if (barFloating)
return 0
if (barPosition === "top")
return -1
if (barPosition === "left")
return -1
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) {
return barIsVertical ? 1 : 2
}
return -1
}
readonly property int topRightCornerState: {
if (barFloating)
return 0
if (barPosition === "top")
return -1
if (barPosition === "right")
return -1
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
return barIsVertical ? 1 : 2
}
return -1
}
readonly property int bottomLeftCornerState: {
if (barFloating)
return 0
if (barPosition === "bottom")
return -1
if (barPosition === "left")
return -1
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
return barIsVertical ? 1 : 2
}
return -1
}
readonly property int bottomRightCornerState: {
if (barFloating)
return 0
if (barPosition === "bottom")
return -1
if (barPosition === "right")
return -1
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
return barIsVertical ? 1 : 2
}
return -1
}
Component.onCompleted: {
Logger.d("MainScreen", "===== Bar placeholder loaded =====")
Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height)
Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical)
Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height)
Logger.d("MainScreen", " Style.barHeight =", Style.barHeight)
Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating)
}
}
/**
* Screen Corners
*/
ScreenCorners {}
}
// Centralized keyboard shortcuts - delegate to opened panel
// This ensures shortcuts work regardless of panel focus state
Shortcut {
sequence: "Escape"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onEscapePressed) {
PanelService.openedPanel.onEscapePressed()
} else if (PanelService.openedPanel) {
PanelService.openedPanel.close()
}
}
}
Shortcut {
sequence: "Tab"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onTabPressed) {
PanelService.openedPanel.onTabPressed()
}
}
}
Shortcut {
sequence: "Shift+Tab"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onShiftTabPressed) {
PanelService.openedPanel.onShiftTabPressed()
}
}
}
Shortcut {
sequence: "Up"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onUpPressed) {
PanelService.openedPanel.onUpPressed()
}
}
}
Shortcut {
sequence: "Down"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onDownPressed) {
PanelService.openedPanel.onDownPressed()
}
}
}
Shortcut {
sequence: "Return"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onReturnPressed) {
PanelService.openedPanel.onReturnPressed()
}
}
}
Shortcut {
sequence: "Enter"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onReturnPressed) {
PanelService.openedPanel.onReturnPressed()
}
}
}
Shortcut {
sequence: "Home"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onHomePressed) {
PanelService.openedPanel.onHomePressed()
}
}
}
Shortcut {
sequence: "End"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onEndPressed) {
PanelService.openedPanel.onEndPressed()
}
}
}
Shortcut {
sequence: "PgUp"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onPageUpPressed) {
PanelService.openedPanel.onPageUpPressed()
}
}
}
Shortcut {
sequence: "PgDown"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onPageDownPressed) {
PanelService.openedPanel.onPageDownPressed()
}
}
}
Shortcut {
sequence: "Ctrl+J"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlJPressed) {
PanelService.openedPanel.onCtrlJPressed()
}
}
}
Shortcut {
sequence: "Ctrl+K"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlKPressed) {
PanelService.openedPanel.onCtrlKPressed()
}
}
}
Shortcut {
sequence: "Ctrl+N"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlNPressed) {
PanelService.openedPanel.onCtrlNPressed()
}
}
}
Shortcut {
sequence: "Ctrl+P"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlPPressed) {
PanelService.openedPanel.onCtrlPPressed()
}
}
}
Shortcut {
sequence: "Left"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onLeftPressed) {
PanelService.openedPanel.onLeftPressed()
}
}
}
Shortcut {
sequence: "Right"
enabled: root.isPanelOpen
onActivated: {
if (PanelService.openedPanel && PanelService.openedPanel.onRightPressed) {
PanelService.openedPanel.onRightPressed()
}
}
}
}