feat(layout): implement framed bar visuals and layout logic

This commit is contained in:
tibssy
2026-01-25 00:33:46 +00:00
parent 2516652c21
commit 680dfccfff
6 changed files with 335 additions and 148 deletions
+19 -14
View File
@@ -13,6 +13,7 @@ import qs.Services.UI
Variants {
model: Quickshell.screens
delegate: Item {
id: windowItem
required property ShellScreen modelData
property bool shouldBeActive: {
@@ -76,23 +77,27 @@ Variants {
// BarExclusionZone - created after MainScreen has fully loaded
// Disabled when bar is hidden or not configured for this screen
Loader {
active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false;
Repeater {
model: Settings.data.bar.barType === "framed" ? ["top", "bottom", "left", "right"] : [Settings.getBarPositionForScreen(windowItem.modelData?.name)]
delegate: Loader {
active: {
if (!windowItem.windowLoaded || !windowItem.shouldBeActive || !BarService.effectivelyVisible)
return false;
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name);
}
asynchronous: false
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(windowItem.modelData?.name);
}
asynchronous: false
sourceComponent: BarExclusionZone {
screen: modelData
}
sourceComponent: BarExclusionZone {
screen: windowItem.modelData
edge: modelData
}
onLoaded: {
Logger.d("AllScreens", "BarExclusionZone created for", modelData?.name);
onLoaded: {
Logger.d("AllScreens", "BarExclusionZone (" + modelData + ") created for", windowItem.modelData?.name);
}
}
}
+141 -38
View File
@@ -48,6 +48,11 @@ ShapePath {
// Corner radius (from Style)
readonly property real radius: Style.radiusL
// Framed bar properties
readonly property bool isFramed: Settings.data.bar.barType === "framed"
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12
readonly property real frameRadius: Settings.data.bar.frameRadius ?? 20
// Bar position - since bar's parent fills the screen and Shape also fills the screen,
// we can use bar.x and bar.y directly (they're already in screen coordinates)
readonly property point barMappedPos: bar ? Qt.point(bar.x, bar.y) : Qt.point(0, 0)
@@ -56,6 +61,18 @@ ShapePath {
readonly property real barWidth: (bar && shouldShow) ? bar.width : 0
readonly property real barHeight: (bar && shouldShow) ? bar.height : 0
// Screen dimensions for frame
readonly property real screenWidth: windowRoot?.screen?.width || 0
readonly property real screenHeight: windowRoot?.screen?.height || 0
// Inner hole dimensions for framed mode - always relative to screen
readonly property string barPosition: Settings.getBarPositionForScreen(windowRoot?.screen?.name)
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property real holeX: (barPosition === "left") ? barWidth : frameThickness
readonly property real holeY: (barPosition === "top") ? barHeight : frameThickness
readonly property real holeWidth: screenWidth - (barPosition === "left" || barPosition === "right" ? (barWidth + frameThickness) : (frameThickness * 2))
readonly property real holeHeight: screenHeight - (barPosition === "top" || barPosition === "bottom" ? (barHeight + frameThickness) : (frameThickness * 2))
// Flatten corners if bar is too small (handle null bar)
readonly property bool shouldFlatten: bar ? ShapeCornerHelper.shouldFlatten(barWidth, barHeight, radius) : false
readonly property real effectiveRadius: shouldFlatten ? (bar ? ShapeCornerHelper.getFlattenedRadius(Math.min(barWidth, barHeight), radius) : 0) : radius
@@ -89,73 +106,159 @@ ShapePath {
// ShapePath configuration
strokeWidth: -1 // No stroke, fill only
fillColor: backgroundColor
fillRule: isFramed ? ShapePath.OddEvenFill : ShapePath.WindingFill
// Starting position (top-left corner, after the arc)
// Use mapped coordinates relative to the Shape container
startX: barMappedPos.x + tlRadius * tlMultX
startY: barMappedPos.y
// Starting position
// In framed mode, we start at (0,0) to draw the screen rectangle first
startX: isFramed ? 0 : (barMappedPos.x + tlRadius * tlMultX)
startY: isFramed ? 0 : barMappedPos.y
// ========== PATH DEFINITION ==========
// Draws a rectangle with potentially inverted corners
// All coordinates are relative to startX/startY
// Top edge (moving right)
// 1. Main Bar / Outer Screen Rectangle
PathLine {
relativeX: root.barWidth - root.tlRadius * root.tlMultX - root.trRadius * root.trMultX
relativeY: 0
x: {
if (!root.shouldShow)
return 0;
if (root.isFramed)
return root.screenWidth;
return root.barMappedPos.x + root.barWidth - root.trRadius * root.trMultX;
}
y: root.isFramed ? 0 : root.barMappedPos.y
}
// Top-right corner arc
// Bar top-right corner (only if not framed)
PathArc {
relativeX: root.trRadius * root.trMultX
relativeY: root.trRadius * root.trMultY
radiusX: root.trRadius
radiusY: root.trRadius
x: root.isFramed ? (root.shouldShow ? root.screenWidth : 0) : (root.barMappedPos.x + root.barWidth)
y: root.isFramed ? 0 : (root.barMappedPos.y + root.trRadius * root.trMultY)
radiusX: root.isFramed ? 0 : root.trRadius
radiusY: root.isFramed ? 0 : root.trRadius
direction: ShapeCornerHelper.getArcDirection(root.trMultX, root.trMultY)
}
// Right edge (moving down)
PathLine {
relativeX: 0
relativeY: root.barHeight - root.trRadius * root.trMultY - root.brRadius * root.brMultY
x: root.isFramed ? (root.shouldShow ? root.screenWidth : 0) : (root.barMappedPos.x + root.barWidth)
y: {
if (!root.shouldShow)
return 0;
if (root.isFramed)
return root.screenHeight;
return root.barMappedPos.y + root.barHeight - root.brRadius * root.brMultY;
}
}
// Bottom-right corner arc
// Bar bottom-right corner (only if not framed)
PathArc {
relativeX: -root.brRadius * root.brMultX
relativeY: root.brRadius * root.brMultY
radiusX: root.brRadius
radiusY: root.brRadius
x: root.isFramed ? (root.shouldShow ? root.screenWidth : 0) : (root.barMappedPos.x + root.barWidth - root.brRadius * root.brMultX)
y: root.isFramed ? (root.shouldShow ? root.screenHeight : 0) : (root.barMappedPos.y + root.barHeight)
radiusX: root.isFramed ? 0 : root.brRadius
radiusY: root.isFramed ? 0 : root.brRadius
direction: ShapeCornerHelper.getArcDirection(root.brMultX, root.brMultY)
}
// Bottom edge (moving left)
PathLine {
relativeX: -(root.barWidth - root.brRadius * root.brMultX - root.blRadius * root.blMultX)
relativeY: 0
x: {
if (!root.shouldShow)
return 0;
if (root.isFramed)
return 0;
return root.barMappedPos.x + root.blRadius * root.blMultX;
}
y: root.isFramed ? (root.shouldShow ? root.screenHeight : 0) : (root.barMappedPos.y + root.barHeight)
}
// Bottom-left corner arc
// Bar bottom-left corner (only if not framed)
PathArc {
relativeX: -root.blRadius * root.blMultX
relativeY: -root.blRadius * root.blMultY
radiusX: root.blRadius
radiusY: root.blRadius
x: root.isFramed ? 0 : root.barMappedPos.x
y: root.isFramed ? (root.shouldShow ? root.screenHeight : 0) : (root.barMappedPos.y + root.barHeight - root.blRadius * root.blMultY)
radiusX: root.isFramed ? 0 : root.blRadius
radiusY: root.isFramed ? 0 : root.blRadius
direction: ShapeCornerHelper.getArcDirection(root.blMultX, root.blMultY)
}
// Left edge (moving up) - closes the path back to start
PathLine {
relativeX: 0
relativeY: -(root.barHeight - root.blRadius * root.blMultY - root.tlRadius * root.tlMultY)
x: root.isFramed ? 0 : root.barMappedPos.x
y: {
if (!root.shouldShow)
return 0;
if (root.isFramed)
return 0;
return root.barMappedPos.y + root.tlRadius * root.tlMultY;
}
}
// Top-left corner arc (back to start)
// Bar top-left corner (only if not framed, back to start)
PathArc {
relativeX: root.tlRadius * root.tlMultX
relativeY: -root.tlRadius * root.tlMultY
radiusX: root.tlRadius
radiusY: root.tlRadius
x: root.isFramed ? 0 : (root.barMappedPos.x + root.tlRadius * root.tlMultX)
y: root.isFramed ? 0 : root.barMappedPos.y
radiusX: root.isFramed ? 0 : root.tlRadius
radiusY: root.isFramed ? 0 : root.tlRadius
direction: ShapeCornerHelper.getArcDirection(root.tlMultX, root.tlMultY)
}
// 2. Inner Hole for Framed Mode (Clockwise)
PathMove {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX
y: (root.isFramed && root.shouldShow) ? root.holeY : root.startY
}
// Top edge
PathLine {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth - root.frameRadius) : ((root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX)
y: (root.isFramed && root.shouldShow) ? root.holeY : ((root.isFramed && root.shouldShow) ? root.holeY : root.startY)
}
// Top-right corner
PathArc {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth) : ((root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth - root.frameRadius) : root.startX)
y: (root.isFramed && root.shouldShow) ? (root.holeY + root.frameRadius) : ((root.isFramed && root.shouldShow) ? root.holeY : root.startY)
radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
direction: PathArc.Clockwise
}
// Right edge
PathLine {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth) : root.startX
y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight - root.frameRadius) : root.startY
}
// Bottom-right corner
PathArc {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.holeWidth - root.frameRadius) : root.startX
y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight) : root.startY
radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
direction: PathArc.Clockwise
}
// Bottom edge
PathLine {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX
y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight) : root.startY
}
// Bottom-left corner
PathArc {
x: (root.isFramed && root.shouldShow) ? root.holeX : root.startX
y: (root.isFramed && root.shouldShow) ? (root.holeY + root.holeHeight - root.frameRadius) : root.startY
radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
direction: PathArc.Clockwise
}
// Left edge
PathLine {
x: (root.isFramed && root.shouldShow) ? root.holeX : root.startX
y: (root.isFramed && root.shouldShow) ? (root.holeY + root.frameRadius) : root.startY
}
// Top-left corner (back to start)
PathArc {
x: (root.isFramed && root.shouldShow) ? (root.holeX + root.frameRadius) : root.startX
y: (root.isFramed && root.shouldShow) ? root.holeY : root.startY
radiusX: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
radiusY: (root.isFramed && root.shouldShow) ? root.frameRadius : 0
direction: PathArc.Clockwise
}
}
+7 -5
View File
@@ -32,6 +32,8 @@ PanelWindow {
// Position and size to match bar location (per-screen)
readonly property string barPosition: Settings.getBarPositionForScreen(barWindow.screen?.name)
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property bool isFramed: Settings.data.bar.barType === "framed"
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12
readonly property bool barFloating: Settings.data.bar.floating || false
readonly property real barMarginH: Math.ceil(barFloating ? Settings.data.bar.marginHorizontal : 0)
readonly property real barMarginV: Math.ceil(barFloating ? Settings.data.bar.marginVertical : 0)
@@ -45,12 +47,12 @@ PanelWindow {
right: barPosition === "right" || !barIsVertical
}
// Handle floating margins
// Handle floating margins and framed mode offsets
margins {
top: barPosition === "top" || barIsVertical ? barMarginV : 0
bottom: barPosition === "bottom" || barIsVertical ? barMarginV : 0
left: barPosition === "left" || !barIsVertical ? barMarginH : 0
right: barPosition === "right" || !barIsVertical ? barMarginH : 0
top: (barPosition === "top") ? barMarginV : (isFramed ? frameThickness : barMarginV)
bottom: (barPosition === "bottom") ? barMarginV : (isFramed ? frameThickness : barMarginV)
left: (barPosition === "left") ? barMarginH : (isFramed ? frameThickness : barMarginH)
right: (barPosition === "right") ? barMarginH : (isFramed ? frameThickness : barMarginH)
}
// Set a tight window size
+17 -18
View File
@@ -13,13 +13,14 @@ import qs.Services.Compositor
PanelWindow {
id: root
// Edge to anchor to and thickness to reserve
property string edge: Settings.getBarPositionForScreen(screen?.name)
property real thickness: (edge === Settings.getBarPositionForScreen(screen?.name)) ? Style.getBarHeightForScreen(screen?.name) : (Settings.data.bar.frameThickness ?? 12)
readonly property bool exclusive: Settings.data.bar.exclusive
readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name)
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property real barHeight: Style.getBarHeightForScreen(screen?.name)
readonly property bool barFloating: Settings.data.bar.floating || false
readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0
readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0
readonly property real barMarginH: (barFloating && edge === Settings.getBarPositionForScreen(screen?.name)) ? Math.ceil(Settings.data.bar.marginHorizontal) : 0
readonly property real barMarginV: (barFloating && edge === Settings.getBarPositionForScreen(screen?.name)) ? Math.ceil(Settings.data.bar.marginVertical) : 0
readonly property real fractOffset: CompositorService.getDisplayScale(screen?.name) % 1.0
// Invisible - just reserves space
@@ -29,30 +30,28 @@ PanelWindow {
// Wayland layer shell configuration
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "noctalia-bar-exclusion-" + (screen?.name || "unknown")
WlrLayershell.namespace: "noctalia-bar-exclusion-" + edge + "-" + (screen?.name || "unknown")
WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore
// Anchor based on bar position
// Anchor based on specified edge
anchors {
top: barPosition === "top"
bottom: barPosition === "bottom"
left: barPosition === "left" || barPosition === "top" || barPosition === "bottom"
right: barPosition === "right" || barPosition === "top" || barPosition === "bottom"
top: edge === "top"
bottom: edge === "bottom"
left: edge === "left" || edge === "top" || edge === "bottom"
right: edge === "right" || edge === "top" || edge === "bottom"
}
// Size based on bar orientation
// Size based on orientation
implicitWidth: {
if (barIsVertical) {
// Vertical bar: reserve bar height + margin on the anchored edge only
return barHeight + barMarginH - fractOffset;
if (edge === "left" || edge === "right") {
return thickness + barMarginH - fractOffset;
}
return 0; // Auto-width when left/right anchors are true
}
implicitHeight: {
if (!barIsVertical) {
// Horizontal bar: reserve bar height + margin on the anchored edge only
return barHeight + barMarginV - fractOffset;
if (edge === "top" || edge === "bottom") {
return thickness + barMarginV - fractOffset;
}
return 0; // Auto-height when top/bottom anchors are true
}
+64 -9
View File
@@ -134,13 +134,58 @@ PanelWindow {
Region {
id: barMaskRegion
x: barPlaceholder.x
y: barPlaceholder.y
readonly property bool isFramed: Settings.data.bar.barType === "framed"
readonly property real barThickness: Style.barHeight
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12
readonly property string barPos: Settings.data.bar.position || "top"
// Set width/height to 0 if bar shouldn't show on this screen (makes region empty)
width: root.barShouldShow ? barPlaceholder.width : 0
height: root.barShouldShow ? barPlaceholder.height : 0
intersection: Intersection.Subtract
// Bar / Frame Mask
Region {
// Mode: Simple or Floating
x: barPlaceholder.x
y: barPlaceholder.y
width: (!barMaskRegion.isFramed && root.barShouldShow) ? barPlaceholder.width : 0
height: (!barMaskRegion.isFramed && root.barShouldShow) ? barPlaceholder.height : 0
intersection: Intersection.Subtract
}
// Mode: Framed - 4 sides
Region {
// Top side
Region {
x: 0
y: 0
width: (barMaskRegion.isFramed && root.barShouldShow) ? root.width : 0
height: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "top" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0
intersection: Intersection.Subtract
}
// Bottom side
Region {
x: 0
y: (barMaskRegion.isFramed && root.barShouldShow) ? (root.height - (barMaskRegion.barPos === "bottom" ? barMaskRegion.barThickness : barMaskRegion.frameThickness)) : 0
width: (barMaskRegion.isFramed && root.barShouldShow) ? root.width : 0
height: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "bottom" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0
intersection: Intersection.Subtract
}
// Left side
Region {
x: 0
y: 0
width: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "left" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0
height: (barMaskRegion.isFramed && root.barShouldShow) ? root.height : 0
intersection: Intersection.Subtract
}
// Right side
Region {
x: (barMaskRegion.isFramed && root.barShouldShow) ? (root.width - (barMaskRegion.barPos === "right" ? barMaskRegion.barThickness : barMaskRegion.frameThickness)) : 0
width: (barMaskRegion.isFramed && root.barShouldShow) ? (barMaskRegion.barPos === "right" ? barMaskRegion.barThickness : barMaskRegion.frameThickness) : 0
height: (barMaskRegion.isFramed && root.barShouldShow) ? root.height : 0
intersection: Intersection.Subtract
}
}
}
// Background region for click-to-close - reactive sizing
@@ -323,6 +368,8 @@ PanelWindow {
// Bar background positioning properties (per-screen)
readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name)
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property bool isFramed: Settings.data.bar.barType === "framed"
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12
readonly property bool barFloating: Settings.data.bar.floating || false
readonly property real barMarginH: barFloating ? Math.floor(Settings.data.bar.marginHorizontal) : 0
readonly property real barMarginV: barFloating ? Math.floor(Settings.data.bar.marginVertical) : 0
@@ -333,24 +380,32 @@ PanelWindow {
x: {
if (barPosition === "right")
return screen.width - barHeight - barMarginH;
if (isFramed && !barIsVertical)
return frameThickness;
return barMarginH;
}
y: {
if (barPosition === "bottom")
return screen.height - barHeight - barMarginV;
if (isFramed && barIsVertical)
return frameThickness;
return barMarginV;
}
width: {
if (barIsVertical) {
return barHeight;
}
if (isFramed)
return screen.width - frameThickness * 2;
return screen.width - barMarginH * 2;
}
height: {
if (barIsVertical) {
return screen.height - barMarginV * 2;
if (!barIsVertical) {
return barHeight;
}
return barHeight;
if (isFramed)
return screen.height - frameThickness * 2;
return screen.height - barMarginV * 2;
}
// Corner states (same as Bar.qml)
+87 -64
View File
@@ -91,6 +91,8 @@ Item {
readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name)
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property real barHeight: Style.getBarHeightForScreen(screen?.name)
readonly property bool isFramed: Settings.data.bar.barType === "framed"
readonly property real frameThickness: Settings.data.bar.frameThickness ?? 12
readonly property bool barFloating: Settings.data.bar.floating
readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0
readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0
@@ -172,15 +174,19 @@ Item {
barWindowX = screenWidth - root.barMarginH - root.barHeight;
} else if (root.barPosition === "left") {
barWindowX = root.barMarginH;
} else if (root.isFramed) {
barWindowX = root.frameThickness;
}
// For top/bottom bars, barWindowX stays 0 (full width window)
// For top/bottom bars, barWindowX stays 0 (full width window) unless framed
if (root.barPosition === "bottom") {
barWindowY = screenHeight - root.barMarginV - root.barHeight;
} else if (root.barPosition === "top") {
barWindowY = root.barMarginV;
} else if (root.isFramed) {
barWindowY = root.frameThickness;
}
// For left/right bars, barWindowY stays 0 (full height window)
// For left/right bars, barWindowY stays 0 (full height window) unless framed
root.buttonPosition = Qt.point(barWindowX + buttonLocal.x, barWindowY + buttonLocal.y);
root.buttonWidth = buttonItem.width;
@@ -298,6 +304,12 @@ Item {
return;
}
// Effective screen margins (account for frame thickness)
var effMarginL = Style.marginL + (root.isFramed ? root.frameThickness : 0);
var effMarginR = Style.marginL + (root.isFramed ? root.frameThickness : 0);
var effMarginT = Style.marginL + (root.isFramed ? root.frameThickness : 0);
var effMarginB = Style.marginL + (root.isFramed ? root.frameThickness : 0);
// Calculate panel dimensions first (needed for positioning)
var w;
// Priority 1: Content-driven size (dynamic)
@@ -310,7 +322,7 @@ Item {
else {
w = root.preferredWidth;
}
var panelWidth = Math.min(w, root.width - Style.marginL * 2);
var panelWidth = Math.min(w, root.width - effMarginL - effMarginR);
var h;
// Priority 1: Content-driven size (dynamic)
@@ -323,7 +335,7 @@ Item {
else {
h = root.preferredHeight;
}
var panelHeight = Math.min(h, root.height - root.barHeight - Style.marginL * 2);
var panelHeight = Math.min(h, root.height - root.barHeight - effMarginT - effMarginB);
// Update panelBackground target size (will be animated)
panelBackground.targetWidth = panelWidth;
@@ -337,6 +349,17 @@ Item {
var topBarEdgeWithOverlap = root.barMarginV + root.barHeight - overlap;
var bottomBarEdgeWithOverlap = root.height - root.barMarginV - root.barHeight + overlap;
if (root.isFramed) {
if (root.barPosition === "left")
leftBarEdgeWithOverlap = root.barHeight - overlap;
if (root.barPosition === "right")
rightBarEdgeWithOverlap = root.width - root.barHeight + overlap;
if (root.barPosition === "top")
topBarEdgeWithOverlap = root.barHeight - overlap;
if (root.barPosition === "bottom")
bottomBarEdgeWithOverlap = root.height - root.barHeight + overlap;
}
// Calculate position
var calculatedX;
var calculatedY;
@@ -355,14 +378,14 @@ Item {
} 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;
var minX = effMarginL;
var maxX = root.width - panelWidth - effMarginR;
// Account for vertical bar taking up space
if (root.barPosition === "left") {
minX = root.barMarginH + root.barHeight + Style.marginL;
minX = (root.isFramed ? 0 : root.barMarginH) + root.barHeight + Style.marginL;
} else if (root.barPosition === "right") {
maxX = root.width - root.barMarginH - root.barHeight - panelWidth - Style.marginL;
maxX = root.width - (root.isFramed ? 0 : root.barMarginH) - root.barHeight - panelWidth - Style.marginL;
}
panelX = Math.max(minX, Math.min(panelX, maxX));
@@ -373,11 +396,11 @@ Item {
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;
var barLeftEdge = (root.isFramed ? root.frameThickness : root.barMarginH) + cornerInset;
var barRightEdge = root.width - (root.isFramed ? root.frameThickness : 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));
panelX = Math.max(effMarginL, Math.min(panelX, root.width - panelWidth - effMarginR));
}
calculatedX = panelX;
}
@@ -386,12 +409,12 @@ Item {
if (root.panelAnchorHorizontalCenter) {
if (root.barIsVertical) {
if (root.barPosition === "left") {
var availableStart = root.barMarginH + root.barHeight;
var availableWidth = root.width - availableStart;
var availableStart = (root.isFramed ? 0 : root.barMarginH) + root.barHeight;
var availableWidth = root.width - availableStart - (root.isFramed ? root.frameThickness : 0);
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
} else if (root.barPosition === "right") {
var availableWidth = root.width - root.barMarginH - root.barHeight;
calculatedX = (availableWidth - panelWidth) / 2;
var availableWidth = root.width - (root.isFramed ? 0 : root.barMarginH) - root.barHeight - (root.isFramed ? root.frameThickness : 0);
calculatedX = (root.isFramed ? root.frameThickness : 0) + (availableWidth - panelWidth) / 2;
} else {
calculatedX = (root.width - panelWidth) / 2;
}
@@ -410,12 +433,12 @@ Item {
var rightCornerInset = Style.radiusL * 2;
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
} else {
calculatedX = root.width - panelWidth;
calculatedX = root.width - panelWidth - (root.isFramed ? root.frameThickness : 0);
}
}
} else {
// Not attached: position at right with margin
calculatedX = root.width - panelWidth - Style.marginL;
calculatedX = root.width - panelWidth - effMarginR;
}
} else if (root.panelAnchorLeft) {
// Use raw panelAnchorLeft for positioning decision
@@ -429,12 +452,12 @@ Item {
var leftCornerInset = Style.radiusL * 2;
calculatedX = root.barMarginH + leftCornerInset;
} else {
calculatedX = 0;
calculatedX = (root.isFramed ? root.frameThickness : 0);
}
}
} else {
// Not attached: position at left with margin
calculatedX = Style.marginL;
calculatedX = effMarginL;
}
} else {
// No explicit anchor: attach to bar if allowAttach, otherwise center
@@ -449,19 +472,19 @@ Item {
} else {
// Not attached: center in available space
if (root.barPosition === "left") {
var availableStart = root.barMarginH + root.barHeight;
var availableWidth = root.width - availableStart - Style.marginL;
var availableStart = (root.isFramed ? 0 : root.barMarginH) + root.barHeight;
var availableWidth = root.width - availableStart - effMarginR;
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
} else {
var availableWidth = root.width - root.barMarginH - root.barHeight - Style.marginL;
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
var availableWidth = root.width - (root.isFramed ? 0 : root.barMarginH) - root.barHeight - effMarginL;
calculatedX = effMarginL + (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 barLeftEdge = (root.isFramed ? root.frameThickness : root.barMarginH) + cornerInset;
var barRightEdge = root.width - (root.isFramed ? root.frameThickness : root.barMarginH) - cornerInset;
var centeredX = (root.width - panelWidth) / 2;
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
} else {
@@ -473,8 +496,8 @@ Item {
// Edge snapping for X
if (panelContent.allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
var leftEdgePos = root.barPosition === "left" ? leftBarEdgeWithOverlap : root.barMarginH;
var rightEdgePos = root.barPosition === "right" ? rightBarEdgeWithOverlap - panelWidth : root.width - root.barMarginH - panelWidth;
var leftEdgePos = root.barPosition === "left" ? leftBarEdgeWithOverlap : (root.isFramed ? root.frameThickness : root.barMarginH);
var rightEdgePos = root.barPosition === "right" ? rightBarEdgeWithOverlap - panelWidth : root.width - (root.isFramed ? root.frameThickness : root.barMarginH) - 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");
@@ -494,30 +517,30 @@ Item {
if (panelContent.allowAttach) {
calculatedY = topBarEdgeWithOverlap;
} else {
calculatedY = root.barMarginV + root.barHeight + Style.marginM;
calculatedY = (root.isFramed ? 0 : root.barMarginV) + root.barHeight + Style.marginM;
}
} else if (root.barPosition === "bottom") {
if (panelContent.allowAttach) {
calculatedY = bottomBarEdgeWithOverlap - panelHeight;
} else {
calculatedY = root.height - root.barMarginV - root.barHeight - panelHeight - Style.marginM;
calculatedY = root.height - (root.isFramed ? 0 : root.barMarginV) - root.barHeight - 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;
var barTopEdge = (root.isFramed ? root.frameThickness : root.barMarginV) + cornerInset;
var barBottomEdge = root.height - (root.isFramed ? root.frameThickness : 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));
panelY = Math.max(effMarginT + extraPadding, Math.min(panelY, root.height - panelHeight - effMarginB - extraPadding));
}
calculatedY = panelY;
}
} else {
// Standard anchor positioning
var barOffset = !panelContent.allowAttach && (root.barPosition === "top" || root.barPosition === "bottom") ? root.barMarginV + root.barHeight + Style.marginM : 0;
var barOffset = !panelContent.allowAttach && (root.barPosition === "top" || root.barPosition === "bottom") ? (root.isFramed ? 0 : root.barMarginV) + root.barHeight + Style.marginM : 0;
if (panelContent.allowAttach && !root.barIsVertical) {
// Attached to horizontal bar: position with overlap
@@ -532,12 +555,12 @@ Item {
if (root.panelAnchorVerticalCenter) {
if (!root.barIsVertical) {
if (root.barPosition === "top") {
var availableStart = root.barMarginV + root.barHeight;
var availableHeight = root.height - availableStart;
var availableStart = (root.isFramed ? 0 : root.barMarginV) + root.barHeight;
var availableHeight = root.height - availableStart - (root.isFramed ? root.frameThickness : 0);
calculatedY = availableStart + (availableHeight - panelHeight) / 2;
} else if (root.barPosition === "bottom") {
var availableHeight = root.height - root.barMarginV - root.barHeight;
calculatedY = (availableHeight - panelHeight) / 2;
var availableHeight = root.height - (root.isFramed ? 0 : root.barMarginV) - root.barHeight - (root.isFramed ? root.frameThickness : 0);
calculatedY = (root.isFramed ? root.frameThickness : 0) + (availableHeight - panelHeight) / 2;
} else {
calculatedY = (root.height - panelHeight) / 2;
}
@@ -546,25 +569,25 @@ Item {
}
} else if (root.panelAnchorTop) {
if (root.effectivePanelAnchorTop) {
calculatedY = root.barPosition === "top" ? topBarEdgeWithOverlap : 0;
calculatedY = root.barPosition === "top" ? topBarEdgeWithOverlap : (root.isFramed ? root.frameThickness : 0);
} else {
var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
calculatedY = topBarOffset + Style.marginL;
calculatedY = topBarOffset + effMarginT;
}
} else if (root.panelAnchorBottom) {
if (root.effectivePanelAnchorBottom) {
calculatedY = root.barPosition === "bottom" ? bottomBarEdgeWithOverlap - panelHeight : root.height - panelHeight;
calculatedY = root.barPosition === "bottom" ? bottomBarEdgeWithOverlap - panelHeight : root.height - panelHeight - (root.isFramed ? root.frameThickness : 0);
} else {
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
calculatedY = root.height - panelHeight - bottomBarOffset - effMarginB;
}
} else {
// No explicit vertical anchor
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 barTopEdge = (root.isFramed ? root.frameThickness : root.barMarginV) + cornerInset;
var barBottomEdge = root.height - (root.isFramed ? root.frameThickness : root.barMarginV) - cornerInset;
var centeredY = (root.height - panelHeight) / 2;
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
} else {
@@ -573,11 +596,11 @@ Item {
} else {
// Horizontal bar, not attached
if (root.barPosition === "top") {
calculatedY = barOffset + Style.marginL;
calculatedY = barOffset + effMarginT;
} else if (root.barPosition === "bottom") {
calculatedY = Style.marginL;
calculatedY = effMarginT;
} else {
calculatedY = Style.marginL;
calculatedY = effMarginT;
}
}
}
@@ -586,8 +609,8 @@ Item {
// Edge snapping for Y
if (panelContent.allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
var topEdgePos = root.barPosition === "top" ? topBarEdgeWithOverlap : root.barMarginV;
var bottomEdgePos = root.barPosition === "bottom" ? bottomBarEdgeWithOverlap - panelHeight : root.height - root.barMarginV - panelHeight;
var topEdgePos = root.barPosition === "top" ? topBarEdgeWithOverlap : (root.isFramed ? root.frameThickness : root.barMarginV);
var bottomEdgePos = root.barPosition === "bottom" ? bottomBarEdgeWithOverlap - panelHeight : root.height - (root.isFramed ? root.frameThickness : root.barMarginV) - 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");
@@ -755,16 +778,16 @@ Item {
}
// 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)
readonly property bool touchingLeftEdge: allowAttach && panelBackground.x <= (isFramed ? frameThickness + 1 : 1)
readonly property bool touchingRightEdge: allowAttach && (panelBackground.x + panelBackground.width) >= (root.width - (isFramed ? frameThickness + 1 : 1))
readonly property bool touchingTopEdge: allowAttach && panelBackground.y <= (isFramed ? frameThickness + 1 : 1)
readonly property bool touchingBottomEdge: allowAttach && (panelBackground.y + panelBackground.height) >= (root.height - (isFramed ? frameThickness + 1 : 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 + root.barHeight)) <= 1
readonly property bool touchingBottomBar: allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((panelBackground.y + panelBackground.height) - (root.height - root.barMarginV - root.barHeight)) <= 1
readonly property bool touchingLeftBar: allowAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(panelBackground.x - (root.barMarginH + root.barHeight)) <= 1
readonly property bool touchingRightBar: allowAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((panelBackground.x + panelBackground.width) - (root.width - root.barMarginH - root.barHeight)) <= 1
readonly property bool touchingTopBar: allowAttachToBar && root.barPosition === "top" && !root.barIsVertical && Math.abs(panelBackground.y - ((isFramed ? 0 : root.barMarginV) + root.barHeight)) <= 1
readonly property bool touchingBottomBar: allowAttachToBar && root.barPosition === "bottom" && !root.barIsVertical && Math.abs((panelBackground.y + panelBackground.height) - (root.height - (isFramed ? 0 : root.barMarginV) - root.barHeight)) <= 1
readonly property bool touchingLeftBar: allowAttachToBar && root.barPosition === "left" && root.barIsVertical && Math.abs(panelBackground.x - ((isFramed ? 0 : root.barMarginH) + root.barHeight)) <= 1
readonly property bool touchingRightBar: allowAttachToBar && root.barPosition === "right" && root.barIsVertical && Math.abs((panelBackground.x + panelBackground.width) - (root.width - (isFramed ? 0 : root.barMarginH) - root.barHeight)) <= 1
// Expose panelBackground for geometry placeholder
property alias geometryPlaceholder: panelBackground
@@ -792,31 +815,31 @@ Item {
readonly property bool willTouchTopBar: {
if (!panelContent.allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
return false;
var targetTopBarY = root.barMarginV + root.barHeight;
var targetTopBarY = (isFramed ? 0 : root.barMarginV) + root.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 - root.barHeight - panelBackground.targetHeight;
var targetBottomBarY = root.height - (isFramed ? 0 : root.barMarginV) - root.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 + root.barHeight;
var targetLeftBarX = (isFramed ? 0 : root.barMarginH) + root.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 - root.barHeight - panelBackground.targetWidth;
var targetRightBarX = root.width - (isFramed ? 0 : root.barMarginH) - root.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 willTouchTopEdge: panelContent.allowAttach && panelBackground.targetY <= (isFramed ? frameThickness + 1 : 1)
readonly property bool willTouchBottomEdge: panelContent.allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - (isFramed ? frameThickness + 1 : 1))
readonly property bool willTouchLeftEdge: panelContent.allowAttach && panelBackground.targetX <= (isFramed ? frameThickness + 1 : 1)
readonly property bool willTouchRightEdge: panelContent.allowAttach && (panelBackground.targetX + panelBackground.targetWidth) >= (root.width - (isFramed ? frameThickness + 1 : 1))
readonly property bool isActuallyAttachedToAnyEdge: {
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;