mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 10:33:34 +00:00
Refactor: Panels and Bar background are now drawn separately with Shapes.
This commit is contained in:
@@ -0,0 +1,536 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
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 trayDropdownPanel: trayDropdownPanel
|
||||
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)
|
||||
}
|
||||
|
||||
// Debug: Log mask region changes
|
||||
onMaskChanged: {
|
||||
Logger.d("MainScreen", "Mask changed!")
|
||||
Logger.d("MainScreen", " Bar region:", barLoader.item?.barRegion)
|
||||
Logger.d("MainScreen", " Panel count:", panelsRepeater.count)
|
||||
for (var i = 0; i < panelsRepeater.count; i++) {
|
||||
var panelItem = panelsRepeater.itemAt(i)?.item
|
||||
Logger.d("MainScreen", " Panel", i, "- open:", panelItem?.isPanelOpen, "- region:", panelItem?.panelRegion)
|
||||
}
|
||||
}
|
||||
|
||||
// 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: Settings.data.ui.panelsOverlayLayer ? WlrLayer.Overlay : 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
|
||||
color: {
|
||||
if (dimDesktop && isPanelOpen) {
|
||||
return Qt.alpha(Color.mSurfaceVariant, Style.opacityHeavy)
|
||||
}
|
||||
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
|
||||
property var barRegion: barLoader.item?.barRegion
|
||||
|
||||
x: barRegion?.x ?? 0
|
||||
y: barRegion?.y ?? 0
|
||||
width: barRegion?.width ?? 0
|
||||
height: barRegion?.height ?? 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
|
||||
// Background region for click-to-close - reactive sizing
|
||||
Region {
|
||||
id: backgroundMaskRegion
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.isPanelOpen ? root.width : 0
|
||||
height: root.isPanelOpen ? root.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
// Variants for panel regions
|
||||
Variants {
|
||||
id: panelRegions
|
||||
|
||||
// Model is the list of open panels (filters out closed ones)
|
||||
model: [audioPanel, controlCenterPanel, calendarPanel].filter(function (p) {
|
||||
return p && p.isPanelOpen
|
||||
})
|
||||
|
||||
Region {
|
||||
required property var modelData
|
||||
property var panelBg: modelData.panelRegion
|
||||
|
||||
// Direct bindings
|
||||
x: panelBg?.x ?? 0
|
||||
y: panelBg?.y ?? 0
|
||||
width: panelBg?.width ?? 0
|
||||
height: panelBg?.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: barLoader.item?.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
|
||||
onClicked: {
|
||||
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)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
BatteryPanel {
|
||||
id: batteryPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "batteryPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(batteryPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothPanel {
|
||||
id: bluetoothPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "bluetoothPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(bluetoothPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
ControlCenterPanel {
|
||||
id: controlCenterPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "controlCenterPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(controlCenterPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
CalendarPanel {
|
||||
id: calendarPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "calendarPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(calendarPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
Launcher {
|
||||
id: launcherPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "launcherPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(launcherPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
NotificationHistoryPanel {
|
||||
id: notificationHistoryPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "notificationHistoryPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(notificationHistoryPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
SessionMenu {
|
||||
id: sessionMenuPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "sessionMenuPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(sessionMenuPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPanel {
|
||||
id: settingsPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "settingsPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(settingsPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
SetupWizard {
|
||||
id: setupWizardPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "setupWizardPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(setupWizardPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
TrayDropdownPanel {
|
||||
id: trayDropdownPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "trayDropdownPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(trayDropdownPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
TrayMenuPanel {
|
||||
id: trayMenuPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "trayMenuPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(trayDropdownPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
WallpaperPanel {
|
||||
id: wallpaperPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "wallpaperPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(wallpaperPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
WiFiPanel {
|
||||
id: wifiPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "wifiPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(wifiPanel)
|
||||
Logger.d("MainScreen", "Panel registered:", objectName, "on screen:", screen?.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Bar (always on top - rendered last in tree, so naturally on top)
|
||||
Loader {
|
||||
id: barLoader
|
||||
asynchronous: false
|
||||
sourceComponent: Bar {}
|
||||
// Keep bar loaded but hide it when BarService.isVisible is false
|
||||
// This allows panels to remain accessible via IPC
|
||||
visible: BarService.isVisible
|
||||
|
||||
// Fill parent to provide dimensions for Bar to reference
|
||||
anchors.fill: parent
|
||||
|
||||
property ShellScreen screen: root.screen
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("MainScreen", "Bar loaded:", item !== null)
|
||||
if (item) {
|
||||
Logger.d("MainScreen", "Bar screen", item.screen?.name, "size:", item.width, "x", item.height)
|
||||
// Bind screen to bar component (use binding for reactivity)
|
||||
item.screen = Qt.binding(function () {
|
||||
return barLoader.screen
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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: "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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user