mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Refactor: Panels and Bar background are now drawn separately with Shapes.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property color fillColor: Color.mPrimary
|
||||
property color strokeColor: Color.mOnSurface
|
||||
property int strokeWidth: 0
|
||||
property var values: []
|
||||
property bool vertical: false
|
||||
|
||||
// Minimum signal properties
|
||||
property bool showMinimumSignal: false
|
||||
property real minimumSignalValue: 0.05 // Default to 5% of height
|
||||
|
||||
// Pre compute horizontal mirroring
|
||||
readonly property int valuesCount: values.length
|
||||
readonly property int totalBars: valuesCount * 2
|
||||
readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0
|
||||
|
||||
Repeater {
|
||||
model: root.totalBars
|
||||
|
||||
Rectangle {
|
||||
// The first half of bars are a mirror image (reversed values array).
|
||||
// The second half of bars are in normal order.
|
||||
property int valueIndex: index < root.valuesCount ? root.valuesCount - 1 - index // Mirrored half
|
||||
: index - root.valuesCount // Normal half
|
||||
|
||||
property real rawAmp: root.values[valueIndex]
|
||||
property real amp: (root.showMinimumSignal && rawAmp === 0) ? root.minimumSignalValue : rawAmp
|
||||
|
||||
color: root.fillColor
|
||||
border.color: root.strokeColor
|
||||
border.width: root.strokeWidth
|
||||
antialiasing: true
|
||||
|
||||
width: vertical ? root.width * amp : root.barSlotSize * 0.5
|
||||
height: vertical ? root.barSlotSize * 0.5 : root.height * amp
|
||||
x: vertical ? root.width - width : index * root.barSlotSize + (root.barSlotSize * 0.25)
|
||||
y: vertical ? index * root.barSlotSize + (root.barSlotSize * 0.25) : root.height - height
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property color fillColor: Color.mPrimary
|
||||
property color strokeColor: Color.mOnSurface
|
||||
property int strokeWidth: 0
|
||||
property var values: []
|
||||
property bool vertical: false
|
||||
|
||||
// Minimum signal properties
|
||||
property bool showMinimumSignal: false
|
||||
property real minimumSignalValue: 0.05 // Default to 5% of height
|
||||
|
||||
// Pre-compute mirroring
|
||||
readonly property int valuesCount: values.length
|
||||
readonly property int totalBars: valuesCount * 2
|
||||
readonly property real barSlotSize: totalBars > 0 ? (vertical ? height : width) / totalBars : 0
|
||||
|
||||
readonly property real centerY: height / 2
|
||||
readonly property real centerX: width / 2
|
||||
|
||||
Repeater {
|
||||
model: root.totalBars
|
||||
|
||||
Rectangle {
|
||||
// The first half of bars are a mirror image (reversed values array).
|
||||
// The second half of bars are in normal order.
|
||||
property int valueIndex: index < root.valuesCount ? root.valuesCount - 1 - index // Mirrored half
|
||||
: index - root.valuesCount // Normal half
|
||||
|
||||
property real rawAmp: root.values[valueIndex]
|
||||
property real amp: (root.showMinimumSignal && rawAmp === 0) ? root.minimumSignalValue : rawAmp
|
||||
|
||||
property real barSize: (vertical ? root.width : root.height) * amp
|
||||
|
||||
color: root.fillColor
|
||||
border.color: root.strokeColor
|
||||
border.width: root.strokeWidth
|
||||
antialiasing: true
|
||||
|
||||
width: vertical ? barSize : root.barSlotSize * 0.8
|
||||
height: vertical ? root.barSlotSize * 0.8 : barSize
|
||||
x: vertical ? root.centerX - (barSize / 2) : index * root.barSlotSize + (root.barSlotSize * 0.25)
|
||||
y: vertical ? index * root.barSlotSize + (root.barSlotSize * 0.25) : root.centerY - (barSize / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property color fillColor: Color.mPrimary
|
||||
property color strokeColor: Color.mOnSurface
|
||||
property int strokeWidth: 0
|
||||
property var values: []
|
||||
property bool vertical: false
|
||||
|
||||
// Minimum signal properties
|
||||
property bool showMinimumSignal: false
|
||||
property real minimumSignalValue: 0.05 // Default to 5% of height
|
||||
|
||||
// Redraw when necessary
|
||||
onWidthChanged: canvas.requestPaint()
|
||||
onHeightChanged: canvas.requestPaint()
|
||||
onValuesChanged: canvas.requestPaint()
|
||||
onFillColorChanged: canvas.requestPaint()
|
||||
onStrokeColorChanged: canvas.requestPaint()
|
||||
onShowMinimumSignalChanged: canvas.requestPaint()
|
||||
onMinimumSignalValueChanged: canvas.requestPaint()
|
||||
onVerticalChanged: canvas.requestPaint()
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
|
||||
if (values.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply minimum signal if enabled
|
||||
var processedValues = values.map(function (v) {
|
||||
return (root.showMinimumSignal && v === 0) ? root.minimumSignalValue : v
|
||||
})
|
||||
|
||||
// Create the mirrored values
|
||||
const partToMirror = processedValues.slice(1).reverse()
|
||||
const mirroredValues = partToMirror.concat(processedValues)
|
||||
|
||||
if (mirroredValues.length < 2) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.fillStyle = root.fillColor
|
||||
ctx.strokeStyle = root.strokeColor
|
||||
ctx.lineWidth = root.strokeWidth
|
||||
|
||||
const count = mirroredValues.length
|
||||
|
||||
if (root.vertical) {
|
||||
// Vertical orientation
|
||||
const stepY = height / (count - 1)
|
||||
const centerX = width / 2
|
||||
const amplitude = width / 2
|
||||
|
||||
ctx.beginPath()
|
||||
|
||||
// Draw the left half of the waveform from top to bottom
|
||||
var xOffset = mirroredValues[0] * amplitude
|
||||
ctx.moveTo(centerX - xOffset, 0)
|
||||
|
||||
for (var i = 1; i < count; i++) {
|
||||
const y = i * stepY
|
||||
xOffset = mirroredValues[i] * amplitude
|
||||
const x = centerX - xOffset
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
|
||||
// Draw the right half of the waveform from bottom to top to create a closed shape
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
const y = i * stepY
|
||||
xOffset = mirroredValues[i] * amplitude
|
||||
const x = centerX + xOffset // Mirrored across the center
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
|
||||
ctx.closePath()
|
||||
} else {
|
||||
// Horizontal orientation
|
||||
const stepX = width / (count - 1)
|
||||
const centerY = height / 2
|
||||
const amplitude = height / 2
|
||||
|
||||
ctx.beginPath()
|
||||
|
||||
// Draw the top half of the waveform from left to right
|
||||
var yOffset = mirroredValues[0] * amplitude
|
||||
ctx.moveTo(0, centerY - yOffset)
|
||||
|
||||
for (var i = 1; i < count; i++) {
|
||||
const x = i * stepX
|
||||
yOffset = mirroredValues[i] * amplitude
|
||||
const y = centerY - yOffset
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
|
||||
// Draw the bottom half of the waveform from right to left to create a closed shape
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
const x = i * stepX
|
||||
yOffset = mirroredValues[i] * amplitude
|
||||
const y = centerY + yOffset // Mirrored across the center
|
||||
ctx.lineTo(x, y)
|
||||
}
|
||||
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
// --- Render the path ---
|
||||
if (root.fillColor.a > 0) {
|
||||
ctx.fill()
|
||||
}
|
||||
if (root.strokeWidth > 0) {
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,616 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
|
||||
/**
|
||||
* NFullScreenWindow - Single PanelWindow per screen that manages all panels and the bar
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
required property var barComponent
|
||||
required property var panelComponents
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("NFullScreenWindow", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y)
|
||||
}
|
||||
|
||||
// Debug: Log mask region changes
|
||||
onMaskChanged: {
|
||||
Logger.d("NFullScreenWindow", "Mask changed!")
|
||||
Logger.d("NFullScreenWindow", " Bar region:", barLoader.item?.barRegion)
|
||||
Logger.d("NFullScreenWindow", " Panel count:", panelsRepeater.count)
|
||||
for (var i = 0; i < panelsRepeater.count; i++) {
|
||||
var panelItem = panelsRepeater.itemAt(i)?.item
|
||||
Logger.d("NFullScreenWindow", " 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
|
||||
}
|
||||
}
|
||||
|
||||
function updateMask() {
|
||||
// Build the regions list
|
||||
var regionsList = [barMaskRegion]
|
||||
|
||||
// Add background region if a panel is open
|
||||
// This makes the background clickable (not click-through) so we can detect clicks to close panels
|
||||
if (root.isPanelOpen) {
|
||||
regionsList.push(backgroundMaskRegion)
|
||||
}
|
||||
|
||||
// Add regions for each open panel
|
||||
// Only include panels that are open AND not closing (to allow click-through during close animation)
|
||||
for (var i = 0; i < panelMaskRepeater.count; i++) {
|
||||
var wrapperItem = panelMaskRepeater.itemAt(i)
|
||||
if (wrapperItem && wrapperItem.maskRegion) {
|
||||
var panelItem = wrapperItem.panelItem
|
||||
if (panelItem && panelItem.isPanelOpen && !panelItem.isClosing) {
|
||||
var panelRegion = panelItem.panelRegion
|
||||
// Update the mask region's coordinates from the panel's actual region
|
||||
if (panelRegion) {
|
||||
wrapperItem.maskRegion.x = panelRegion.x
|
||||
wrapperItem.maskRegion.y = panelRegion.y
|
||||
wrapperItem.maskRegion.width = panelRegion.width
|
||||
wrapperItem.maskRegion.height = panelRegion.height
|
||||
regionsList.push(wrapperItem.maskRegion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the mask's regions
|
||||
clickableMask.regions = regionsList
|
||||
}
|
||||
|
||||
// Listen to PanelService to update mask when panels open/close
|
||||
Connections {
|
||||
target: PanelService
|
||||
function onWillOpen() {
|
||||
root.updateMask()
|
||||
}
|
||||
function onDidClose() {
|
||||
// Delay mask update to ensure panel's isPanelOpen is updated first
|
||||
Qt.callLater(() => root.updateMask())
|
||||
}
|
||||
}
|
||||
|
||||
// Background region - for closing panels when clicking outside (separate from mask)
|
||||
Region {
|
||||
id: backgroundMaskRegion
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.width
|
||||
height: root.height
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
|
||||
// Smart mask: 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
|
||||
|
||||
// Regions list is set programmatically in updateMask()
|
||||
// Initially just the bar
|
||||
regions: [barMaskRegion]
|
||||
|
||||
// Bar region - subtract bar area from mask
|
||||
Region {
|
||||
id: barMaskRegion
|
||||
property var barRegion: barLoader.item && barLoader.item.barRegion ? barLoader.item.barRegion : null
|
||||
|
||||
x: barRegion ? barRegion.x : 0
|
||||
y: barRegion ? barRegion.y : 0
|
||||
width: barRegion ? barRegion.width : 0
|
||||
height: barRegion ? barRegion.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
|
||||
// Update mask when bar geometry changes
|
||||
onWidthChanged: Qt.callLater(() => root.updateMask())
|
||||
onHeightChanged: Qt.callLater(() => root.updateMask())
|
||||
}
|
||||
}
|
||||
|
||||
// Container for panel mask regions (created dynamically)
|
||||
Item {
|
||||
id: panelMaskRegions
|
||||
|
||||
// Create a Region for each panel
|
||||
Repeater {
|
||||
id: panelMaskRepeater
|
||||
model: panelsRepeater.count
|
||||
|
||||
delegate: Item {
|
||||
required property int index
|
||||
property var panelItem: panelsRepeater.itemAt(index)?.item
|
||||
property var region: panelItem && panelItem.panelRegion ? panelItem.panelRegion : null
|
||||
|
||||
// The actual mask region as a child
|
||||
property alias maskRegion: panelMask
|
||||
|
||||
Region {
|
||||
id: panelMask
|
||||
// Coordinates are set programmatically in updateMask()
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container for all UI elements
|
||||
Item {
|
||||
id: container
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
||||
// Apply shadow effect
|
||||
layer.enabled: Settings.data.general.enableShadows
|
||||
layer.smooth: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowOpacity: Style.shadowOpacity
|
||||
shadowHorizontalOffset: Style.shadowHorizontalOffset
|
||||
shadowVerticalOffset: Style.shadowVerticalOffset
|
||||
shadowColor: Color.black
|
||||
blur: Style.shadowBlur
|
||||
blurMax: Style.shadowBlurMax
|
||||
}
|
||||
|
||||
// Screen corners (integrated to avoid separate PanelWindow)
|
||||
// Always positioned at actual screen edges
|
||||
Loader {
|
||||
id: screenCornersLoader
|
||||
active: Settings.data.general.showScreenCorners
|
||||
|
||||
anchors.fill: parent
|
||||
z: 1000 // Very high z-index to be on top of everything
|
||||
|
||||
sourceComponent: Item {
|
||||
id: cornersRoot
|
||||
anchors.fill: parent
|
||||
|
||||
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Color.black : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||
property real cornerRadius: Style.screenRadius
|
||||
property real cornerSize: Style.screenRadius
|
||||
|
||||
// Top-left concave corner
|
||||
Canvas {
|
||||
id: topLeftCorner
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: cornersRoot.cornerSize
|
||||
height: cornersRoot.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = Color.white
|
||||
ctx.beginPath()
|
||||
ctx.arc(width, height, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Top-right concave corner
|
||||
Canvas {
|
||||
id: topRightCorner
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: cornersRoot.cornerSize
|
||||
height: cornersRoot.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = Color.white
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, height, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Bottom-left concave corner
|
||||
Canvas {
|
||||
id: bottomLeftCorner
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: cornersRoot.cornerSize
|
||||
height: cornersRoot.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = Color.white
|
||||
ctx.beginPath()
|
||||
ctx.arc(width, 0, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Bottom-right concave corner
|
||||
Canvas {
|
||||
id: bottomRightCorner
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: cornersRoot.cornerSize
|
||||
height: cornersRoot.cornerSize
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = Color.white
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
requestPaint()
|
||||
onHeightChanged: if (available)
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Repaint all corners when color or radius changes
|
||||
onCornerColorChanged: {
|
||||
if (topLeftCorner.available)
|
||||
topLeftCorner.requestPaint()
|
||||
if (topRightCorner.available)
|
||||
topRightCorner.requestPaint()
|
||||
if (bottomLeftCorner.available)
|
||||
bottomLeftCorner.requestPaint()
|
||||
if (bottomRightCorner.available)
|
||||
bottomRightCorner.requestPaint()
|
||||
}
|
||||
|
||||
onCornerRadiusChanged: {
|
||||
if (topLeftCorner.available)
|
||||
topLeftCorner.requestPaint()
|
||||
if (topRightCorner.available)
|
||||
topRightCorner.requestPaint()
|
||||
if (bottomLeftCorner.available)
|
||||
bottomLeftCorner.requestPaint()
|
||||
if (bottomRightCorner.available)
|
||||
bottomRightCorner.requestPaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (as Items, not PanelWindows)
|
||||
Repeater {
|
||||
id: panelsRepeater
|
||||
model: root.panelComponents
|
||||
|
||||
delegate: Loader {
|
||||
id: panelLoader
|
||||
|
||||
// Lazy load panels - only create when first requested
|
||||
// Panel stays loaded once created for faster subsequent opens
|
||||
active: false
|
||||
asynchronous: false
|
||||
sourceComponent: modelData.component
|
||||
|
||||
// Fill the container so panels have proper parent dimensions
|
||||
anchors.fill: parent
|
||||
|
||||
// Panel properties binding
|
||||
property var panelScreen: root.screen
|
||||
property string panelId: modelData.id
|
||||
property int panelZIndex: modelData.zIndex || 50
|
||||
property bool hasBeenRequested: false
|
||||
|
||||
Component.onCompleted: {
|
||||
// Register the loader immediately so PanelService can load it on-demand
|
||||
var objectName = panelId + "-" + (panelScreen?.name || "unknown")
|
||||
PanelService.registerPanelLoader(panelLoader, objectName)
|
||||
}
|
||||
|
||||
// Activate loader when panel is first requested
|
||||
function ensureLoaded() {
|
||||
if (!hasBeenRequested) {
|
||||
Logger.d("NFullScreenWindow", "Loading panel on-demand:", panelId)
|
||||
hasBeenRequested = true
|
||||
active = true
|
||||
}
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
// Set unique objectName per screen BEFORE registration: "calendarPanel-DP-1"
|
||||
item.objectName = panelId + "-" + (panelScreen?.name || "unknown")
|
||||
item.screen = panelScreen
|
||||
PanelService.registerPanel(item)
|
||||
Logger.d("NFullScreenWindow", "Panel loaded with objectName:", item.objectName, "on screen:", panelScreen?.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bar (always on top)
|
||||
Loader {
|
||||
id: barLoader
|
||||
asynchronous: false
|
||||
sourceComponent: root.barComponent
|
||||
// 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("NFullScreenWindow", "Bar loaded:", item !== null)
|
||||
if (item) {
|
||||
Logger.d("NFullScreenWindow", "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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,858 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
|
||||
/**
|
||||
* NPanel for use within NFullScreenWindow
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Screen property provided by NFullScreenWindow
|
||||
property ShellScreen screen: null
|
||||
|
||||
// Edge snapping: if panel is within this distance (in pixels) from a screen edge, snap
|
||||
property real edgeSnapDistance: 50
|
||||
|
||||
property Component panelContent: null
|
||||
|
||||
// Panel size properties
|
||||
property real preferredWidth: 700
|
||||
property real preferredHeight: 900
|
||||
property real preferredWidthRatio
|
||||
property real preferredHeightRatio
|
||||
property color panelBackgroundColor: Color.mSurface
|
||||
property color panelBorderColor: Color.mOutline
|
||||
property var buttonItem: null
|
||||
|
||||
// Anchoring properties
|
||||
property bool panelAnchorHorizontalCenter: false
|
||||
property bool panelAnchorVerticalCenter: false
|
||||
property bool panelAnchorTop: false
|
||||
property bool panelAnchorBottom: false
|
||||
property bool panelAnchorLeft: false
|
||||
property bool panelAnchorRight: false
|
||||
|
||||
// Button position properties
|
||||
property bool useButtonPosition: false
|
||||
property point buttonPosition: Qt.point(0, 0)
|
||||
property int buttonWidth: 0
|
||||
property int buttonHeight: 0
|
||||
|
||||
// Track whether panel is open
|
||||
property bool isPanelOpen: false
|
||||
|
||||
// Animation properties
|
||||
property real animationProgress: 0
|
||||
property bool isClosing: false
|
||||
// Per-panel animation overrides
|
||||
property bool disableScaleAnimation: false
|
||||
property bool disableSlideAnimation: false
|
||||
// If >= 0, use this pixel distance for slide instead of default
|
||||
property int customSlideDistance: -1
|
||||
|
||||
// Keyboard event handlers - override these in specific panels to handle shortcuts
|
||||
// These are called from NFullScreenWindow's centralized shortcuts
|
||||
function onEscapePressed() {
|
||||
close()
|
||||
}
|
||||
function onTabPressed() {}
|
||||
function onShiftTabPressed() {}
|
||||
function onUpPressed() {}
|
||||
function onDownPressed() {}
|
||||
function onLeftPressed() {}
|
||||
function onRightPressed() {}
|
||||
function onReturnPressed() {}
|
||||
function onHomePressed() {}
|
||||
function onEndPressed() {}
|
||||
function onPageUpPressed() {}
|
||||
function onPageDownPressed() {}
|
||||
function onCtrlJPressed() {}
|
||||
function onCtrlKPressed() {}
|
||||
|
||||
Behavior on animationProgress {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuint
|
||||
onRunningChanged: {
|
||||
// When close animation finishes, actually hide the panel
|
||||
if (!running && root.isClosing) {
|
||||
root.isClosing = false
|
||||
root.isPanelOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expose panel region for click-through mask (only when open)
|
||||
readonly property var panelRegion: panelContentContainer.item?.maskRegion || null
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
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
|
||||
|
||||
// Helper to detect if any anchor is explicitly set
|
||||
readonly property bool hasExplicitHorizontalAnchor: panelAnchorHorizontalCenter || panelAnchorLeft || panelAnchorRight
|
||||
readonly property bool hasExplicitVerticalAnchor: panelAnchorVerticalCenter || panelAnchorTop || panelAnchorBottom
|
||||
|
||||
signal opened
|
||||
signal closed
|
||||
|
||||
// Panel visibility and sizing
|
||||
// Keep visible during close animation
|
||||
visible: isPanelOpen || isClosing
|
||||
width: parent ? parent.width : 0
|
||||
height: parent ? parent.height : 0
|
||||
|
||||
// Panel control functions
|
||||
function toggle(buttonItem, buttonName) {
|
||||
if (!isPanelOpen) {
|
||||
open(buttonItem, buttonName)
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
function open(buttonItem, buttonName) {
|
||||
if (!buttonItem && buttonName) {
|
||||
buttonItem = BarService.lookupWidget(buttonName, screen.name)
|
||||
}
|
||||
|
||||
if (buttonItem) {
|
||||
root.buttonItem = buttonItem
|
||||
// Map button position to screen coordinates
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0)
|
||||
root.buttonPosition = Qt.point(buttonPos.x, buttonPos.y)
|
||||
root.buttonWidth = buttonItem.width
|
||||
root.buttonHeight = buttonItem.height
|
||||
root.useButtonPosition = true
|
||||
} else {
|
||||
// No button provided: reset button position mode
|
||||
root.buttonItem = null
|
||||
root.useButtonPosition = false
|
||||
}
|
||||
|
||||
setPosition()
|
||||
isPanelOpen = true
|
||||
animationProgress = 1
|
||||
|
||||
// Notify PanelService
|
||||
PanelService.willOpenPanel(root)
|
||||
|
||||
// Delay the opened signal to ensure content is fully loaded
|
||||
// This ensures Component.onCompleted of the loaded content runs first
|
||||
Qt.callLater(() => {
|
||||
opened()
|
||||
})
|
||||
|
||||
Logger.d("NPanel", "Opened panel", objectName)
|
||||
Logger.d("NPanel", " Root size:", width, "x", height)
|
||||
}
|
||||
|
||||
function close() {
|
||||
// Start close animation
|
||||
isClosing = true
|
||||
animationProgress = 0
|
||||
|
||||
// Notify PanelService immediately
|
||||
PanelService.closedPanel(root)
|
||||
|
||||
// Emit closed signal
|
||||
closed()
|
||||
|
||||
Logger.d("NPanel", "Closing panel with animation", objectName)
|
||||
// isPanelOpen will be set to false when animation completes
|
||||
}
|
||||
|
||||
function setPosition() {// Position calculation will be handled here
|
||||
// For now, panels will be positioned based on anchors
|
||||
}
|
||||
|
||||
// Loader for panel content
|
||||
Loader {
|
||||
id: panelContentContainer
|
||||
anchors.fill: parent
|
||||
// Keep active during close animation
|
||||
active: root.isPanelOpen || root.isClosing
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Screen-dependent attachment properties (moved from root to avoid race condition)
|
||||
// By the time this Loader is active, screen has been assigned by NFullScreenWindow
|
||||
readonly property bool couldAttach: Settings.data.ui.panelsAttachedToBar
|
||||
readonly property bool couldAttachToBar: {
|
||||
if (!Settings.data.ui.panelsAttachedToBar || Settings.data.bar.backgroundOpacity < 1.0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// A panel can only be attached to a bar if there is a bar on that screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "")
|
||||
return result
|
||||
}
|
||||
|
||||
// Effective anchor properties (moved from root, depend on couldAttach)
|
||||
// These are true when:
|
||||
// 1. Explicitly anchored, OR
|
||||
// 2. Using button position and bar is on that edge, OR
|
||||
// 3. Attached to bar with no explicit anchors (default centering behavior)
|
||||
readonly property bool effectivePanelAnchorTop: root.panelAnchorTop || (root.useButtonPosition && root.barPosition === "top") || (couldAttach && !root.hasExplicitVerticalAnchor && root.barPosition === "top" && !root.barIsVertical)
|
||||
readonly property bool effectivePanelAnchorBottom: root.panelAnchorBottom || (root.useButtonPosition && root.barPosition === "bottom") || (couldAttach && !root.hasExplicitVerticalAnchor && root.barPosition === "bottom" && !root.barIsVertical)
|
||||
readonly property bool effectivePanelAnchorLeft: root.panelAnchorLeft || (root.useButtonPosition && root.barPosition === "left") || (couldAttach && !root.hasExplicitHorizontalAnchor && root.barPosition === "left" && root.barIsVertical)
|
||||
readonly property bool effectivePanelAnchorRight: root.panelAnchorRight || (root.useButtonPosition && root.barPosition === "right") || (couldAttach && !root.hasExplicitHorizontalAnchor && root.barPosition === "right" && root.barIsVertical)
|
||||
|
||||
// Expose panelBackground for mask region
|
||||
property alias maskRegion: panelBackground
|
||||
|
||||
// The actual panel background and content
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
NShapedRectangle {
|
||||
id: panelBackground
|
||||
|
||||
backgroundColor: root.panelBackgroundColor
|
||||
|
||||
Behavior on backgroundColor {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// Check if panel has any inverted corners
|
||||
readonly property bool hasInvertedCorners: topLeftInverted || topRightInverted || bottomLeftInverted || bottomRightInverted
|
||||
|
||||
// Determine panel attachment type for animation
|
||||
readonly property bool isAttachedToFloatingBar: hasInvertedCorners && root.barFloating && couldAttach
|
||||
readonly property bool isAttachedToNonFloating: hasInvertedCorners && (!root.barFloating || !couldAttach)
|
||||
readonly property bool isDetached: !hasInvertedCorners
|
||||
|
||||
// Determine closest screen edge to slide from (for full slide animation)
|
||||
readonly property string slideDirection: {
|
||||
if (!isAttachedToNonFloating)
|
||||
return "none"
|
||||
|
||||
// Priority: If panel is touching the bar (but not touching any screen edge), slide from the bar direction
|
||||
// This handles cases where centered panels snap to the bar due to height constraints
|
||||
// If touching screen edges, fall through to the distance-based calculation below
|
||||
// var touchingAnyScreenEdge = touchingLeftEdge || touchingRightEdge || touchingTopEdge || touchingBottomEdge
|
||||
// if (!touchingAnyScreenEdge) {
|
||||
if (touchingTopBar && root.barPosition === "top")
|
||||
return "top"
|
||||
if (touchingBottomBar && root.barPosition === "bottom")
|
||||
return "bottom"
|
||||
if (touchingLeftBar && root.barPosition === "left")
|
||||
return "left"
|
||||
if (touchingRightBar && root.barPosition === "right")
|
||||
return "right"
|
||||
//}
|
||||
|
||||
// Use panel's center point (barycenter) as reference
|
||||
var centerX = x + width / 2
|
||||
var centerY = y + height / 2
|
||||
|
||||
// Calculate actual travel distances (barycenter to screen edge)
|
||||
var travelFromTop = centerY
|
||||
var travelFromBottom = parent.height - centerY
|
||||
var travelFromLeft = centerX
|
||||
var travelFromRight = parent.width - centerX
|
||||
|
||||
// Find minimum travel distance
|
||||
var minTravel = Math.min(travelFromTop, travelFromBottom, travelFromLeft, travelFromRight)
|
||||
|
||||
// Return the direction with least travel distance
|
||||
if (minTravel === travelFromTop)
|
||||
return "top"
|
||||
if (minTravel === travelFromBottom)
|
||||
return "bottom"
|
||||
if (minTravel === travelFromLeft)
|
||||
return "left"
|
||||
if (minTravel === travelFromRight)
|
||||
return "right"
|
||||
return "none"
|
||||
}
|
||||
|
||||
// Animation offset calculation
|
||||
readonly property real slideOffset: {
|
||||
if (root.disableSlideAnimation)
|
||||
return 0
|
||||
if (root.customSlideDistance >= 0) {
|
||||
return (1 - root.animationProgress) * root.customSlideDistance
|
||||
}
|
||||
// Full slide for non-floating attached panels
|
||||
if (isAttachedToNonFloating) {
|
||||
var distance = (slideDirection === "left" || slideDirection === "right") ? width : height
|
||||
return Math.round((1 - root.animationProgress) * distance)
|
||||
}
|
||||
// Small 40px slide for floating bar attached panels
|
||||
if (isAttachedToFloatingBar) {
|
||||
return (1 - root.animationProgress) * 40
|
||||
}
|
||||
// No slide for detached panels
|
||||
return 0
|
||||
}
|
||||
|
||||
// Animation properties
|
||||
opacity: isAttachedToNonFloating ? Math.min(1, root.animationProgress * 5) : root.animationProgress
|
||||
scale: {
|
||||
if (root.disableScaleAnimation)
|
||||
return 1
|
||||
if (isAttachedToNonFloating)
|
||||
return 1 // No scale for full slide animation
|
||||
if (isAttachedToFloatingBar)
|
||||
return 1 // No scale for floating bar (40px slide + opacity only)
|
||||
return (0.9 + root.animationProgress * 0.1) // Scale for detached panels
|
||||
}
|
||||
|
||||
// Transform origin for scale animation
|
||||
transformOrigin: Item.Center
|
||||
|
||||
// Slide animation using transform
|
||||
transform: Translate {
|
||||
x: {
|
||||
// Full slide from nearest edge for non-floating attached panels
|
||||
if (panelBackground.isAttachedToNonFloating) {
|
||||
if (panelBackground.slideDirection === "left")
|
||||
return -panelBackground.slideOffset
|
||||
if (panelBackground.slideDirection === "right")
|
||||
return panelBackground.slideOffset
|
||||
return 0
|
||||
}
|
||||
// Small 40px slide from bar for floating bar attached panels
|
||||
if (panelBackground.isAttachedToFloatingBar) {
|
||||
if (root.barPosition === "left")
|
||||
return -panelBackground.slideOffset
|
||||
if (root.barPosition === "right")
|
||||
return panelBackground.slideOffset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
y: {
|
||||
// Full slide from nearest edge for non-floating attached panels
|
||||
if (panelBackground.isAttachedToNonFloating) {
|
||||
if (panelBackground.slideDirection === "top")
|
||||
return -panelBackground.slideOffset
|
||||
if (panelBackground.slideDirection === "bottom")
|
||||
return panelBackground.slideOffset
|
||||
return 0
|
||||
}
|
||||
// Small 40px slide from bar for floating bar attached panels
|
||||
if (panelBackground.isAttachedToFloatingBar) {
|
||||
if (root.barPosition === "top")
|
||||
return -panelBackground.slideOffset
|
||||
if (root.barPosition === "bottom")
|
||||
return panelBackground.slideOffset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
topLeftRadius: Style.radiusL
|
||||
topRightRadius: Style.radiusL
|
||||
bottomLeftRadius: Style.radiusL
|
||||
bottomRightRadius: Style.radiusL
|
||||
|
||||
// Inverted corners based on bar attachment
|
||||
// When attached to bar AND effectively anchored to it, the corner(s) touching the bar should be inverted
|
||||
// Also invert corners when touching screen edges (non-floating bar only)
|
||||
topLeftInverted: {
|
||||
// Bar attachment: only attach to bar if bar opacity >= 1.0 (no color clash)
|
||||
var barInverted = couldAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && effectivePanelAnchorLeft))
|
||||
// Also detect when panel touches bar edge (e.g., centered panel that's too tall)
|
||||
var barTouchInverted = touchingTopBar || touchingLeftBar
|
||||
// Screen edge contact: can attach to screen edges even if bar opacity < 1.0
|
||||
// For horizontal bars: invert when touching left/right edges
|
||||
// For vertical bars: invert when touching top/bottom edges
|
||||
var edgeInverted = couldAttach && ((touchingLeftEdge && !root.barIsVertical) || (touchingTopEdge && root.barIsVertical))
|
||||
// Also invert when touching screen edge opposite to bar (e.g., bottom edge when bar is at top)
|
||||
var oppositeEdgeInverted = couldAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top")
|
||||
return barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted
|
||||
}
|
||||
topRightInverted: {
|
||||
var barInverted = couldAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && effectivePanelAnchorRight))
|
||||
var barTouchInverted = touchingTopBar || touchingRightBar
|
||||
var edgeInverted = couldAttach && ((touchingRightEdge && !root.barIsVertical) || (touchingTopEdge && root.barIsVertical))
|
||||
var oppositeEdgeInverted = couldAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top")
|
||||
return barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted
|
||||
}
|
||||
bottomLeftInverted: {
|
||||
var barInverted = couldAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && effectivePanelAnchorLeft))
|
||||
var barTouchInverted = touchingBottomBar || touchingLeftBar
|
||||
var edgeInverted = couldAttach && ((touchingLeftEdge && !root.barIsVertical) || (touchingBottomEdge && root.barIsVertical))
|
||||
var oppositeEdgeInverted = couldAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom")
|
||||
return barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted
|
||||
}
|
||||
bottomRightInverted: {
|
||||
var barInverted = couldAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && effectivePanelAnchorRight))
|
||||
var barTouchInverted = touchingBottomBar || touchingRightBar
|
||||
var edgeInverted = couldAttach && ((touchingRightEdge && !root.barIsVertical) || (touchingBottomEdge && root.barIsVertical))
|
||||
var oppositeEdgeInverted = couldAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom")
|
||||
return barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted
|
||||
}
|
||||
|
||||
// Set inverted corner direction based on which edge touches
|
||||
// Bar edges: horizontal bars → horizontal curves, vertical bars → vertical curves
|
||||
// Screen edges: opposite - left/right edges → vertical curves, top/bottom edges → horizontal curves
|
||||
topLeftInvertedDirection: {
|
||||
if (touchingLeftEdge && !root.barIsVertical)
|
||||
return "vertical"
|
||||
if (touchingTopEdge && root.barIsVertical)
|
||||
return "horizontal"
|
||||
return root.barIsVertical ? "vertical" : "horizontal"
|
||||
}
|
||||
topRightInvertedDirection: {
|
||||
if (touchingRightEdge && !root.barIsVertical)
|
||||
return "vertical"
|
||||
if (touchingTopEdge && root.barIsVertical)
|
||||
return "horizontal"
|
||||
return root.barIsVertical ? "vertical" : "horizontal"
|
||||
}
|
||||
bottomLeftInvertedDirection: {
|
||||
if (touchingLeftEdge && !root.barIsVertical)
|
||||
return "vertical"
|
||||
if (touchingBottomEdge && root.barIsVertical)
|
||||
return "horizontal"
|
||||
return root.barIsVertical ? "vertical" : "horizontal"
|
||||
}
|
||||
bottomRightInvertedDirection: {
|
||||
if (touchingRightEdge && !root.barIsVertical)
|
||||
return "vertical"
|
||||
if (touchingBottomEdge && root.barIsVertical)
|
||||
return "horizontal"
|
||||
return root.barIsVertical ? "vertical" : "horizontal"
|
||||
}
|
||||
width: {
|
||||
var w
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentLoader.item && contentLoader.item.contentPreferredWidth !== undefined) {
|
||||
w = contentLoader.item.contentPreferredWidth
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredWidthRatio !== undefined) {
|
||||
w = Math.round(Math.max((parent.width || 1920) * root.preferredWidthRatio, root.preferredWidth))
|
||||
} // Priority 3: Static preferred width
|
||||
else {
|
||||
w = root.preferredWidth
|
||||
}
|
||||
return Math.min(w, (parent.width || 1920) - Style.marginL * 2)
|
||||
}
|
||||
|
||||
height: {
|
||||
var h
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentLoader.item && contentLoader.item.contentPreferredHeight !== undefined) {
|
||||
h = contentLoader.item.contentPreferredHeight
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredHeightRatio !== undefined) {
|
||||
h = Math.round(Math.max((parent.height || 1080) * root.preferredHeightRatio, root.preferredHeight))
|
||||
} // Priority 3: Static preferred height
|
||||
else {
|
||||
h = root.preferredHeight
|
||||
}
|
||||
return Math.min(h, (parent.height || 1080) - Style.barHeight - Style.marginL * 2)
|
||||
}
|
||||
|
||||
// Detect if panel is touching screen edges
|
||||
readonly property bool touchingLeftEdge: couldAttach && x <= 1
|
||||
readonly property bool touchingRightEdge: couldAttach && (x + width) >= (parent.width - 1)
|
||||
readonly property bool touchingTopEdge: couldAttach && y <= 1
|
||||
readonly property bool touchingBottomEdge: couldAttach && (y + height) >= (parent.height - 1)
|
||||
|
||||
// Detect if panel is touching bar edges (for cases where centered panels snap to bar due to height constraints)
|
||||
readonly property bool touchingTopBar: couldAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(y - (root.barMarginV + Style.barHeight)) <= 1
|
||||
readonly property bool touchingBottomBar: couldAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((y + height) - (parent.height - root.barMarginV - Style.barHeight)) <= 1
|
||||
readonly property bool touchingLeftBar: couldAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(x - (root.barMarginH + Style.barHeight)) <= 1
|
||||
readonly property bool touchingRightBar: couldAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((x + width) - (parent.width - root.barMarginH - Style.barHeight)) <= 1
|
||||
|
||||
// Position the panel using explicit x/y coordinates (no anchors)
|
||||
// This makes coordinates clearer for the click-through mask system
|
||||
x: {
|
||||
var calculatedX
|
||||
|
||||
// If useButtonPosition is enabled, align panel X with button
|
||||
// Note: We check useButtonPosition, not buttonItem, because buttonItem may become invalid
|
||||
// after the source panel (e.g., ControlCenter) closes, but we still have valid position data
|
||||
if (root.useButtonPosition && parent.width > 0 && width > 0) {
|
||||
if (root.barIsVertical) {
|
||||
// For vertical bars
|
||||
if (couldAttach) {
|
||||
// Attached panels: align with bar edge (left or right side)
|
||||
if (root.barPosition === "left") {
|
||||
// Panel to the right of left bar
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight
|
||||
// Panel sits right at bar edge (inverted corners align perfectly)
|
||||
calculatedX = leftBarEdge
|
||||
} else {
|
||||
// right
|
||||
// Panel to the left of right bar
|
||||
var rightBarEdge = parent.width - root.barMarginH - Style.barHeight
|
||||
// Panel sits right at bar edge (inverted corners align perfectly)
|
||||
calculatedX = rightBarEdge - width
|
||||
}
|
||||
} else {
|
||||
// Detached panels: center on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - width / 2
|
||||
// Clamp to screen bounds with margins, accounting for bar position
|
||||
var minX = Style.marginL
|
||||
var maxX = parent.width - width - Style.marginL
|
||||
|
||||
// Account for vertical bar taking up space
|
||||
if (root.barPosition === "left") {
|
||||
minX = root.barMarginH + Style.barHeight + Style.marginL
|
||||
} else if (root.barPosition === "right") {
|
||||
maxX = parent.width - root.barMarginH - Style.barHeight - width - Style.marginL
|
||||
}
|
||||
|
||||
panelX = Math.max(minX, Math.min(panelX, maxX))
|
||||
calculatedX = panelX
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars, center panel on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - width / 2
|
||||
// Clamp to bar bounds (account for floating bar margins)
|
||||
// When attached, panel should not extend beyond bar edges
|
||||
if (couldAttach) {
|
||||
// Inverted corners with horizontal direction extend left/right by radiusL
|
||||
// When bar is floating, it also has rounded corners, so we need extra insets
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0
|
||||
var barLeftEdge = root.barMarginH + cornerInset
|
||||
var barRightEdge = parent.width - root.barMarginH - cornerInset
|
||||
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - width))
|
||||
} else {
|
||||
panelX = Math.max(Style.marginL, Math.min(panelX, parent.width - width - Style.marginL))
|
||||
}
|
||||
calculatedX = panelX
|
||||
}
|
||||
} else {
|
||||
|
||||
// Standard anchor positioning
|
||||
Logger.d("NPanel", "Fallback to standard anchor positioning")
|
||||
|
||||
if (root.panelAnchorHorizontalCenter) {
|
||||
Logger.d("NPanel", " -> Horizontal center")
|
||||
// Center horizontally, accounting for bar position and margins
|
||||
if (root.barIsVertical) {
|
||||
// For vertical bars, center in the available space not occupied by the bar
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight
|
||||
var availableWidth = parent.width - availableStart
|
||||
calculatedX = availableStart + (availableWidth - width) / 2
|
||||
} else if (root.barPosition === "right") {
|
||||
var availableWidth = parent.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = (availableWidth - width) / 2
|
||||
} else {
|
||||
// No vertical bar, center normally
|
||||
calculatedX = (parent.width - width) / 2
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars or no bar, center normally
|
||||
calculatedX = (parent.width - width) / 2
|
||||
}
|
||||
} else if (effectivePanelAnchorRight) {
|
||||
Logger.d("NPanel", " -> Right anchor")
|
||||
// When attached to right vertical bar, position next to bar (like useButtonPosition does)
|
||||
if (couldAttach && root.barIsVertical && root.barPosition === "right") {
|
||||
var rightBarEdge = parent.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = rightBarEdge - width
|
||||
} else if (couldAttach) {
|
||||
// Attach to right screen edge
|
||||
calculatedX = parent.width - width
|
||||
} else {
|
||||
// Detached: use margin
|
||||
calculatedX = parent.width - width - Style.marginL
|
||||
}
|
||||
} else if (effectivePanelAnchorLeft) {
|
||||
Logger.d("NPanel", " -> Left anchor")
|
||||
// When attached to left vertical bar, position next to bar (like useButtonPosition does)
|
||||
if (couldAttach && root.barIsVertical && root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight
|
||||
calculatedX = leftBarEdge
|
||||
} else if (couldAttach) {
|
||||
// Attach to left screen edge
|
||||
calculatedX = 0
|
||||
} else {
|
||||
// Detached: use margin
|
||||
calculatedX = Style.marginL
|
||||
}
|
||||
} else {
|
||||
// No explicit anchor: default to centering on bar
|
||||
Logger.d("NPanel", " -> Default to center (no explicit anchor)")
|
||||
|
||||
// For horizontal bars: center horizontally
|
||||
// For vertical bars: center horizontally in available space
|
||||
if (root.barIsVertical) {
|
||||
// Center in the space not occupied by the bar
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight
|
||||
var availableWidth = parent.width - availableStart - Style.marginL
|
||||
calculatedX = availableStart + (availableWidth - width) / 2
|
||||
} else {
|
||||
// right
|
||||
var availableWidth = parent.width - root.barMarginH - Style.barHeight - Style.marginL
|
||||
calculatedX = Style.marginL + (availableWidth - width) / 2
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars: center horizontally, respect bar margins if attached
|
||||
if (couldAttach) {
|
||||
// When attached, respect bar bounds (like button position does)
|
||||
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0)
|
||||
var barLeftEdge = root.barMarginH + cornerInset
|
||||
var barRightEdge = parent.width - root.barMarginH - cornerInset
|
||||
var centeredX = (parent.width - width) / 2
|
||||
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - width))
|
||||
} else {
|
||||
calculatedX = (parent.width - width) / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edge snapping: snap to screen edges if close (only when attached and bar is not floating)
|
||||
if (couldAttach && !root.barFloating && parent.width > 0 && width > 0) {
|
||||
// Calculate edge positions accounting for bar position
|
||||
// For vertical bars (left/right), we need to position panels AFTER the bar, not behind it
|
||||
var leftEdgePos = root.barMarginH
|
||||
if (root.barPosition === "left") {
|
||||
// Bar is on the left, so left edge is after the bar
|
||||
leftEdgePos = root.barMarginH + Style.barHeight
|
||||
}
|
||||
|
||||
var rightEdgePos = parent.width - root.barMarginH - width
|
||||
if (root.barPosition === "right") {
|
||||
// Bar is on the right, so right edge is before the bar
|
||||
rightEdgePos = parent.width - root.barMarginH - Style.barHeight - width
|
||||
}
|
||||
|
||||
// Snap to left edge if within snap distance
|
||||
if (Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = leftEdgePos
|
||||
} // Snap to right edge if within snap distance
|
||||
else if (Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = rightEdgePos
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedX
|
||||
}
|
||||
|
||||
y: {
|
||||
var calculatedY
|
||||
|
||||
// If useButtonPosition is enabled, position panel relative to bar
|
||||
// Note: We check useButtonPosition, not buttonItem, because buttonItem may become invalid
|
||||
// after the source panel (e.g., ControlCenter) closes, but we still have valid position data
|
||||
if (root.useButtonPosition && parent.height > 0 && height > 0) {
|
||||
if (root.barPosition === "top") {
|
||||
// Panel below top bar
|
||||
var topBarEdge = root.barMarginV + Style.barHeight
|
||||
if (couldAttach) {
|
||||
// Panel sits right at bar edge (inverted corners align perfectly)
|
||||
calculatedY = topBarEdge
|
||||
} else {
|
||||
calculatedY = topBarEdge + Style.marginM
|
||||
}
|
||||
} else if (root.barPosition === "bottom") {
|
||||
// Panel above bottom bar
|
||||
var bottomBarEdge = parent.height - root.barMarginV - Style.barHeight
|
||||
if (couldAttach) {
|
||||
// Panel sits right at bar edge (inverted corners align perfectly)
|
||||
calculatedY = bottomBarEdge - height
|
||||
} else {
|
||||
calculatedY = bottomBarEdge - height - Style.marginM
|
||||
}
|
||||
} else if (root.barIsVertical) {
|
||||
// For vertical bars, center panel on button Y position
|
||||
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - height / 2
|
||||
// Clamp to bar bounds (account for floating bar margins and inverted corners)
|
||||
var extraPadding = (couldAttach && root.barFloating) ? Style.radiusL : 0
|
||||
if (couldAttach) {
|
||||
// When attached, panel should not extend beyond bar edges (accounting for floating margins)
|
||||
// Inverted corners with vertical direction extend up/down by radiusL
|
||||
// When bar is floating, it also has rounded corners, so we need extra inset
|
||||
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0)
|
||||
var barTopEdge = root.barMarginV + cornerInset
|
||||
var barBottomEdge = parent.height - root.barMarginV - cornerInset
|
||||
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - height))
|
||||
} else {
|
||||
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, parent.height - height - Style.marginL - extraPadding))
|
||||
}
|
||||
calculatedY = panelY
|
||||
}
|
||||
} else {
|
||||
|
||||
// Standard anchor positioning
|
||||
// Calculate bar offset for detached panels - they should never overlap the bar
|
||||
var barOffset = 0
|
||||
if (!couldAttach) {
|
||||
// For detached panels, always account for bar position
|
||||
if (root.barPosition === "top") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM
|
||||
} else if (root.barPosition === "bottom") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM
|
||||
}
|
||||
} else {
|
||||
// For attached panels with explicit anchors
|
||||
if (effectivePanelAnchorTop && root.barPosition === "top") {
|
||||
// When attached to top bar: position right at bar edge (inverted corners align perfectly)
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
} else if (effectivePanelAnchorBottom && root.barPosition === "bottom") {
|
||||
// When attached to bottom bar: position right at bar edge (inverted corners align perfectly)
|
||||
calculatedY = parent.height - root.barMarginV - Style.barHeight - height
|
||||
} else if (!root.hasExplicitVerticalAnchor) {
|
||||
// No explicit vertical anchor AND attached: default to attaching to bar edge
|
||||
if (root.barPosition === "top") {
|
||||
// Attach to top bar
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
} else if (root.barPosition === "bottom") {
|
||||
// Attach to bottom bar
|
||||
calculatedY = parent.height - root.barMarginV - Style.barHeight - height
|
||||
}
|
||||
// For vertical bars with no explicit anchor: fall through to center vertically on bar
|
||||
}
|
||||
}
|
||||
|
||||
// Continue if calculatedY was already set above, or proceed with anchor positioning
|
||||
if (calculatedY === undefined) {
|
||||
if (root.panelAnchorVerticalCenter) {
|
||||
// Center vertically, accounting for bar position and margins
|
||||
if (!root.barIsVertical) {
|
||||
// For horizontal bars, center in the available space not occupied by the bar
|
||||
if (root.barPosition === "top") {
|
||||
var availableStart = root.barMarginV + Style.barHeight
|
||||
var availableHeight = parent.height - availableStart
|
||||
calculatedY = availableStart + (availableHeight - height) / 2
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var availableHeight = parent.height - root.barMarginV - Style.barHeight
|
||||
calculatedY = (availableHeight - height) / 2
|
||||
} else {
|
||||
// No horizontal bar, center normally
|
||||
calculatedY = (parent.height - height) / 2
|
||||
}
|
||||
} else {
|
||||
// For vertical bars or no bar, center normally
|
||||
calculatedY = (parent.height - height) / 2
|
||||
}
|
||||
} else if (effectivePanelAnchorTop) {
|
||||
// When couldAttach=true, attach to top screen edge; otherwise use margin
|
||||
if (couldAttach) {
|
||||
calculatedY = 0
|
||||
} else {
|
||||
// Only apply barOffset if bar is also at top (to avoid overlapping)
|
||||
var topBarOffset = (root.barPosition === "top") ? barOffset : 0
|
||||
calculatedY = topBarOffset + Style.marginL
|
||||
}
|
||||
} else if (effectivePanelAnchorBottom) {
|
||||
// When couldAttach=true, attach to bottom screen edge; otherwise use margin
|
||||
if (couldAttach) {
|
||||
calculatedY = parent.height - height
|
||||
} else {
|
||||
// Only apply barOffset if bar is also at bottom (to avoid overlapping)
|
||||
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0
|
||||
calculatedY = parent.height - height - bottomBarOffset - Style.marginL
|
||||
}
|
||||
} else {
|
||||
// No explicit vertical anchor
|
||||
if (root.barIsVertical) {
|
||||
// For vertical bars: center vertically on bar
|
||||
if (couldAttach) {
|
||||
// When attached, respect bar bounds
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0
|
||||
var barTopEdge = root.barMarginV + cornerInset
|
||||
var barBottomEdge = parent.height - root.barMarginV - cornerInset
|
||||
var centeredY = (parent.height - height) / 2
|
||||
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - height))
|
||||
} else {
|
||||
calculatedY = (parent.height - height) / 2
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars: attach to bar edge by default
|
||||
if (couldAttach && !root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = parent.height - root.barMarginV - Style.barHeight - height
|
||||
}
|
||||
} else {
|
||||
// Detached or no bar position: use default positioning
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = barOffset + Style.marginL
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = Style.marginL
|
||||
} else {
|
||||
calculatedY = Style.marginL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edge snapping: snap to screen edges if close (only when attached and bar is not floating)
|
||||
if (couldAttach && !root.barFloating && parent.height > 0 && height > 0) {
|
||||
// Calculate edge positions accounting for bar position
|
||||
// For horizontal bars (top/bottom), we need to position panels AFTER the bar, not behind it
|
||||
var topEdgePos = root.barMarginV
|
||||
if (root.barPosition === "top") {
|
||||
// Bar is on the top, so top edge is after the bar
|
||||
topEdgePos = root.barMarginV + Style.barHeight
|
||||
}
|
||||
|
||||
var bottomEdgePos = parent.height - root.barMarginV - height
|
||||
if (root.barPosition === "bottom") {
|
||||
// Bar is on the bottom, so bottom edge is before the bar
|
||||
bottomEdgePos = parent.height - root.barMarginV - Style.barHeight - height
|
||||
}
|
||||
|
||||
// Snap to top edge if within snap distance
|
||||
if (Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = topEdgePos
|
||||
} // Snap to bottom edge if within snap distance
|
||||
else if (Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = bottomEdgePos
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedY
|
||||
}
|
||||
|
||||
// MouseArea to catch clicks on the panel and prevent them from reaching the background
|
||||
// This prevents closing the panel when clicking inside it
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1 // Behind content, but on the panel background
|
||||
onClicked: {
|
||||
|
||||
// Accept and ignore - prevents propagation to background
|
||||
}
|
||||
}
|
||||
|
||||
// Panel content loader
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.panelContent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user