mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
ff6bc083fd
+ Don't hog the shortcuts unless they are defined, this bring back support for the Return key as submit in NTextInput.
1154 lines
50 KiB
QML
1154 lines
50 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import qs.Commons
|
|
import qs.Services.UI
|
|
|
|
/**
|
|
* SmartPanel for use within MainScreen
|
|
*/
|
|
Item {
|
|
id: root
|
|
|
|
// Screen property provided by MainScreen
|
|
property ShellScreen screen: null
|
|
|
|
// Panel content: Text, icons, etc...
|
|
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
|
|
property bool forceAttachToBar: false
|
|
|
|
// 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
|
|
|
|
// Edge snapping: if panel is within this distance (in pixels) from a screen edge, snap
|
|
property real edgeSnapDistance: 50
|
|
|
|
// Track whether panel is open
|
|
property bool isPanelOpen: false
|
|
|
|
// Track actual visibility (delayed until content is loaded and sized)
|
|
property bool isPanelVisible: false
|
|
|
|
// Track size animation completion for sequential opacity animation
|
|
property bool sizeAnimationComplete: false
|
|
|
|
// Derived state: track opening transition
|
|
readonly property bool isOpening: isPanelVisible && !isClosing && !sizeAnimationComplete
|
|
|
|
// Track close animation state: fade opacity first, then shrink size
|
|
property bool isClosing: false
|
|
property bool opacityFadeComplete: false
|
|
property bool closeFinalized: false // Prevent double-finalization
|
|
|
|
// Safety: Watchdog timers to prevent stuck states
|
|
property bool closeWatchdogActive: false
|
|
property bool openWatchdogActive: false
|
|
|
|
// Close with escape key
|
|
property bool closeWithEscape: true
|
|
|
|
property bool exclusiveKeyboard: true
|
|
|
|
// Keyboard event handler
|
|
// These are called from MainScreen's centralized shortcuts
|
|
// override these in specific panels to handle shortcuts
|
|
function onEscapePressed() {
|
|
if (closeWithEscape)
|
|
close();
|
|
}
|
|
|
|
// Expose panel region for background rendering
|
|
readonly property var panelRegion: panelContent.geometryPlaceholder
|
|
|
|
readonly property string barPosition: Settings.data.bar.position
|
|
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
|
|
readonly property bool barFloating: Settings.data.bar.floating
|
|
readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
|
readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
|
|
|
// Check if bar should be visible on this screen
|
|
readonly property bool barShouldShow: {
|
|
if (!BarService.isVisible)
|
|
return false;
|
|
var monitors = Settings.data.bar.monitors || [];
|
|
var screenName = screen?.name || "";
|
|
return monitors.length === 0 || monitors.includes(screenName);
|
|
}
|
|
|
|
// Helper to detect if any anchor is explicitly set
|
|
readonly property bool hasExplicitHorizontalAnchor: panelAnchorHorizontalCenter || panelAnchorLeft || panelAnchorRight
|
|
readonly property bool hasExplicitVerticalAnchor: panelAnchorVerticalCenter || panelAnchorTop || panelAnchorBottom
|
|
|
|
// Effective anchor properties (depend on allowAttach)
|
|
// 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: panelAnchorTop || (useButtonPosition && barPosition === "top") || (panelContent.allowAttach && !hasExplicitVerticalAnchor && barPosition === "top" && !barIsVertical)
|
|
readonly property bool effectivePanelAnchorBottom: panelAnchorBottom || (useButtonPosition && barPosition === "bottom") || (panelContent.allowAttach && !hasExplicitVerticalAnchor && barPosition === "bottom" && !barIsVertical)
|
|
readonly property bool effectivePanelAnchorLeft: panelAnchorLeft || (useButtonPosition && barPosition === "left") || (panelContent.allowAttach && !hasExplicitHorizontalAnchor && barPosition === "left" && barIsVertical)
|
|
readonly property bool effectivePanelAnchorRight: panelAnchorRight || (useButtonPosition && barPosition === "right") || (panelContent.allowAttach && !hasExplicitHorizontalAnchor && barPosition === "right" && barIsVertical)
|
|
|
|
signal opened
|
|
signal closed
|
|
|
|
Connections {
|
|
target: Style
|
|
|
|
function onUiScaleRatioChanged() {
|
|
if (root.isPanelOpen && root.isPanelVisible) {
|
|
root.setPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Panel visibility and sizing
|
|
visible: isPanelVisible
|
|
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;
|
|
}
|
|
|
|
// Set isPanelOpen to trigger content loading, but don't show yet
|
|
isPanelOpen = true;
|
|
|
|
// Notify PanelService
|
|
PanelService.willOpenPanel(root);
|
|
|
|
// Position and visibility will be set by Loader.onLoaded
|
|
// This ensures no flicker from default size to content size
|
|
}
|
|
|
|
function close() {
|
|
// Start close sequence: fade opacity first
|
|
isClosing = true;
|
|
sizeAnimationComplete = false;
|
|
closeFinalized = false;
|
|
|
|
// Stop the open animation timer if it's still running
|
|
opacityTrigger.stop();
|
|
openWatchdogActive = false;
|
|
openWatchdogTimer.stop();
|
|
|
|
// Start close watchdog timer
|
|
closeWatchdogActive = true;
|
|
closeWatchdogTimer.restart();
|
|
|
|
// If opacity is already 0 (closed during open animation before fade-in),
|
|
// skip directly to size animation
|
|
if (root.opacity === 0.0) {
|
|
opacityFadeComplete = true;
|
|
} else {
|
|
opacityFadeComplete = false;
|
|
}
|
|
|
|
// Opacity will fade out, then size will shrink, then finalizeClose() will complete
|
|
Logger.d("SmartPanel", "Closing panel", objectName);
|
|
}
|
|
|
|
function finalizeClose() {
|
|
// Prevent double-finalization
|
|
if (root.closeFinalized) {
|
|
Logger.w("SmartPanel", "finalizeClose called but already finalized - ignoring", objectName);
|
|
return;
|
|
}
|
|
|
|
// Complete the close sequence after animations finish
|
|
root.closeFinalized = true;
|
|
root.closeWatchdogActive = false;
|
|
closeWatchdogTimer.stop();
|
|
|
|
root.isPanelVisible = false;
|
|
root.isPanelOpen = false;
|
|
root.isClosing = false;
|
|
root.opacityFadeComplete = false;
|
|
|
|
// Reset dimensionsInitialized for next opening
|
|
panelBackground.dimensionsInitialized = false;
|
|
|
|
PanelService.closedPanel(root);
|
|
closed();
|
|
|
|
Logger.d("SmartPanel", "Panel close finalized", objectName);
|
|
}
|
|
|
|
function setPosition() {
|
|
// Don't calculate position if parent dimensions aren't available yet
|
|
// This prevents centering around (0,0) when width/height are still 0
|
|
if (!root.width || !root.height) {
|
|
Logger.d("SmartPanel", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height);
|
|
// Retry on next frame when dimensions should be available
|
|
Qt.callLater(setPosition);
|
|
return;
|
|
}
|
|
|
|
// Calculate panel dimensions first (needed for positioning)
|
|
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(root.width * root.preferredWidthRatio, root.preferredWidth));
|
|
} // Priority 3: Static preferred width
|
|
else {
|
|
w = root.preferredWidth;
|
|
}
|
|
var panelWidth = Math.min(w, root.width - Style.marginL * 2);
|
|
|
|
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(root.height * root.preferredHeightRatio, root.preferredHeight));
|
|
} // Priority 3: Static preferred height
|
|
else {
|
|
h = root.preferredHeight;
|
|
}
|
|
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2);
|
|
|
|
// Update panelBackground target size (will be animated)
|
|
panelBackground.targetWidth = panelWidth;
|
|
panelBackground.targetHeight = panelHeight;
|
|
|
|
// Calculate position
|
|
var calculatedX;
|
|
var calculatedY;
|
|
|
|
// ===== X POSITIONING =====
|
|
if (root.useButtonPosition && root.width > 0 && panelWidth > 0) {
|
|
if (root.barIsVertical) {
|
|
// For vertical bars
|
|
if (panelContent.allowAttach) {
|
|
// Attached panels: align with bar edge (left or right side)
|
|
if (root.barPosition === "left") {
|
|
var leftBarEdge = root.barMarginH + Style.barHeight;
|
|
calculatedX = leftBarEdge;
|
|
} else {
|
|
// right
|
|
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
|
calculatedX = rightBarEdge - panelWidth;
|
|
}
|
|
} else {
|
|
// Detached panels: center on button X position
|
|
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
|
var minX = Style.marginL;
|
|
var maxX = root.width - panelWidth - 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 = root.width - root.barMarginH - Style.barHeight - panelWidth - 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 - panelWidth / 2;
|
|
if (panelContent.allowAttach) {
|
|
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
|
var barLeftEdge = root.barMarginH + cornerInset;
|
|
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
|
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth));
|
|
} else {
|
|
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL));
|
|
}
|
|
calculatedX = panelX;
|
|
}
|
|
} else {
|
|
// Standard anchor positioning
|
|
if (root.panelAnchorHorizontalCenter) {
|
|
if (root.barIsVertical) {
|
|
if (root.barPosition === "left") {
|
|
var availableStart = root.barMarginH + Style.barHeight;
|
|
var availableWidth = root.width - availableStart;
|
|
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
|
} else if (root.barPosition === "right") {
|
|
var availableWidth = root.width - root.barMarginH - Style.barHeight;
|
|
calculatedX = (availableWidth - panelWidth) / 2;
|
|
} else {
|
|
calculatedX = (root.width - panelWidth) / 2;
|
|
}
|
|
} else {
|
|
calculatedX = (root.width - panelWidth) / 2;
|
|
}
|
|
} else if (root.effectivePanelAnchorRight) {
|
|
if (panelContent.allowAttach && root.barIsVertical && root.barPosition === "right") {
|
|
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
|
calculatedX = rightBarEdge - panelWidth;
|
|
} else if (panelContent.allowAttach) {
|
|
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
|
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
|
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
|
var rightCornerInset = Style.radiusL * 2;
|
|
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
|
|
} else {
|
|
calculatedX = root.width - panelWidth;
|
|
}
|
|
} else {
|
|
calculatedX = root.width - panelWidth - Style.marginL;
|
|
}
|
|
} else if (root.effectivePanelAnchorLeft) {
|
|
if (panelContent.allowAttach && root.barIsVertical && root.barPosition === "left") {
|
|
var leftBarEdge = root.barMarginH + Style.barHeight;
|
|
calculatedX = leftBarEdge;
|
|
} else if (panelContent.allowAttach) {
|
|
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
|
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
|
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
|
var leftCornerInset = Style.radiusL * 2;
|
|
calculatedX = root.barMarginH + leftCornerInset;
|
|
} else {
|
|
calculatedX = 0;
|
|
}
|
|
} else {
|
|
calculatedX = Style.marginL;
|
|
}
|
|
} else {
|
|
// No explicit anchor: default to centering on bar
|
|
if (root.barIsVertical) {
|
|
if (root.barPosition === "left") {
|
|
var availableStart = root.barMarginH + Style.barHeight;
|
|
var availableWidth = root.width - availableStart - Style.marginL;
|
|
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
|
} else {
|
|
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL;
|
|
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
|
|
}
|
|
} else {
|
|
if (panelContent.allowAttach) {
|
|
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0);
|
|
var barLeftEdge = root.barMarginH + cornerInset;
|
|
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
|
var centeredX = (root.width - panelWidth) / 2;
|
|
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
|
|
} else {
|
|
calculatedX = (root.width - panelWidth) / 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Edge snapping for X
|
|
if (panelContent.allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
|
|
var leftEdgePos = root.barMarginH;
|
|
if (root.barPosition === "left") {
|
|
leftEdgePos = root.barMarginH + Style.barHeight;
|
|
}
|
|
|
|
var rightEdgePos = root.width - root.barMarginH - panelWidth;
|
|
if (root.barPosition === "right") {
|
|
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth;
|
|
}
|
|
|
|
// Only snap to left edge if panel is actually meant to be at left (or no explicit anchor)
|
|
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left");
|
|
// Only snap to right edge if panel is actually meant to be at right (or no explicit anchor)
|
|
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right");
|
|
|
|
if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
|
|
calculatedX = leftEdgePos;
|
|
} else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
|
|
calculatedX = rightEdgePos;
|
|
}
|
|
}
|
|
|
|
// ===== Y POSITIONING =====
|
|
if (root.useButtonPosition && root.height > 0 && panelHeight > 0) {
|
|
if (root.barPosition === "top") {
|
|
var topBarEdge = root.barMarginV + Style.barHeight;
|
|
if (panelContent.allowAttach) {
|
|
calculatedY = topBarEdge;
|
|
} else {
|
|
calculatedY = topBarEdge + Style.marginM;
|
|
}
|
|
} else if (root.barPosition === "bottom") {
|
|
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight;
|
|
if (panelContent.allowAttach) {
|
|
calculatedY = bottomBarEdge - panelHeight;
|
|
} else {
|
|
calculatedY = bottomBarEdge - panelHeight - Style.marginM;
|
|
}
|
|
} else if (root.barIsVertical) {
|
|
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2;
|
|
var extraPadding = (panelContent.allowAttach && root.barFloating) ? Style.radiusL : 0;
|
|
if (panelContent.allowAttach) {
|
|
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0);
|
|
var barTopEdge = root.barMarginV + cornerInset;
|
|
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
|
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight));
|
|
} else {
|
|
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding));
|
|
}
|
|
calculatedY = panelY;
|
|
}
|
|
} else {
|
|
// Standard anchor positioning
|
|
var barOffset = 0;
|
|
if (!panelContent.allowAttach) {
|
|
if (root.barPosition === "top") {
|
|
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
|
} else if (root.barPosition === "bottom") {
|
|
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
|
}
|
|
} else {
|
|
if (root.effectivePanelAnchorTop && root.barPosition === "top") {
|
|
calculatedY = root.barMarginV + Style.barHeight;
|
|
} else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") {
|
|
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
|
} else if (!root.hasExplicitVerticalAnchor) {
|
|
if (root.barPosition === "top") {
|
|
calculatedY = root.barMarginV + Style.barHeight;
|
|
} else if (root.barPosition === "bottom") {
|
|
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (calculatedY === undefined) {
|
|
if (root.panelAnchorVerticalCenter) {
|
|
if (!root.barIsVertical) {
|
|
if (root.barPosition === "top") {
|
|
var availableStart = root.barMarginV + Style.barHeight;
|
|
var availableHeight = root.height - availableStart;
|
|
calculatedY = availableStart + (availableHeight - panelHeight) / 2;
|
|
} else if (root.barPosition === "bottom") {
|
|
var availableHeight = root.height - root.barMarginV - Style.barHeight;
|
|
calculatedY = (availableHeight - panelHeight) / 2;
|
|
} else {
|
|
calculatedY = (root.height - panelHeight) / 2;
|
|
}
|
|
} else {
|
|
calculatedY = (root.height - panelHeight) / 2;
|
|
}
|
|
} else if (root.effectivePanelAnchorTop) {
|
|
if (panelContent.allowAttach) {
|
|
calculatedY = 0;
|
|
} else {
|
|
var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
|
|
calculatedY = topBarOffset + Style.marginL;
|
|
}
|
|
} else if (root.effectivePanelAnchorBottom) {
|
|
if (panelContent.allowAttach) {
|
|
calculatedY = root.height - panelHeight;
|
|
} else {
|
|
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
|
|
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
|
|
}
|
|
} else {
|
|
if (root.barIsVertical) {
|
|
if (panelContent.allowAttach) {
|
|
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
|
var barTopEdge = root.barMarginV + cornerInset;
|
|
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
|
var centeredY = (root.height - panelHeight) / 2;
|
|
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
|
|
} else {
|
|
calculatedY = (root.height - panelHeight) / 2;
|
|
}
|
|
} else {
|
|
if (panelContent.allowAttach && !root.barIsVertical) {
|
|
if (root.barPosition === "top") {
|
|
calculatedY = root.barMarginV + Style.barHeight;
|
|
} else if (root.barPosition === "bottom") {
|
|
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
|
}
|
|
} else {
|
|
if (root.barPosition === "top") {
|
|
calculatedY = barOffset + Style.marginL;
|
|
} else if (root.barPosition === "bottom") {
|
|
calculatedY = Style.marginL;
|
|
} else {
|
|
calculatedY = Style.marginL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Edge snapping for Y
|
|
if (panelContent.allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
|
|
var topEdgePos = root.barMarginV;
|
|
if (root.barPosition === "top") {
|
|
topEdgePos = root.barMarginV + Style.barHeight;
|
|
}
|
|
|
|
var bottomEdgePos = root.height - root.barMarginV - panelHeight;
|
|
if (root.barPosition === "bottom") {
|
|
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
|
}
|
|
|
|
// Only snap to top edge if panel is actually meant to be at top (or no explicit anchor)
|
|
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top");
|
|
// Only snap to bottom edge if panel is actually meant to be at bottom (or no explicit anchor)
|
|
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom");
|
|
|
|
if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
|
|
calculatedY = topEdgePos;
|
|
} else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
|
|
calculatedY = bottomEdgePos;
|
|
}
|
|
}
|
|
|
|
// Apply calculated positions (set targets for animation)
|
|
panelBackground.targetX = calculatedX;
|
|
panelBackground.targetY = calculatedY;
|
|
|
|
Logger.d("SmartPanel", "Position calculated:", calculatedX, calculatedY);
|
|
Logger.d("SmartPanel", " Panel size:", panelWidth, "x", panelHeight);
|
|
Logger.d("SmartPanel", " Parent size:", root.width, "x", root.height);
|
|
}
|
|
|
|
// Watch for changes in content-driven sizes and update position
|
|
Connections {
|
|
target: contentLoader.item
|
|
ignoreUnknownSignals: true
|
|
|
|
function onContentPreferredWidthChanged() {
|
|
if (root.isPanelOpen && root.isPanelVisible) {
|
|
root.setPosition();
|
|
}
|
|
}
|
|
|
|
function onContentPreferredHeightChanged() {
|
|
if (root.isPanelOpen && root.isPanelVisible) {
|
|
root.setPosition();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Opacity animation
|
|
// Opening: fade in after size animation reaches 75%
|
|
// Closing: fade out immediately
|
|
opacity: {
|
|
if (isClosing)
|
|
return 0.0; // Fade out when closing
|
|
if (isPanelVisible && sizeAnimationComplete)
|
|
return 1.0; // Fade in when opening
|
|
return 0.0;
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
id: opacityAnimation
|
|
duration: root.isClosing ? Style.animationFaster : Style.animationFast
|
|
easing.type: Easing.OutQuad
|
|
|
|
onRunningChanged: {
|
|
// Safety: If animation didn't run (zero duration), handle immediately
|
|
if (!running && duration === 0) {
|
|
if (root.isClosing && root.opacity === 0.0) {
|
|
root.opacityFadeComplete = true;
|
|
var shouldFinalizeNow = panelContent.geometryPlaceholder && !panelContent.geometryPlaceholder.shouldAnimateWidth && !panelContent.geometryPlaceholder.shouldAnimateHeight;
|
|
if (shouldFinalizeNow) {
|
|
Logger.d("SmartPanel", "Zero-duration opacity + no size animation - finalizing", root.objectName);
|
|
Qt.callLater(root.finalizeClose);
|
|
}
|
|
} else if (root.isPanelVisible && root.opacity === 1.0) {
|
|
// Open completed with zero duration
|
|
root.openWatchdogActive = false;
|
|
openWatchdogTimer.stop();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// When opacity fade completes during close, trigger size animation
|
|
if (!running && root.isClosing && root.opacity === 0.0) {
|
|
root.opacityFadeComplete = true;
|
|
// If no size animation will run (centered attached panels only), finalize immediately
|
|
// Detached panels (allowAttach === false) should always animate from top
|
|
var shouldFinalizeNow = panelContent.geometryPlaceholder && !panelContent.geometryPlaceholder.shouldAnimateWidth && !panelContent.geometryPlaceholder.shouldAnimateHeight;
|
|
if (shouldFinalizeNow) {
|
|
Logger.d("SmartPanel", "No animation - finalizing immediately", root.objectName);
|
|
Qt.callLater(root.finalizeClose);
|
|
} else {
|
|
Logger.d("SmartPanel", "Animation will run - waiting for size animation", root.objectName, "shouldAnimateHeight:", panelContent.geometryPlaceholder.shouldAnimateHeight, "shouldAnimateWidth:", panelContent.geometryPlaceholder.shouldAnimateWidth);
|
|
}
|
|
} // When opacity fade completes during open, stop watchdog
|
|
else if (!running && root.isPanelVisible && root.opacity === 1.0) {
|
|
root.openWatchdogActive = false;
|
|
openWatchdogTimer.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Timer to trigger opacity fade at 50% of size animation
|
|
Timer {
|
|
id: opacityTrigger
|
|
interval: Style.animationNormal * 0.5
|
|
repeat: false
|
|
onTriggered: {
|
|
if (root.isPanelVisible) {
|
|
root.sizeAnimationComplete = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Watchdog timer for open sequence (safety mechanism)
|
|
Timer {
|
|
id: openWatchdogTimer
|
|
interval: Style.animationNormal * 3 // 3x normal animation time
|
|
repeat: false
|
|
onTriggered: {
|
|
if (root.openWatchdogActive) {
|
|
Logger.w("SmartPanel", "Open watchdog timeout - forcing panel visible state", root.objectName);
|
|
root.openWatchdogActive = false;
|
|
// Force completion of open sequence
|
|
if (root.isPanelOpen && !root.isPanelVisible) {
|
|
root.isPanelVisible = true;
|
|
root.sizeAnimationComplete = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Watchdog timer for close sequence (safety mechanism)
|
|
Timer {
|
|
id: closeWatchdogTimer
|
|
interval: Style.animationFast * 3 // 3x fast animation time
|
|
repeat: false
|
|
onTriggered: {
|
|
if (root.closeWatchdogActive && !root.closeFinalized) {
|
|
Logger.w("SmartPanel", "Close watchdog timeout - forcing panel close", root.objectName);
|
|
// Force finalization
|
|
Qt.callLater(root.finalizeClose);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------
|
|
// Panel Content
|
|
Item {
|
|
id: panelContent
|
|
anchors.fill: parent
|
|
|
|
// Screen-dependent attachment properties
|
|
// Allow panel content to override allowAttach (e.g., plugin panels)
|
|
readonly property bool allowAttach: {
|
|
if (contentLoader.item && contentLoader.item.allowAttach !== undefined) {
|
|
return contentLoader.item.allowAttach;
|
|
}
|
|
return Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar;
|
|
}
|
|
readonly property bool allowAttachToBar: {
|
|
if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar) || 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;
|
|
}
|
|
|
|
// Edge detection - detect if panel is touching screen edges
|
|
readonly property bool touchingLeftEdge: allowAttach && panelBackground.x <= 1
|
|
readonly property bool touchingRightEdge: allowAttach && (panelBackground.x + panelBackground.width) >= (root.width - 1)
|
|
readonly property bool touchingTopEdge: allowAttach && panelBackground.y <= 1
|
|
readonly property bool touchingBottomEdge: allowAttach && (panelBackground.y + panelBackground.height) >= (root.height - 1)
|
|
|
|
// Bar edge detection - detect if panel is touching bar edges (for cases where centered panels snap to bar due to height constraints)
|
|
readonly property bool touchingTopBar: allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.y - (root.barMarginV + Style.barHeight)) <= 1
|
|
readonly property bool touchingBottomBar: allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((panelBackground.y + panelBackground.height) - (root.height - root.barMarginV - Style.barHeight)) <= 1
|
|
readonly property bool touchingLeftBar: allowAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(panelBackground.x - (root.barMarginH + Style.barHeight)) <= 1
|
|
readonly property bool touchingRightBar: allowAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((panelBackground.x + panelBackground.width) - (root.width - root.barMarginH - Style.barHeight)) <= 1
|
|
|
|
// Expose panelBackground for geometry placeholder
|
|
property alias geometryPlaceholder: panelBackground
|
|
|
|
// The actual panel background - provides geometry for PanelBackground rendering
|
|
Item {
|
|
id: panelBackground
|
|
|
|
// Expose self as panelItem for PanelBackground compatibility
|
|
readonly property var panelItem: panelBackground
|
|
|
|
// Store target dimensions (Initialize to 0, set by setPosition())
|
|
property real targetWidth: 0
|
|
property real targetHeight: 0
|
|
property real targetX: root.x
|
|
property real targetY: root.y
|
|
|
|
// Track whether dimensions have been initialized (to prevent initial changes from animating)
|
|
property bool dimensionsInitialized: false
|
|
|
|
property var bezierCurve: [0.05, 0, 0.133, 0.06, 0.166, 0.4, 0.208, 0.82, 0.25, 1, 1, 1]
|
|
|
|
// Determine which edges the panel is closest to for animation direction
|
|
// Use target position (not animated position) to avoid binding loops
|
|
readonly property bool willTouchTopBar: {
|
|
if (!panelContent.allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
|
|
return false;
|
|
var targetTopBarY = root.barMarginV + Style.barHeight;
|
|
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1;
|
|
}
|
|
readonly property bool willTouchBottomBar: {
|
|
if (!panelContent.allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical)
|
|
return false;
|
|
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight;
|
|
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1;
|
|
}
|
|
readonly property bool willTouchLeftBar: {
|
|
if (!panelContent.allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical)
|
|
return false;
|
|
var targetLeftBarX = root.barMarginH + Style.barHeight;
|
|
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1;
|
|
}
|
|
readonly property bool willTouchRightBar: {
|
|
if (!panelContent.allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical)
|
|
return false;
|
|
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth;
|
|
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1;
|
|
}
|
|
readonly property bool willTouchTopEdge: panelContent.allowAttach && panelBackground.targetY <= 1
|
|
readonly property bool willTouchBottomEdge: panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
|
readonly property bool willTouchLeftEdge: panelContent.allowAttach && panelBackground.targetX <= 1
|
|
readonly property bool willTouchRightEdge: panelContent.allowAttach && (panelBackground.targetX + panelBackground.targetWidth) >= (root.width - 1)
|
|
|
|
readonly property bool isActuallyAttachedToAnyEdge: {
|
|
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;
|
|
}
|
|
|
|
readonly property bool animateFromTop: {
|
|
// When panel is opening, use effective anchors (known before position is calculated)
|
|
if (!root.isPanelVisible) {
|
|
// Attached to horizontal bar at top
|
|
if (panelContent.allowAttachToBar && root.effectivePanelAnchorTop && !root.barIsVertical) {
|
|
return true;
|
|
}
|
|
// Attached to vertical bar (left/right) - don't animate from top
|
|
if (panelContent.allowAttachToBar && root.barIsVertical && (root.effectivePanelAnchorLeft || root.effectivePanelAnchorRight)) {
|
|
return false;
|
|
}
|
|
// Default: animate from top
|
|
return true;
|
|
}
|
|
// Panel is visible - use calculated positions
|
|
if (willTouchTopBar) {
|
|
return true;
|
|
}
|
|
if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) {
|
|
return true;
|
|
}
|
|
if (!isActuallyAttachedToAnyEdge) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
readonly property bool animateFromBottom: {
|
|
if (!root.isPanelVisible) {
|
|
if (panelContent.allowAttachToBar && root.effectivePanelAnchorBottom && !root.barIsVertical) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (willTouchBottomBar) {
|
|
return true;
|
|
}
|
|
if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
readonly property bool animateFromLeft: {
|
|
if (!root.isPanelVisible) {
|
|
if (panelContent.allowAttachToBar && root.effectivePanelAnchorLeft && root.barIsVertical) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (willTouchTopBar || willTouchBottomBar) {
|
|
return false;
|
|
}
|
|
if (willTouchLeftBar) {
|
|
return true;
|
|
}
|
|
if (willTouchTopEdge || willTouchBottomEdge) {
|
|
return false;
|
|
}
|
|
if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
readonly property bool animateFromRight: {
|
|
if (!root.isPanelVisible) {
|
|
if (panelContent.allowAttachToBar && root.effectivePanelAnchorRight && root.barIsVertical) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (willTouchTopBar || willTouchBottomBar) {
|
|
return false;
|
|
}
|
|
if (willTouchRightBar) {
|
|
return true;
|
|
}
|
|
if (willTouchTopEdge || willTouchBottomEdge) {
|
|
return false;
|
|
}
|
|
if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Determine animation axis based on which edge is closest
|
|
// Priority: horizontal edges (top/bottom) take precedence over vertical edges (left/right)
|
|
// This prevents diagonal animations when panel is attached to a corner
|
|
readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight)
|
|
readonly property bool shouldAnimateHeight: animateFromTop || animateFromBottom
|
|
|
|
// Current animated width/height (referenced by x/y for right/bottom positioning)
|
|
readonly property real currentWidth: {
|
|
if (isClosing && opacityFadeComplete && shouldAnimateWidth)
|
|
return 0;
|
|
if (isClosing || isPanelVisible)
|
|
return targetWidth;
|
|
// If not animating width, start at target (no visual change)
|
|
// If animating width, start at 0 (will animate to target)
|
|
return shouldAnimateWidth ? 0 : targetWidth;
|
|
}
|
|
readonly property real currentHeight: {
|
|
if (isClosing && opacityFadeComplete && shouldAnimateHeight)
|
|
return 0;
|
|
if (isClosing || isPanelVisible)
|
|
return targetHeight;
|
|
// If not animating height, start at target (no visual change)
|
|
// If animating height, start at 0 (will animate to target)
|
|
return shouldAnimateHeight ? 0 : targetHeight;
|
|
}
|
|
|
|
width: currentWidth
|
|
height: currentHeight
|
|
|
|
x: {
|
|
// Offset x to make panel grow/shrink from the appropriate edge
|
|
if (animateFromRight) {
|
|
// Keep the RIGHT edge fixed at its target position
|
|
if (isPanelVisible || isClosing) {
|
|
var targetRightEdge = targetX + targetWidth;
|
|
return targetRightEdge - width;
|
|
}
|
|
}
|
|
return targetX;
|
|
}
|
|
y: {
|
|
// Offset y to make panel grow/shrink from the appropriate edge
|
|
if (animateFromBottom) {
|
|
// Keep the BOTTOM edge fixed at its target position
|
|
if (isPanelVisible || isClosing) {
|
|
var targetBottomEdge = targetY + targetHeight;
|
|
return targetBottomEdge - height;
|
|
}
|
|
}
|
|
return targetY;
|
|
}
|
|
|
|
Behavior on width {
|
|
NumberAnimation {
|
|
id: widthAnimation
|
|
// Use 0ms if dimensions not initialized to prevent initial changes from animating
|
|
// During opening: use 0ms if not animating width, otherwise use normal duration
|
|
// During closing: use 0ms if not animating width, otherwise use fast duration
|
|
// During normal content resizing: always use normal duration
|
|
duration: !panelBackground.dimensionsInitialized ? 0 : (root.isOpening && !panelBackground.shouldAnimateWidth) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateWidth) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: panelBackground.bezierCurve
|
|
|
|
onRunningChanged: {
|
|
// Safety: Zero-duration animation handling
|
|
if (!running && duration === 0) {
|
|
if (root.isClosing && panelBackground.width === 0 && panelBackground.shouldAnimateWidth) {
|
|
Logger.d("SmartPanel", "Zero-duration width animation - finalizing", root.objectName);
|
|
Qt.callLater(root.finalizeClose);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// When width shrink completes during close, finalize
|
|
if (!running && root.isClosing && panelBackground.width === 0 && panelBackground.shouldAnimateWidth) {
|
|
Qt.callLater(root.finalizeClose);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
id: heightAnimation
|
|
// Use 0ms if dimensions not initialized to prevent initial changes from animating
|
|
// During opening: use 0ms if not animating height, otherwise use normal duration
|
|
// During closing: use 0ms if not animating height, otherwise use fast duration
|
|
// During normal content resizing: always use normal duration
|
|
duration: !panelBackground.dimensionsInitialized ? 0 : (root.isOpening && !panelBackground.shouldAnimateHeight) ? 0 : root.isOpening ? Style.animationNormal : (root.isClosing && !panelBackground.shouldAnimateHeight) ? 0 : root.isClosing ? Style.animationFast : Style.animationNormal
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: panelBackground.bezierCurve
|
|
|
|
onRunningChanged: {
|
|
// Safety: Zero-duration animation handling
|
|
if (!running && duration === 0) {
|
|
if (root.isClosing && panelBackground.height === 0 && panelBackground.shouldAnimateHeight) {
|
|
Logger.d("SmartPanel", "Zero-duration height animation - finalizing", root.objectName);
|
|
Qt.callLater(root.finalizeClose);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// When height shrink completes during close, finalize
|
|
if (!running && root.isClosing && panelBackground.height === 0 && panelBackground.shouldAnimateHeight) {
|
|
Qt.callLater(root.finalizeClose);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Corner states for PanelBackground to read
|
|
// State -1: No radius (flat/square corner)
|
|
// State 0: Normal (inner curve)
|
|
// State 1: Horizontal inversion (outer curve on X-axis)
|
|
// State 2: Vertical inversion (outer curve on Y-axis)
|
|
|
|
// Smart corner state calculation based on bar attachment and edge touching
|
|
property int topLeftCornerState: {
|
|
// If bar is not visible, don't show outer corners based on bar attachment
|
|
if (!root.barShouldShow) {
|
|
// Only check edge touching, not bar touching
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingTopEdge);
|
|
if (edgeInverted) {
|
|
if (panelContent.touchingLeftEdge && panelContent.touchingTopEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingLeftEdge)
|
|
return 2; // Left edge: vertical inversion
|
|
if (panelContent.touchingTopEdge)
|
|
return 1; // Top edge: horizontal inversion
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
var barInverted = panelContent.allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
|
var barTouchInverted = panelContent.touchingTopBar || panelContent.touchingLeftBar;
|
|
// Invert if touching either edge that forms this corner (left OR top), regardless of bar position
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingTopEdge);
|
|
var oppositeEdgeInverted = panelContent.allowAttach && (panelContent.touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
|
|
|
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
|
// Determine inversion direction based on which edge is touched
|
|
if (panelContent.touchingLeftEdge && panelContent.touchingTopEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingLeftEdge)
|
|
return 2; // Left edge: vertical inversion
|
|
if (panelContent.touchingTopEdge)
|
|
return 1; // Top edge: horizontal inversion
|
|
return root.barIsVertical ? 2 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
property int topRightCornerState: {
|
|
// If bar is not visible, don't show outer corners based on bar attachment
|
|
if (!root.barShouldShow) {
|
|
// Only check edge touching, not bar touching
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingTopEdge);
|
|
if (edgeInverted) {
|
|
if (panelContent.touchingRightEdge && panelContent.touchingTopEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingRightEdge)
|
|
return 2; // Right edge: vertical inversion
|
|
if (panelContent.touchingTopEdge)
|
|
return 1; // Top edge: horizontal inversion
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
var barInverted = panelContent.allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
|
var barTouchInverted = panelContent.touchingTopBar || panelContent.touchingRightBar;
|
|
// Invert if touching either edge that forms this corner (right OR top), regardless of bar position
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingTopEdge);
|
|
var oppositeEdgeInverted = panelContent.allowAttach && (panelContent.touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
|
|
|
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
|
// Determine inversion direction based on which edge is touched
|
|
if (panelContent.touchingRightEdge && panelContent.touchingTopEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingRightEdge)
|
|
return 2; // Right edge: vertical inversion
|
|
if (panelContent.touchingTopEdge)
|
|
return 1; // Top edge: horizontal inversion
|
|
return root.barIsVertical ? 2 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
property int bottomLeftCornerState: {
|
|
// If bar is not visible, don't show outer corners based on bar attachment
|
|
if (!root.barShouldShow) {
|
|
// Only check edge touching, not bar touching
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingBottomEdge);
|
|
if (edgeInverted) {
|
|
if (panelContent.touchingLeftEdge && panelContent.touchingBottomEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingLeftEdge)
|
|
return 2; // Left edge: vertical inversion
|
|
if (panelContent.touchingBottomEdge)
|
|
return 1; // Bottom edge: horizontal inversion
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
var barInverted = panelContent.allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
|
var barTouchInverted = panelContent.touchingBottomBar || panelContent.touchingLeftBar;
|
|
// Invert if touching either edge that forms this corner (left OR bottom), regardless of bar position
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingLeftEdge || panelContent.touchingBottomEdge);
|
|
var oppositeEdgeInverted = panelContent.allowAttach && (panelContent.touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
|
|
|
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
|
// Determine inversion direction based on which edge is touched
|
|
if (panelContent.touchingLeftEdge && panelContent.touchingBottomEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingLeftEdge)
|
|
return 2; // Left edge: vertical inversion
|
|
if (panelContent.touchingBottomEdge)
|
|
return 1; // Bottom edge: horizontal inversion
|
|
return root.barIsVertical ? 2 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
property int bottomRightCornerState: {
|
|
// If bar is not visible, don't show outer corners based on bar attachment
|
|
if (!root.barShouldShow) {
|
|
// Only check edge touching, not bar touching
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingBottomEdge);
|
|
if (edgeInverted) {
|
|
if (panelContent.touchingRightEdge && panelContent.touchingBottomEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingRightEdge)
|
|
return 2; // Right edge: vertical inversion
|
|
if (panelContent.touchingBottomEdge)
|
|
return 1; // Bottom edge: horizontal inversion
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
var barInverted = panelContent.allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
|
var barTouchInverted = panelContent.touchingBottomBar || panelContent.touchingRightBar;
|
|
// Invert if touching either edge that forms this corner (right OR bottom), regardless of bar position
|
|
var edgeInverted = panelContent.allowAttach && (panelContent.touchingRightEdge || panelContent.touchingBottomEdge);
|
|
var oppositeEdgeInverted = panelContent.allowAttach && (panelContent.touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
|
|
|
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
|
// Determine inversion direction based on which edge is touched
|
|
if (panelContent.touchingRightEdge && panelContent.touchingBottomEdge)
|
|
return 0; // Both edges: no inversion (normal rounded corner)
|
|
if (panelContent.touchingRightEdge)
|
|
return 2; // Right edge: vertical inversion
|
|
if (panelContent.touchingBottomEdge)
|
|
return 1; // Bottom edge: horizontal inversion
|
|
return root.barIsVertical ? 2 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// 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
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
z: -1 // Behind content, but on the panel background
|
|
onClicked: mouse => {
|
|
mouse.accepted = true; // Accept and ignore - prevents propagation to background
|
|
}
|
|
}
|
|
}
|
|
|
|
// Panel top content: Text, icons, etc...
|
|
Loader {
|
|
id: contentLoader
|
|
active: isPanelOpen
|
|
x: panelBackground.x
|
|
y: panelBackground.y
|
|
width: panelBackground.width
|
|
height: panelBackground.height
|
|
sourceComponent: root.panelContent
|
|
|
|
onLoaded: {
|
|
// Wait for contentPreferredWidth/Height to be available before making visible
|
|
Qt.callLater(function () {
|
|
// Calculate position with stable contentPreferredWidth/Height values
|
|
setPosition();
|
|
|
|
// Mark dimensions as initialized to enable animations
|
|
panelBackground.dimensionsInitialized = true;
|
|
|
|
// Make panel visible, now only the intended dimension will animate
|
|
root.isPanelVisible = true;
|
|
opacityTrigger.start();
|
|
|
|
// Start open watchdog timer
|
|
root.openWatchdogActive = true;
|
|
openWatchdogTimer.start();
|
|
|
|
opened();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
PanelService.registerPanel(root);
|
|
}
|
|
}
|