NPanel refactor - 1st pass: SidePanel and settings an new logic
This commit is contained in:
parent
c8a93d7d1e
commit
57fee68793
14 changed files with 500 additions and 616 deletions
|
|
@ -30,9 +30,6 @@ Singleton {
|
||||||
// Flag to prevent unnecessary wallpaper calls during reloads
|
// Flag to prevent unnecessary wallpaper calls during reloads
|
||||||
property bool isInitialLoad: true
|
property bool isInitialLoad: true
|
||||||
|
|
||||||
// Needed to only have one NPanel loaded at a time. <--- VERY BROKEN
|
|
||||||
//property var openPanel: null
|
|
||||||
|
|
||||||
// Function to validate monitor configurations
|
// Function to validate monitor configurations
|
||||||
function validateMonitorConfigurations() {
|
function validateMonitorConfigurations() {
|
||||||
var availableScreenNames = []
|
var availableScreenNames = []
|
||||||
|
|
@ -86,14 +83,14 @@ Singleton {
|
||||||
if (isInitialLoad) {
|
if (isInitialLoad) {
|
||||||
Logger.log("Settings", "OnLoaded")
|
Logger.log("Settings", "OnLoaded")
|
||||||
// Only set wallpaper on initial load, not on reloads
|
// Only set wallpaper on initial load, not on reloads
|
||||||
if (adapter.wallpaper.current !== "") {
|
if (adapter.wallpaper.current !== "") {
|
||||||
Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current)
|
Logger.log("Settings", "Set current wallpaper", adapter.wallpaper.current)
|
||||||
WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true)
|
WallpaperService.setCurrentWallpaper(adapter.wallpaper.current, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate monitor configurations, only once
|
// Validate monitor configurations, only once
|
||||||
// if none of the configured monitors exist, clear the lists
|
// if none of the configured monitors exist, clear the lists
|
||||||
validateMonitorConfigurations()
|
validateMonitorConfigurations()
|
||||||
}
|
}
|
||||||
|
|
||||||
isInitialLoad = false
|
isInitialLoad = false
|
||||||
|
|
@ -128,7 +125,7 @@ Singleton {
|
||||||
|
|
||||||
general: JsonObject {
|
general: JsonObject {
|
||||||
property string avatarImage: defaultAvatar
|
property string avatarImage: defaultAvatar
|
||||||
property bool dimDesktop: true
|
property bool dimDesktop: false
|
||||||
property bool showScreenCorners: false
|
property bool showScreenCorners: false
|
||||||
property real radiusRatio: 1.0
|
property real radiusRatio: 1.0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,22 +15,25 @@ NIconButton {
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// Map this button's center to the screen and open the side panel below it
|
sidePanel.toggle(screen)
|
||||||
const localCenterX = width / 2
|
// sidePanel.isLoaded = !sidePanel.isLoaded
|
||||||
const localCenterY = height / 2
|
// Logger.log("SidePanelToggle", sidePanel.isLoaded)
|
||||||
const globalPoint = mapToItem(null, localCenterX, localCenterY)
|
// // Map this button's center to the screen and open the side panel below it
|
||||||
if (sidePanel.isLoaded) {
|
// const localCenterX = width / 2
|
||||||
// Call hide() instead of directly setting isLoaded to false
|
// const localCenterY = height / 2
|
||||||
if (sidePanel.item && sidePanel.item.hide) {
|
// const globalPoint = mapToItem(null, localCenterX, localCenterY)
|
||||||
sidePanel.item.hide()
|
// if (sidePanel.isLoaded) {
|
||||||
} else {
|
// // Call hide() instead of directly setting isLoaded to false
|
||||||
sidePanel.isLoaded = false
|
// if (sidePanel.item && sidePanel.item.hide) {
|
||||||
}
|
// sidePanel.item.hide()
|
||||||
} else if (sidePanel.openAt) {
|
// } else {
|
||||||
sidePanel.openAt(globalPoint.x, screen)
|
// sidePanel.isLoaded = false
|
||||||
} else {
|
// }
|
||||||
// Fallback: toggle if API unavailable
|
// } else if (sidePanel.openAt) {
|
||||||
sidePanel.isLoaded = true
|
// sidePanel.openAt(globalPoint.x, screen)
|
||||||
}
|
// } else {
|
||||||
|
// // Fallback: toggle if API unavailable
|
||||||
|
// sidePanel.isLoaded = true
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,6 @@ Rectangle {
|
||||||
// Wrapped in NPanel so we can detect click outside of the menu to close the TrayMenu
|
// Wrapped in NPanel so we can detect click outside of the menu to close the TrayMenu
|
||||||
NPanel {
|
NPanel {
|
||||||
id: trayPanel
|
id: trayPanel
|
||||||
showOverlay: false // no colors overlay even if activated in settings
|
|
||||||
|
|
||||||
// Override hide function to animate first
|
// Override hide function to animate first
|
||||||
function hide() {
|
function hide() {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ Item {
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.AudioService
|
settingsPanel.requestedTab = SettingsPanel.Tab.AudioService
|
||||||
settingsPanel.isLoaded = true
|
settingsPanel.open(screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,13 @@ import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
NLoader {
|
NPanel {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
rWidth: Math.max(screen?.width * 0.5, 1280) * scaling
|
||||||
|
rHeight: Math.max(screen?.height * 0.5, 720) * scaling
|
||||||
|
rAnchorCentered: true
|
||||||
|
|
||||||
// Tabs enumeration, order is NOT relevant
|
// Tabs enumeration, order is NOT relevant
|
||||||
enum Tab {
|
enum Tab {
|
||||||
About,
|
About,
|
||||||
|
|
@ -28,344 +32,264 @@ NLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
property int requestedTab: SettingsPanel.Tab.General
|
property int requestedTab: SettingsPanel.Tab.General
|
||||||
|
property int currentTabIndex: 0
|
||||||
|
|
||||||
content: Component {
|
Component {
|
||||||
NPanel {
|
id: generalTab
|
||||||
id: panel
|
Tabs.GeneralTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: barTab
|
||||||
|
Tabs.BarTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: audioTab
|
||||||
|
Tabs.AudioTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: brightnessTab
|
||||||
|
Tabs.BrightnessTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: displayTab
|
||||||
|
Tabs.DisplayTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: networkTab
|
||||||
|
Tabs.NetworkTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: timeWeatherTab
|
||||||
|
Tabs.TimeWeatherTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: colorSchemeTab
|
||||||
|
Tabs.ColorSchemeTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: wallpaperTab
|
||||||
|
Tabs.WallpaperTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: wallpaperSelectorTab
|
||||||
|
Tabs.WallpaperSelectorTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: screenRecorderTab
|
||||||
|
Tabs.ScreenRecorderTab {}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: aboutTab
|
||||||
|
Tabs.AboutTab {}
|
||||||
|
}
|
||||||
|
|
||||||
property int currentTabIndex: 0
|
// Order *DOES* matter
|
||||||
|
property var tabsModel: [{
|
||||||
|
"id": SettingsPanel.Tab.General,
|
||||||
|
"label": "General",
|
||||||
|
"icon": "tune",
|
||||||
|
"source": generalTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.Bar,
|
||||||
|
"label": "Bar",
|
||||||
|
"icon": "web_asset",
|
||||||
|
"source": barTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.AudioService,
|
||||||
|
"label": "Audio",
|
||||||
|
"icon": "volume_up",
|
||||||
|
"source": audioTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.Display,
|
||||||
|
"label": "Display",
|
||||||
|
"icon": "monitor",
|
||||||
|
"source": displayTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.Network,
|
||||||
|
"label": "Network",
|
||||||
|
"icon": "lan",
|
||||||
|
"source": networkTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.Brightness,
|
||||||
|
"label": "Brightness",
|
||||||
|
"icon": "brightness_6",
|
||||||
|
"source": brightnessTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.TimeWeather,
|
||||||
|
"label": "Time & Weather",
|
||||||
|
"icon": "schedule",
|
||||||
|
"source": timeWeatherTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.ColorScheme,
|
||||||
|
"label": "Color Scheme",
|
||||||
|
"icon": "palette",
|
||||||
|
"source": colorSchemeTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.Wallpaper,
|
||||||
|
"label": "Wallpaper",
|
||||||
|
"icon": "image",
|
||||||
|
"source": wallpaperTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.WallpaperSelector,
|
||||||
|
"label": "Wallpaper Selector",
|
||||||
|
"icon": "wallpaper_slideshow",
|
||||||
|
"source": wallpaperSelectorTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||||
|
"label": "Screen Recorder",
|
||||||
|
"icon": "videocam",
|
||||||
|
"source": screenRecorderTab
|
||||||
|
}, {
|
||||||
|
"id": SettingsPanel.Tab.About,
|
||||||
|
"label": "About",
|
||||||
|
"icon": "info",
|
||||||
|
"source": aboutTab
|
||||||
|
}]
|
||||||
|
|
||||||
// Override hide function to animate first
|
// When the panel opens, choose the appropriate tab
|
||||||
function hide() {
|
onOpened: {
|
||||||
// Start hide animation
|
var initialIndex = SettingsPanel.Tab.General
|
||||||
bgRect.scaleValue = 0.8
|
if (root.requestedTab !== null) {
|
||||||
bgRect.opacityValue = 0.0
|
for (var i = 0; i < root.tabsModel.length; i++) {
|
||||||
// Hide after animation completes
|
if (root.tabsModel[i].id === root.requestedTab) {
|
||||||
hideTimer.start()
|
initialIndex = i
|
||||||
}
|
break
|
||||||
|
|
||||||
// Connect to NPanel's dismissed signal to handle external close events
|
|
||||||
Connections {
|
|
||||||
target: panel
|
|
||||||
function onDismissed() {
|
|
||||||
hide()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Now that the UI is settled, set the current tab index.
|
||||||
|
root.currentTabIndex = initialIndex
|
||||||
|
}
|
||||||
|
|
||||||
// Timer to hide panel after animation
|
panelContent: Rectangle {
|
||||||
Timer {
|
anchors.fill: parent
|
||||||
id: hideTimer
|
anchors.margins: Style.marginL * scaling
|
||||||
interval: Style.animationSlow
|
color: Color.transparent
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
panel.visible = false
|
|
||||||
panel.dismissed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
Component {
|
spacing: Style.marginM * scaling
|
||||||
id: generalTab
|
|
||||||
Tabs.GeneralTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: barTab
|
|
||||||
Tabs.BarTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: audioTab
|
|
||||||
Tabs.AudioTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: brightnessTab
|
|
||||||
Tabs.BrightnessTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: displayTab
|
|
||||||
Tabs.DisplayTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: networkTab
|
|
||||||
Tabs.NetworkTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: timeWeatherTab
|
|
||||||
Tabs.TimeWeatherTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: colorSchemeTab
|
|
||||||
Tabs.ColorSchemeTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: wallpaperTab
|
|
||||||
Tabs.WallpaperTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: wallpaperSelectorTab
|
|
||||||
Tabs.WallpaperSelectorTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: screenRecorderTab
|
|
||||||
Tabs.ScreenRecorderTab {}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: aboutTab
|
|
||||||
Tabs.AboutTab {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order *DOES* matter
|
|
||||||
property var tabsModel: [{
|
|
||||||
"id": SettingsPanel.Tab.General,
|
|
||||||
"label": "General",
|
|
||||||
"icon": "tune",
|
|
||||||
"source": generalTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.Bar,
|
|
||||||
"label": "Bar",
|
|
||||||
"icon": "web_asset",
|
|
||||||
"source": barTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.AudioService,
|
|
||||||
"label": "Audio",
|
|
||||||
"icon": "volume_up",
|
|
||||||
"source": audioTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.Display,
|
|
||||||
"label": "Display",
|
|
||||||
"icon": "monitor",
|
|
||||||
"source": displayTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.Network,
|
|
||||||
"label": "Network",
|
|
||||||
"icon": "lan",
|
|
||||||
"source": networkTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.Brightness,
|
|
||||||
"label": "Brightness",
|
|
||||||
"icon": "brightness_6",
|
|
||||||
"source": brightnessTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.TimeWeather,
|
|
||||||
"label": "Time & Weather",
|
|
||||||
"icon": "schedule",
|
|
||||||
"source": timeWeatherTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.ColorScheme,
|
|
||||||
"label": "Color Scheme",
|
|
||||||
"icon": "palette",
|
|
||||||
"source": colorSchemeTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.Wallpaper,
|
|
||||||
"label": "Wallpaper",
|
|
||||||
"icon": "image",
|
|
||||||
"source": wallpaperTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.WallpaperSelector,
|
|
||||||
"label": "Wallpaper Selector",
|
|
||||||
"icon": "wallpaper_slideshow",
|
|
||||||
"source": wallpaperSelectorTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
|
||||||
"label": "Screen Recorder",
|
|
||||||
"icon": "videocam",
|
|
||||||
"source": screenRecorderTab
|
|
||||||
}, {
|
|
||||||
"id": SettingsPanel.Tab.About,
|
|
||||||
"label": "About",
|
|
||||||
"icon": "info",
|
|
||||||
"source": aboutTab
|
|
||||||
}]
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
var initialIndex = 0
|
|
||||||
if (root.requestedTab !== null) {
|
|
||||||
for (var i = 0; i < panel.tabsModel.length; i++) {
|
|
||||||
if (panel.tabsModel[i].id === root.requestedTab) {
|
|
||||||
initialIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Now that the UI is settled, set the current tab index.
|
|
||||||
panel.currentTabIndex = initialIndex
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible && (bgRect.opacityValue > 0)) {
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bgRect
|
id: sidebar
|
||||||
color: Color.mSurface
|
Layout.preferredWidth: Style.sliderWidth * 1.3 * scaling
|
||||||
radius: Style.radiusL * scaling
|
Layout.fillHeight: true
|
||||||
|
color: Color.mSurfaceVariant
|
||||||
border.color: Color.mOutline
|
border.color: Color.mOutline
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
layer.enabled: true
|
radius: Style.radiusM * scaling
|
||||||
width: Math.max(screen.width * 0.5, 1280) * scaling
|
|
||||||
height: Math.max(screen.height * 0.5, 720) * scaling
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
// Animation properties
|
Column {
|
||||||
property real scaleValue: 0.8
|
|
||||||
property real opacityValue: 0.0
|
|
||||||
|
|
||||||
scale: scaleValue
|
|
||||||
opacity: opacityValue
|
|
||||||
|
|
||||||
// Animate in when component is completed
|
|
||||||
Component.onCompleted: {
|
|
||||||
scaleValue = 1.0
|
|
||||||
opacityValue = 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
anchors.margins: Style.marginS * scaling
|
||||||
|
spacing: Style.marginXS * 1.5 * scaling
|
||||||
|
|
||||||
Behavior on scale {
|
Repeater {
|
||||||
NumberAnimation {
|
id: sections
|
||||||
duration: Style.animationSlow
|
model: root.tabsModel
|
||||||
easing.type: Easing.OutExpo
|
delegate: Rectangle {
|
||||||
}
|
id: tabItem
|
||||||
}
|
width: parent.width
|
||||||
Behavior on opacity {
|
height: 32 * scaling
|
||||||
NumberAnimation {
|
radius: Style.radiusS * scaling
|
||||||
duration: Style.animationNormal
|
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mTertiary : Color.transparent)
|
||||||
easing.type: Easing.OutQuad
|
readonly property bool selected: index === currentTabIndex
|
||||||
}
|
property bool hovering: false
|
||||||
}
|
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnTertiary : Color.mOnSurface)
|
||||||
|
RowLayout {
|
||||||
RowLayout {
|
anchors.fill: parent
|
||||||
anchors.fill: parent
|
anchors.leftMargin: Style.marginS * scaling
|
||||||
anchors.margins: Style.marginL * scaling
|
anchors.rightMargin: Style.marginS * scaling
|
||||||
spacing: Style.marginL * scaling
|
spacing: Style.marginS * scaling
|
||||||
|
// Tab icon on the left side
|
||||||
Rectangle {
|
NIcon {
|
||||||
id: sidebar
|
text: modelData.icon
|
||||||
Layout.preferredWidth: Style.sliderWidth * 1.3 * scaling
|
color: tabTextColor
|
||||||
Layout.fillHeight: true
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
color: Color.mSurfaceVariant
|
|
||||||
border.color: Color.mOutline
|
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
|
||||||
radius: Style.radiusM * scaling
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginS * scaling
|
|
||||||
spacing: Style.marginXS * 1.5 * scaling
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
id: sections
|
|
||||||
model: panel.tabsModel
|
|
||||||
delegate: Rectangle {
|
|
||||||
id: tabItem
|
|
||||||
width: parent.width
|
|
||||||
height: 32 * scaling
|
|
||||||
radius: Style.radiusS * scaling
|
|
||||||
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mTertiary : Color.transparent)
|
|
||||||
readonly property bool selected: index === currentTabIndex
|
|
||||||
property bool hovering: false
|
|
||||||
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnTertiary : Color.mOnSurface)
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: Style.marginS * scaling
|
|
||||||
anchors.rightMargin: Style.marginS * scaling
|
|
||||||
spacing: Style.marginS * scaling
|
|
||||||
// Tab icon on the left side
|
|
||||||
NIcon {
|
|
||||||
text: modelData.icon
|
|
||||||
color: tabTextColor
|
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
|
||||||
}
|
|
||||||
// Tab label on the left side
|
|
||||||
NText {
|
|
||||||
text: modelData.label
|
|
||||||
color: tabTextColor
|
|
||||||
font.pointSize: Style.fontSizeM * scaling
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onEntered: tabItem.hovering = true
|
|
||||||
onExited: tabItem.hovering = false
|
|
||||||
onCanceled: tabItem.hovering = false
|
|
||||||
onClicked: currentTabIndex = index
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// Tab label on the left side
|
||||||
|
NText {
|
||||||
|
text: modelData.label
|
||||||
|
color: tabTextColor
|
||||||
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onEntered: tabItem.hovering = true
|
||||||
|
onExited: tabItem.hovering = false
|
||||||
|
onCanceled: tabItem.hovering = false
|
||||||
|
onClicked: currentTabIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: contentPane
|
id: contentPane
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
radius: Style.radiusM * scaling
|
||||||
|
color: Color.mSurfaceVariant
|
||||||
|
border.color: Color.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Style.marginL * scaling
|
||||||
|
spacing: Style.marginS * scaling
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: headerRow
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.marginS * scaling
|
||||||
|
|
||||||
|
// Tab label on the main right side
|
||||||
|
NText {
|
||||||
|
text: root.tabsModel[currentTabIndex].label
|
||||||
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
|
font.weight: Style.fontWeightBold
|
||||||
|
color: Color.mPrimary
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
NIconButton {
|
||||||
|
icon: "close"
|
||||||
|
tooltipText: "Close"
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
onClicked: root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NDivider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
radius: Style.radiusM * scaling
|
|
||||||
color: Color.mSurfaceVariant
|
|
||||||
border.color: Color.mOutline
|
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
ColumnLayout {
|
Repeater {
|
||||||
id: contentLayout
|
model: root.tabsModel
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Style.marginL * scaling
|
|
||||||
spacing: Style.marginS * scaling
|
|
||||||
|
|
||||||
RowLayout {
|
onItemAdded: function (index, item) {
|
||||||
id: headerRow
|
item.sourceComponent = root.tabsModel[index].source
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Style.marginS * scaling
|
|
||||||
|
|
||||||
// Tab label on the main right side
|
|
||||||
NText {
|
|
||||||
text: panel.tabsModel[currentTabIndex].label
|
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
|
||||||
font.weight: Style.fontWeightBold
|
|
||||||
color: Color.mPrimary
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
NIconButton {
|
|
||||||
icon: "close"
|
|
||||||
tooltipText: "Close"
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
onClicked: panel.hide()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NDivider {
|
delegate: Loader {
|
||||||
Layout.fillWidth: true
|
// All loaders will occupy the same space, stacked on top of each other.
|
||||||
}
|
anchors.fill: parent
|
||||||
|
visible: index === root.currentTabIndex
|
||||||
Item {
|
// The loader is only active (and uses memory) when its page is visible.
|
||||||
Layout.fillWidth: true
|
active: visible
|
||||||
Layout.fillHeight: true
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: panel.tabsModel
|
|
||||||
|
|
||||||
onItemAdded: function (index, item) {
|
|
||||||
item.sourceComponent = panel.tabsModel[index].source
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Loader {
|
|
||||||
// All loaders will occupy the same space, stacked on top of each other.
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: index === panel.currentTabIndex
|
|
||||||
// The loader is only active (and uses memory) when its page is visible.
|
|
||||||
active: visible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ NBox {
|
||||||
// PowerProfiles service
|
// PowerProfiles service
|
||||||
property var powerProfiles: PowerProfiles
|
property var powerProfiles: PowerProfiles
|
||||||
readonly property bool hasPP: powerProfiles.hasPerformanceProfile
|
readonly property bool hasPP: powerProfiles.hasPerformanceProfile
|
||||||
|
property real spacing: 0
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: powerRow
|
id: powerRow
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginS * scaling
|
anchors.margins: Style.marginS * scaling
|
||||||
spacing: sidePanel.cardSpacing
|
spacing: spacing
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ NBox {
|
||||||
tooltipText: "Open Settings"
|
tooltipText: "Open Settings"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.General
|
settingsPanel.requestedTab = SettingsPanel.Tab.General
|
||||||
settingsPanel.isLoaded = !settingsPanel.isLoaded
|
settingsPanel.open(screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,8 +78,9 @@ NBox {
|
||||||
|
|
||||||
PowerMenu {
|
PowerMenu {
|
||||||
id: powerMenu
|
id: powerMenu
|
||||||
anchors.top: powerButton.bottom
|
// TBC
|
||||||
anchors.right: powerButton.right
|
// anchors.top: powerButton.bottom
|
||||||
|
// anchors.right: powerButton.right
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import qs.Widgets
|
||||||
|
|
||||||
// Utilities: record & wallpaper
|
// Utilities: record & wallpaper
|
||||||
NBox {
|
NBox {
|
||||||
|
|
||||||
|
property real spacing: 0
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: 1
|
Layout.preferredWidth: 1
|
||||||
implicitHeight: utilRow.implicitHeight + Style.marginM * 2 * scaling
|
implicitHeight: utilRow.implicitHeight + Style.marginM * 2 * scaling
|
||||||
|
|
@ -16,7 +19,7 @@ NBox {
|
||||||
id: utilRow
|
id: utilRow
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginS * scaling
|
anchors.margins: Style.marginS * scaling
|
||||||
spacing: sidePanel.cardSpacing
|
spacing: spacing
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +40,7 @@ NBox {
|
||||||
tooltipText: "Open Wallpaper Selector"
|
tooltipText: "Open Wallpaper Selector"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector
|
settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector
|
||||||
settingsPanel.isLoaded = true
|
settingsPanel.open(screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,205 +7,79 @@ import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
NLoader {
|
NPanel {
|
||||||
id: root
|
id: panel
|
||||||
|
|
||||||
// X coordinate on screen (in pixels) where the panel should align its center.
|
rWidth: 460 * scaling
|
||||||
// Set via openAt(x) from the bar button.
|
rHeight: 700 * scaling
|
||||||
property real anchorX: 0
|
rAnchorRight: true
|
||||||
// Target screen to open on
|
|
||||||
property var targetScreen: null
|
|
||||||
|
|
||||||
function openAt(x, screen) {
|
// rectX: Math.max(Style.marginS * scaling, Math.min(parent.width - width - Style.marginS * scaling,
|
||||||
anchorX = x
|
// Math.round(anchorX - width / 2)))
|
||||||
targetScreen = screen
|
// rectY: Settings.data.bar.position === "top" ? Style.marginS * scaling : undefined
|
||||||
isLoaded = true
|
panelContent: Item {
|
||||||
// If the panel is already instantiated, update immediately
|
id: content
|
||||||
if (item) {
|
|
||||||
if (item.anchorX !== undefined)
|
|
||||||
item.anchorX = anchorX
|
|
||||||
if (item.screen !== undefined)
|
|
||||||
item.screen = targetScreen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content: Component {
|
property real cardSpacing: Style.marginL * scaling
|
||||||
NPanel {
|
|
||||||
id: sidePanel
|
|
||||||
|
|
||||||
// Single source of truth for spacing between cards (both axes)
|
anchors.left: parent.left
|
||||||
property real cardSpacing: Style.marginL * scaling
|
anchors.right: parent.right
|
||||||
// X coordinate from the bar to align this panel under
|
anchors.top: parent.top
|
||||||
property real anchorX: root.anchorX
|
anchors.margins: content.cardSpacing
|
||||||
// Ensure this panel attaches to the intended screen
|
implicitHeight: layout.implicitHeight
|
||||||
screen: root.targetScreen
|
|
||||||
|
|
||||||
// Override hide function to animate first
|
// Layout content (not vertically anchored so implicitHeight is valid)
|
||||||
function hide() {
|
ColumnLayout {
|
||||||
// Start hide animation
|
id: layout
|
||||||
panelBackground.scaleValue = 0.8
|
// Use the same spacing value horizontally and vertically
|
||||||
panelBackground.opacityValue = 0.0
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
spacing: content.cardSpacing
|
||||||
|
|
||||||
// Hide after animation completes
|
// Cards (consistent inter-card spacing via ColumnLayout spacing)
|
||||||
hideTimer.start()
|
ProfileCard {// Layout.topMargin: 0
|
||||||
|
// Layout.bottomMargin: 0
|
||||||
|
}
|
||||||
|
WeatherCard {// Layout.topMargin: 0
|
||||||
|
// Layout.bottomMargin: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to NPanel's dismissed signal to handle external close events
|
// Middle section: media + stats column
|
||||||
Connections {
|
RowLayout {
|
||||||
target: sidePanel
|
Layout.fillWidth: true
|
||||||
function onDismissed() {
|
Layout.topMargin: 0
|
||||||
// Start hide animation
|
Layout.bottomMargin: 0
|
||||||
panelBackground.scaleValue = 0.8
|
spacing: content.cardSpacing
|
||||||
panelBackground.opacityValue = 0.0
|
|
||||||
|
|
||||||
// Hide after animation completes
|
// Media card
|
||||||
hideTimer.start()
|
MediaCard {
|
||||||
|
id: mediaCard
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: statsCard.implicitHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// System monitors combined in one card
|
||||||
|
SystemMonitorCard {
|
||||||
|
id: statsCard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also handle visibility changes from external sources
|
// Bottom actions (two grouped rows of round buttons)
|
||||||
onVisibleChanged: {
|
RowLayout {
|
||||||
if (!visible && panelBackground.opacityValue > 0) {
|
Layout.fillWidth: true
|
||||||
// Start hide animation
|
Layout.topMargin: 0
|
||||||
panelBackground.scaleValue = 0.8
|
Layout.bottomMargin: 0
|
||||||
panelBackground.opacityValue = 0.0
|
spacing: content.cardSpacing
|
||||||
|
|
||||||
// Hide after animation completes
|
// Power Profiles switcher
|
||||||
hideTimer.start()
|
PowerProfilesCard {
|
||||||
}
|
spacing: content.cardSpacing
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure panel shows itself once created
|
|
||||||
Component.onCompleted: show()
|
|
||||||
|
|
||||||
// Inline helpers moved to dedicated widgets: NCard and NCircleStat
|
|
||||||
Rectangle {
|
|
||||||
id: panelBackground
|
|
||||||
color: Color.mSurface
|
|
||||||
radius: Style.radiusL * scaling
|
|
||||||
border.color: Color.mOutline
|
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
|
||||||
layer.enabled: true
|
|
||||||
width: 460 * scaling
|
|
||||||
property real innerMargin: sidePanel.cardSpacing
|
|
||||||
// Height scales to content plus vertical padding
|
|
||||||
height: content.implicitHeight + innerMargin * 2
|
|
||||||
// Place the panel relative to the bar based on its position
|
|
||||||
y: Settings.data.bar.position === "top" ? Style.marginS * scaling : undefined
|
|
||||||
anchors {
|
|
||||||
bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
|
|
||||||
bottomMargin: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling + Style.marginS * scaling : undefined
|
|
||||||
}
|
|
||||||
// Center horizontally under the anchorX, clamped to the screen bounds
|
|
||||||
x: Math.max(Style.marginS * scaling, Math.min(parent.width - width - Style.marginS * scaling,
|
|
||||||
Math.round(anchorX - width / 2)))
|
|
||||||
|
|
||||||
// Animation properties
|
|
||||||
property real scaleValue: 0.8
|
|
||||||
property real opacityValue: 0.0
|
|
||||||
|
|
||||||
scale: scaleValue
|
|
||||||
opacity: opacityValue
|
|
||||||
|
|
||||||
// Animate in when component is completed
|
|
||||||
Component.onCompleted: {
|
|
||||||
scaleValue = 1.0
|
|
||||||
opacityValue = 1.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timer to hide panel after animation
|
// Utilities buttons
|
||||||
Timer {
|
UtilitiesCard {
|
||||||
id: hideTimer
|
spacing: content.cardSpacing
|
||||||
interval: Style.animationSlow
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
sidePanel.visible = false
|
|
||||||
sidePanel.dismissed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent closing when clicking in the panel bg
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation behaviors
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationSlow
|
|
||||||
easing.type: Easing.OutExpo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationNormal
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content wrapper to ensure childrenRect drives implicit height
|
|
||||||
Item {
|
|
||||||
id: content
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: panelBackground.innerMargin
|
|
||||||
implicitHeight: layout.implicitHeight
|
|
||||||
|
|
||||||
// Layout content (not vertically anchored so implicitHeight is valid)
|
|
||||||
ColumnLayout {
|
|
||||||
id: layout
|
|
||||||
// Use the same spacing value horizontally and vertically
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
spacing: sidePanel.cardSpacing
|
|
||||||
|
|
||||||
// Cards (consistent inter-card spacing via ColumnLayout spacing)
|
|
||||||
ProfileCard {
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
}
|
|
||||||
WeatherCard {
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middle section: media + stats column
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
spacing: sidePanel.cardSpacing
|
|
||||||
|
|
||||||
// Media card
|
|
||||||
MediaCard {
|
|
||||||
id: mediaCard
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: statsCard.implicitHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// System monitors combined in one card
|
|
||||||
SystemMonitorCard {
|
|
||||||
id: statsCard
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom actions (two grouped rows of round buttons)
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
spacing: sidePanel.cardSpacing
|
|
||||||
|
|
||||||
// Power Profiles switcher
|
|
||||||
PowerProfilesCard {}
|
|
||||||
|
|
||||||
// Utilities buttons
|
|
||||||
UtilitiesCard {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ Singleton {
|
||||||
Process {
|
Process {
|
||||||
id: process
|
id: process
|
||||||
stdinEnabled: true
|
stdinEnabled: true
|
||||||
running: (Settings.data.audio.visualizerType !== "none") && PanelService.sidePanel.isLoaded
|
running: (Settings.data.audio.visualizerType !== "none") && PanelService.sidePanel.active
|
||||||
command: ["cava", "-p", "/dev/stdin"]
|
command: ["cava", "-p", "/dev/stdin"]
|
||||||
onExited: {
|
onExited: {
|
||||||
stdinEnabled = true
|
stdinEnabled = true
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,16 @@ import Quickshell
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
// A ref. to the sidePanel, so it's accessible from other services
|
||||||
|
property var sidePanel: null
|
||||||
|
|
||||||
// Currently opened panel
|
// Currently opened panel
|
||||||
property var openedPanel: null
|
property var openedPanel: null
|
||||||
|
|
||||||
property var sidePanel: null
|
function registerOpen(panel) {
|
||||||
|
if (openedPanel && openedPanel != panel) {
|
||||||
|
openedPanel.close()
|
||||||
|
}
|
||||||
|
openedPanel = panel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,15 @@ Singleton {
|
||||||
// -------------------------------------------
|
// -------------------------------------------
|
||||||
// Manual scaling via Settings
|
// Manual scaling via Settings
|
||||||
function scale(aScreen) {
|
function scale(aScreen) {
|
||||||
return scaleByName(aScreen.name)
|
try {
|
||||||
|
if (aScreen !== undefined && aScreen.name !== undefined) {
|
||||||
|
return scaleByName(aScreen.name)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
//Logger.warn(e)
|
||||||
|
}
|
||||||
|
return 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
function scaleByName(aScreenName) {
|
function scaleByName(aScreenName) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import QtQuick
|
||||||
// Example usage:
|
// Example usage:
|
||||||
// NLoader {
|
// NLoader {
|
||||||
// content: Component {
|
// content: Component {
|
||||||
// NPanel {
|
// YourComponent {
|
||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
|
|
||||||
|
|
@ -17,9 +17,6 @@ Loader {
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
sourceComponent: content
|
sourceComponent: content
|
||||||
|
|
||||||
// onLoaded: {
|
|
||||||
// Logger.log("NLoader", "OnLoaded:", item.toString());
|
|
||||||
// }
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active && item && item.show) {
|
if (active && item && item.show) {
|
||||||
item.show()
|
item.show()
|
||||||
|
|
|
||||||
|
|
@ -4,111 +4,180 @@ import Quickshell.Wayland
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
PanelWindow {
|
Loader {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
active: false
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
readonly property real scaling: ScalingService.scale(screen)
|
readonly property real scaling: ScalingService.scale(screen)
|
||||||
|
property ShellScreen screen
|
||||||
|
|
||||||
property bool showOverlay: Settings.data.general.dimDesktop
|
property Component panelContent: null
|
||||||
property int topMargin: Settings.data.bar.position === "top" ? Style.barHeight * scaling : 0
|
property int rWidth: 1500
|
||||||
property int bottomMargin: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling : 0
|
property int rHeight: 400
|
||||||
// Show dimming if this panel is opened OR if we're in a transition (to prevent flickering)
|
property bool rAnchorCentered: false
|
||||||
property color overlayColor: (showOverlay && (PanelService.openedPanel === root
|
property bool rAnchorLeft: false
|
||||||
|| isTransitioning)) ? Color.applyOpacity(Color.mShadow,
|
property bool rAnchorRight: false
|
||||||
"AA") : Color.transparent
|
|
||||||
property bool isTransitioning: false
|
|
||||||
signal dismissed
|
|
||||||
|
|
||||||
function hide() {
|
// Animation properties
|
||||||
// Clear the panel service when hiding
|
readonly property real originalScale: 0.7
|
||||||
if (PanelService.openedPanel === root) {
|
readonly property real originalOpacity: 0.0
|
||||||
PanelService.openedPanel = null
|
property real scaleValue: originalScale
|
||||||
}
|
property real opacityValue: originalOpacity
|
||||||
isTransitioning = false
|
|
||||||
visible = false
|
|
||||||
root.dismissed()
|
|
||||||
}
|
|
||||||
|
|
||||||
function show() {
|
property alias isClosing: hideTimer.running
|
||||||
// Ensure only one panel is visible at a time using PanelService as ephemeral storage
|
|
||||||
try {
|
|
||||||
if (PanelService.openedPanel && PanelService.openedPanel !== root && PanelService.openedPanel.hide) {
|
|
||||||
// Mark both panels as transitioning to prevent dimming flicker
|
|
||||||
isTransitioning = true
|
|
||||||
PanelService.openedPanel.isTransitioning = true
|
|
||||||
PanelService.openedPanel.hide()
|
|
||||||
// Small delay to ensure smooth transition
|
|
||||||
showTimer.start()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// No previous panel, show immediately
|
|
||||||
PanelService.openedPanel = root
|
|
||||||
visible = true
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
// ignore
|
signal opened
|
||||||
|
signal closed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
function toggle(aScreen) {
|
||||||
|
if (!active || isClosing) {
|
||||||
|
open(aScreen)
|
||||||
|
} else {
|
||||||
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: screen.width
|
// -----------------------------------------
|
||||||
implicitHeight: screen.height
|
function open(aScreen) {
|
||||||
color: visible ? overlayColor : Color.transparent
|
if (aScreen !== null) {
|
||||||
visible: false
|
screen = aScreen
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
|
||||||
|
|
||||||
anchors.top: true
|
|
||||||
anchors.left: true
|
|
||||||
anchors.right: true
|
|
||||||
anchors.bottom: true
|
|
||||||
margins.top: topMargin
|
|
||||||
margins.bottom: bottomMargin
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: root.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationSlow
|
|
||||||
easing.type: Easing.InOutCubic
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case if currently closing/animating
|
||||||
|
if (isClosing) {
|
||||||
|
hideTimer.stop() // in case we were closing
|
||||||
|
scaleValue = 1.0
|
||||||
|
opacityValue = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelService.registerOpen(root)
|
||||||
|
|
||||||
|
active = true
|
||||||
|
root.opened()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
function close() {
|
||||||
|
scaleValue = originalScale
|
||||||
|
opacityValue = originalOpacity
|
||||||
|
hideTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
function closeCompleted() {
|
||||||
|
root.closed()
|
||||||
|
active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// Timer to disable the loader after the close animation is completed
|
||||||
Timer {
|
Timer {
|
||||||
id: showTimer
|
id: hideTimer
|
||||||
interval: 50 // Small delay to ensure smooth transition
|
interval: Style.animationSlow
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
PanelService.openedPanel = root
|
closeCompleted()
|
||||||
isTransitioning = false
|
|
||||||
visible = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
// -----------------------------------------
|
||||||
try {
|
sourceComponent: Component {
|
||||||
if (visible && Settings.openPanel === root)
|
PanelWindow {
|
||||||
Settings.openPanel = null
|
id: panelWindow
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
visible: true
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
// Dim desktop if required
|
||||||
try {
|
color: (root.active && !root.isClosing && Settings.data.general.dimDesktop) ? Color.applyOpacity(
|
||||||
if (!visible) {
|
Color.mShadow,
|
||||||
// Clear panel service when panel becomes invisible
|
"BB") : Color.transparent
|
||||||
if (PanelService.openedPanel === root) {
|
|
||||||
PanelService.openedPanel = null
|
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.namespace: "noctalia-panel"
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationNormal
|
||||||
}
|
}
|
||||||
if (Settings.openPanel === root) {
|
|
||||||
Settings.openPanel = null
|
|
||||||
}
|
|
||||||
isTransitioning = false
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
|
anchors.top: true
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
anchors.bottom: true
|
||||||
|
margins.top: Settings.data.bar.position === "top" ? Style.barHeight * scaling : 0
|
||||||
|
margins.bottom: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling : 0
|
||||||
|
|
||||||
|
// Clicking outside of the rectangle to close
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: panelBackground
|
||||||
|
color: Color.mSurface
|
||||||
|
radius: Style.radiusL * scaling
|
||||||
|
border.color: Color.mOutline
|
||||||
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
|
layer.enabled: true
|
||||||
|
width: rWidth
|
||||||
|
height: rHeight
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
centerIn: rAnchorCentered ? parent : null
|
||||||
|
left: !rAnchorCentered && rAnchorLeft ? parent.left : parent.center
|
||||||
|
right: !rAnchorCentered && rAnchorRight ? parent.right : parent.center
|
||||||
|
top: !rAnchorCentered && (Settings.data.bar.position === "top") ? parent.top : undefined
|
||||||
|
bottom: !rAnchorCentered && (Settings.data.bar.position === "bottom") ? parent.bottom : undefined
|
||||||
|
|
||||||
|
// margins
|
||||||
|
topMargin: !rAnchorCentered && (Settings.data.bar.position === "top") ? Style.marginS * scaling : undefined
|
||||||
|
bottomMargin: !rAnchorCentered
|
||||||
|
&& (Settings.data.bar.position === "bottom") ? Style.marginS * scaling : undefined
|
||||||
|
rightMargin: !rAnchorCentered && rAnchorRight ? Style.marginS * scaling : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
scale: root.scaleValue
|
||||||
|
opacity: root.opacityValue
|
||||||
|
|
||||||
|
// Animate in when component is completed
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.scaleValue = 1.0
|
||||||
|
root.opacityValue = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation behaviors
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationSlow
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationNormal
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: root.panelContent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue