mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-02 02:26:00 +00:00
NPanel: extract PanelWindow to separate reusable component
This commit is contained in:
+2
-347
@@ -126,353 +126,8 @@ Loader {
|
||||
// -----------------------------------------
|
||||
sourceComponent: Component {
|
||||
// PanelWindow has its own screen property inherited of QsWindow
|
||||
PanelWindow {
|
||||
id: panelWindow
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
|
||||
readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0))
|
||||
readonly property real verticalBarWidth: Style.barHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("NPanel", "Opened", root.objectName, "on", screen.name)
|
||||
dimmingOpacity = Style.opacityHeavy
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: panelWindow
|
||||
function onScreenChanged() {
|
||||
root.screen = screen
|
||||
|
||||
// If called from IPC always reposition if screen is updated
|
||||
if (buttonName) {
|
||||
setPosition()
|
||||
}
|
||||
Logger.d("NPanel", "OnScreenChanged", root.screen.name)
|
||||
}
|
||||
}
|
||||
|
||||
visible: true
|
||||
color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent
|
||||
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "noctalia-panel"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
|
||||
Region {
|
||||
id: maskRegion
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
anchors.top: true
|
||||
anchors.left: true
|
||||
anchors.right: true
|
||||
anchors.bottom: true
|
||||
|
||||
// Close any panel with Esc without requiring focus
|
||||
Shortcut {
|
||||
sequences: ["Escape"]
|
||||
enabled: root.active
|
||||
onActivated: root.close()
|
||||
context: Qt.WindowShortcut
|
||||
}
|
||||
|
||||
// Clicking outside of the rectangle to close
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.backgroundClickEnabled
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
// The actual panel's content
|
||||
Rectangle {
|
||||
id: panelBackground
|
||||
color: panelBackgroundColor
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
// Dragging support
|
||||
property bool draggable: root.draggable
|
||||
property bool isDragged: false
|
||||
property real manualX: 0
|
||||
property real manualY: 0
|
||||
width: {
|
||||
var w
|
||||
if (preferredWidthRatio !== undefined) {
|
||||
w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth))
|
||||
} else {
|
||||
w = preferredWidth
|
||||
}
|
||||
// Clamp width so it is never bigger than the screen
|
||||
return Math.min(w, screen?.width - Style.marginL * 2)
|
||||
}
|
||||
height: {
|
||||
var h
|
||||
if (preferredHeightRatio !== undefined) {
|
||||
h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight))
|
||||
} else {
|
||||
h = preferredHeight
|
||||
}
|
||||
|
||||
// Clamp width so it is never bigger than the screen
|
||||
return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2)
|
||||
}
|
||||
|
||||
scale: root.scaleValue
|
||||
x: isDragged ? manualX : calculatedX
|
||||
y: isDragged ? manualY : calculatedY
|
||||
|
||||
// ---------------------------------------------
|
||||
// Does not account for corners are they are negligible and helps keep the code clean.
|
||||
// ---------------------------------------------
|
||||
property real marginTop: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "top":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
property real marginBottom: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
property real marginLeft: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "left":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
property real marginRight: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "right":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
property int calculatedX: {
|
||||
// Priority to fixed anchoring
|
||||
if (panelAnchorHorizontalCenter) {
|
||||
// Center horizontally but respect bar margins
|
||||
var centerX = Math.round((panelWindow.width - panelBackground.width) / 2)
|
||||
var minX = marginLeft
|
||||
var maxX = panelWindow.width - panelBackground.width - marginRight
|
||||
return Math.round(Math.max(minX, Math.min(centerX, maxX)))
|
||||
} else if (panelAnchorLeft) {
|
||||
return marginLeft
|
||||
} else if (panelAnchorRight) {
|
||||
return Math.round(panelWindow.width - panelBackground.width - marginRight)
|
||||
}
|
||||
|
||||
// No fixed anchoring
|
||||
if (isVertical) {
|
||||
// Vertical bar
|
||||
if (barPosition === "right") {
|
||||
// To the left of the right bar
|
||||
return Math.round(panelWindow.width - panelBackground.width - marginRight)
|
||||
} else {
|
||||
// To the right of the left bar
|
||||
return marginLeft
|
||||
}
|
||||
} else {
|
||||
// Horizontal bar
|
||||
if (root.useButtonPosition) {
|
||||
// Position panel relative to button
|
||||
var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2)
|
||||
// Keep panel within screen bounds
|
||||
var maxX = panelWindow.width - panelBackground.width - marginRight
|
||||
var minX = marginLeft
|
||||
return Math.round(Math.max(minX, Math.min(targetX, maxX)))
|
||||
} else {
|
||||
// Fallback to center horizontally
|
||||
return Math.round((panelWindow.width - panelBackground.width) / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
property int calculatedY: {
|
||||
// Priority to fixed anchoring
|
||||
if (panelAnchorVerticalCenter) {
|
||||
// Center vertically but respect bar margins
|
||||
var centerY = Math.round((panelWindow.height - panelBackground.height) / 2)
|
||||
var minY = marginTop
|
||||
var maxY = panelWindow.height - panelBackground.height - marginBottom
|
||||
return Math.round(Math.max(minY, Math.min(centerY, maxY)))
|
||||
} else if (panelAnchorTop) {
|
||||
return marginTop
|
||||
} else if (panelAnchorBottom) {
|
||||
return Math.round(panelWindow.height - panelBackground.height - marginBottom)
|
||||
}
|
||||
|
||||
// No fixed anchoring
|
||||
if (isVertical) {
|
||||
// Vertical bar
|
||||
if (useButtonPosition) {
|
||||
// Position panel relative to button
|
||||
var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2)
|
||||
// Keep panel within screen bounds
|
||||
var maxY = panelWindow.height - panelBackground.height - marginBottom
|
||||
var minY = marginTop
|
||||
return Math.round(Math.max(minY, Math.min(targetY, maxY)))
|
||||
} else {
|
||||
// Fallback to center vertically
|
||||
return Math.round((panelWindow.height - panelBackground.height) / 2)
|
||||
}
|
||||
} else {
|
||||
// Horizontal bar
|
||||
if (barPosition === "bottom") {
|
||||
// Above the bottom bar
|
||||
return Math.round(panelWindow.height - panelBackground.height - marginBottom)
|
||||
} else {
|
||||
// Below the top bar
|
||||
return marginTop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animate in when component is completed
|
||||
Component.onCompleted: {
|
||||
root.scaleValue = 1.0
|
||||
}
|
||||
|
||||
// Reset drag position when panel closes
|
||||
Connections {
|
||||
target: root
|
||||
function onClosed() {
|
||||
panelBackground.isDragged = false
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Animation behaviors
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: panelContentLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.panelContent
|
||||
}
|
||||
|
||||
// Handle drag move on the whole panel area
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
target: null
|
||||
enabled: panelBackground.draggable
|
||||
property real dragStartX: 0
|
||||
property real dragStartY: 0
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
// Capture current position into manual coordinates BEFORE toggling isDragged
|
||||
panelBackground.manualX = panelBackground.x
|
||||
panelBackground.manualY = panelBackground.y
|
||||
dragStartX = panelBackground.x
|
||||
dragStartY = panelBackground.y
|
||||
panelBackground.isDragged = true
|
||||
if (root.enableBackgroundClick)
|
||||
root.disableBackgroundClick()
|
||||
} else {
|
||||
// Keep isDragged true so we continue using the manual x/y after release
|
||||
if (root.enableBackgroundClick)
|
||||
root.enableBackgroundClick()
|
||||
}
|
||||
}
|
||||
onTranslationChanged: {
|
||||
// Proposed new coordinates from fixed drag origin
|
||||
var nx = dragStartX + translation.x
|
||||
var ny = dragStartY + translation.y
|
||||
|
||||
// Calculate gaps so we never overlap the bar on any side
|
||||
var baseGap = Style.marginS
|
||||
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0
|
||||
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0
|
||||
|
||||
var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0)
|
||||
var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0)
|
||||
var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0)
|
||||
var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0)
|
||||
|
||||
// Clamp within screen bounds accounting for insets
|
||||
var maxX = panelWindow.width - panelBackground.width - insetRight
|
||||
var minX = insetLeft
|
||||
var maxY = panelWindow.height - panelBackground.height - insetBottom
|
||||
var minY = insetTop
|
||||
|
||||
panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX)))
|
||||
panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY)))
|
||||
}
|
||||
}
|
||||
|
||||
// Drag indicator border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(2, Style.borderL)
|
||||
radius: parent.radius
|
||||
visible: panelBackground.isDragged && dragHandler.active
|
||||
opacity: 0.8
|
||||
z: 3000
|
||||
|
||||
// Subtle glow effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
radius: parent.radius
|
||||
opacity: 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
NPanelWindow {
|
||||
loggerPrefix: "NPanel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
id: panelWindow
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
|
||||
readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0))
|
||||
readonly property real verticalBarWidth: Style.barHeight
|
||||
|
||||
property string loggerPrefix
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d(loggerPrefix, "Opened", root.objectName, "on", screen.name)
|
||||
dimmingOpacity = Style.opacityHeavy
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: panelWindow
|
||||
function onScreenChanged() {
|
||||
root.screen = screen
|
||||
|
||||
// If called from IPC always reposition if screen is updated
|
||||
if (buttonName) {
|
||||
setPosition()
|
||||
}
|
||||
Logger.d(loggerPrefix, "OnScreenChanged", root.screen.name)
|
||||
}
|
||||
}
|
||||
|
||||
visible: true
|
||||
color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent
|
||||
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "noctalia-panel"
|
||||
WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
|
||||
Region {
|
||||
id: maskRegion
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
anchors.top: true
|
||||
anchors.left: true
|
||||
anchors.right: true
|
||||
anchors.bottom: true
|
||||
|
||||
// Close any panel with Esc without requiring focus
|
||||
Shortcut {
|
||||
sequences: ["Escape"]
|
||||
enabled: root.active
|
||||
onActivated: root.close()
|
||||
context: Qt.WindowShortcut
|
||||
}
|
||||
|
||||
// Clicking outside of the rectangle to close
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.backgroundClickEnabled
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
// The actual panel's content
|
||||
Rectangle {
|
||||
id: panelBackground
|
||||
color: panelBackgroundColor
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
// Dragging support
|
||||
property bool draggable: root.draggable
|
||||
property bool isDragged: false
|
||||
property real manualX: 0
|
||||
property real manualY: 0
|
||||
width: {
|
||||
var w
|
||||
if (preferredWidthRatio !== undefined) {
|
||||
w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth))
|
||||
} else {
|
||||
w = preferredWidth
|
||||
}
|
||||
// Clamp width so it is never bigger than the screen
|
||||
return Math.min(w, screen?.width - Style.marginL * 2)
|
||||
}
|
||||
height: {
|
||||
var h
|
||||
if (preferredHeightRatio !== undefined) {
|
||||
h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight))
|
||||
} else {
|
||||
h = preferredHeight
|
||||
}
|
||||
|
||||
// Clamp width so it is never bigger than the screen
|
||||
return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2)
|
||||
}
|
||||
|
||||
scale: root.scaleValue
|
||||
x: isDragged ? manualX : calculatedX
|
||||
y: isDragged ? manualY : calculatedY
|
||||
|
||||
// ---------------------------------------------
|
||||
// Does not account for corners are they are negligible and helps keep the code clean.
|
||||
// ---------------------------------------------
|
||||
property real marginTop: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "top":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
property real marginBottom: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
property real marginLeft: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "left":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
property real marginRight: {
|
||||
if (!barIsVisible) {
|
||||
return 0
|
||||
}
|
||||
switch (barPosition) {
|
||||
case "right":
|
||||
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
|
||||
default:
|
||||
return Style.marginS
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
property int calculatedX: {
|
||||
// Priority to fixed anchoring
|
||||
if (panelAnchorHorizontalCenter) {
|
||||
// Center horizontally but respect bar margins
|
||||
var centerX = Math.round((panelWindow.width - panelBackground.width) / 2)
|
||||
var minX = marginLeft
|
||||
var maxX = panelWindow.width - panelBackground.width - marginRight
|
||||
return Math.round(Math.max(minX, Math.min(centerX, maxX)))
|
||||
} else if (panelAnchorLeft) {
|
||||
return marginLeft
|
||||
} else if (panelAnchorRight) {
|
||||
return Math.round(panelWindow.width - panelBackground.width - marginRight)
|
||||
}
|
||||
|
||||
// No fixed anchoring
|
||||
if (isVertical) {
|
||||
// Vertical bar
|
||||
if (barPosition === "right") {
|
||||
// To the left of the right bar
|
||||
return Math.round(panelWindow.width - panelBackground.width - marginRight)
|
||||
} else {
|
||||
// To the right of the left bar
|
||||
return marginLeft
|
||||
}
|
||||
} else {
|
||||
// Horizontal bar
|
||||
if (root.useButtonPosition) {
|
||||
// Position panel relative to button
|
||||
var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2)
|
||||
// Keep panel within screen bounds
|
||||
var maxX = panelWindow.width - panelBackground.width - marginRight
|
||||
var minX = marginLeft
|
||||
return Math.round(Math.max(minX, Math.min(targetX, maxX)))
|
||||
} else {
|
||||
// Fallback to center horizontally
|
||||
return Math.round((panelWindow.width - panelBackground.width) / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
property int calculatedY: {
|
||||
// Priority to fixed anchoring
|
||||
if (panelAnchorVerticalCenter) {
|
||||
// Center vertically but respect bar margins
|
||||
var centerY = Math.round((panelWindow.height - panelBackground.height) / 2)
|
||||
var minY = marginTop
|
||||
var maxY = panelWindow.height - panelBackground.height - marginBottom
|
||||
return Math.round(Math.max(minY, Math.min(centerY, maxY)))
|
||||
} else if (panelAnchorTop) {
|
||||
return marginTop
|
||||
} else if (panelAnchorBottom) {
|
||||
return Math.round(panelWindow.height - panelBackground.height - marginBottom)
|
||||
}
|
||||
|
||||
// No fixed anchoring
|
||||
if (isVertical) {
|
||||
// Vertical bar
|
||||
if (useButtonPosition) {
|
||||
// Position panel relative to button
|
||||
var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2)
|
||||
// Keep panel within screen bounds
|
||||
var maxY = panelWindow.height - panelBackground.height - marginBottom
|
||||
var minY = marginTop
|
||||
return Math.round(Math.max(minY, Math.min(targetY, maxY)))
|
||||
} else {
|
||||
// Fallback to center vertically
|
||||
return Math.round((panelWindow.height - panelBackground.height) / 2)
|
||||
}
|
||||
} else {
|
||||
// Horizontal bar
|
||||
if (barPosition === "bottom") {
|
||||
// Above the bottom bar
|
||||
return Math.round(panelWindow.height - panelBackground.height - marginBottom)
|
||||
} else {
|
||||
// Below the top bar
|
||||
return marginTop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animate in when component is completed
|
||||
Component.onCompleted: {
|
||||
root.scaleValue = 1.0
|
||||
}
|
||||
|
||||
// Reset drag position when panel closes
|
||||
Connections {
|
||||
target: root
|
||||
function onClosed() {
|
||||
panelBackground.isDragged = false
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Animation behaviors
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: panelContentLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.panelContent
|
||||
}
|
||||
|
||||
// Handle drag move on the whole panel area
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
target: null
|
||||
enabled: panelBackground.draggable
|
||||
property real dragStartX: 0
|
||||
property real dragStartY: 0
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
// Capture current position into manual coordinates BEFORE toggling isDragged
|
||||
panelBackground.manualX = panelBackground.x
|
||||
panelBackground.manualY = panelBackground.y
|
||||
dragStartX = panelBackground.x
|
||||
dragStartY = panelBackground.y
|
||||
panelBackground.isDragged = true
|
||||
if (root.enableBackgroundClick)
|
||||
root.disableBackgroundClick()
|
||||
} else {
|
||||
// Keep isDragged true so we continue using the manual x/y after release
|
||||
if (root.enableBackgroundClick)
|
||||
root.enableBackgroundClick()
|
||||
}
|
||||
}
|
||||
onTranslationChanged: {
|
||||
// Proposed new coordinates from fixed drag origin
|
||||
var nx = dragStartX + translation.x
|
||||
var ny = dragStartY + translation.y
|
||||
|
||||
// Calculate gaps so we never overlap the bar on any side
|
||||
var baseGap = Style.marginS
|
||||
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0
|
||||
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0
|
||||
|
||||
var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0)
|
||||
var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0)
|
||||
var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0)
|
||||
var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0)
|
||||
|
||||
// Clamp within screen bounds accounting for insets
|
||||
var maxX = panelWindow.width - panelBackground.width - insetRight
|
||||
var minX = insetLeft
|
||||
var maxY = panelWindow.height - panelBackground.height - insetBottom
|
||||
var minY = insetTop
|
||||
|
||||
panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX)))
|
||||
panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY)))
|
||||
}
|
||||
}
|
||||
|
||||
// Drag indicator border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(2, Style.borderL)
|
||||
radius: parent.radius
|
||||
visible: panelBackground.isDragged && dragHandler.active
|
||||
opacity: 0.8
|
||||
z: 3000
|
||||
|
||||
// Subtle glow effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderS)
|
||||
radius: parent.radius
|
||||
opacity: 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user