mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-05-24 23:25:32 +00:00
210 lines
8.0 KiB
QML
210 lines
8.0 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import qs.Commons
|
|
|
|
Item {
|
|
id: root
|
|
|
|
// Corner radius properties
|
|
property real topLeftRadius: 0
|
|
property real topRightRadius: 0
|
|
property real bottomLeftRadius: 0
|
|
property real bottomRightRadius: 0
|
|
|
|
// Inverted corner properties (concave instead of convex)
|
|
property bool topLeftInverted: false
|
|
property bool topRightInverted: false
|
|
property bool bottomLeftInverted: false
|
|
property bool bottomRightInverted: false
|
|
|
|
// Direction for inverted corners: "horizontal" or "vertical"
|
|
// horizontal: curves left/right (extends beyond left/right edges)
|
|
// vertical: curves up/down (extends beyond top/bottom edges)
|
|
property string topLeftInvertedDirection: "horizontal" // default: curves left
|
|
property string topRightInvertedDirection: "horizontal" // default: curves right
|
|
property string bottomLeftInvertedDirection: "horizontal" // default: curves left
|
|
property string bottomRightInvertedDirection: "horizontal" // default: curves right
|
|
|
|
// Background color and borders
|
|
property color backgroundColor: Color.mPrimary
|
|
property color borderColor: Color.mOutline
|
|
property int borderWidth: Style.borderS
|
|
|
|
// Check if any corner is inverted
|
|
readonly property bool hasInvertedCorners: topLeftInverted || topRightInverted || bottomLeftInverted || bottomRightInverted
|
|
|
|
// Calculate padding needed for inverted corners based on their direction
|
|
readonly property real topPadding: Math.max((topLeftInverted && topLeftInvertedDirection === "vertical") ? topLeftRadius : 0, (topRightInverted && topRightInvertedDirection === "vertical") ? topRightRadius : 0)
|
|
readonly property real bottomPadding: Math.max((bottomLeftInverted && bottomLeftInvertedDirection === "vertical") ? bottomLeftRadius : 0, (bottomRightInverted && bottomRightInvertedDirection === "vertical") ? bottomRightRadius : 0)
|
|
readonly property real leftPadding: Math.max((topLeftInverted && topLeftInvertedDirection === "horizontal") ? topLeftRadius : 0, (bottomLeftInverted && bottomLeftInvertedDirection === "horizontal") ? bottomLeftRadius : 0)
|
|
readonly property real rightPadding: Math.max((topRightInverted && topRightInvertedDirection === "horizontal") ? topRightRadius : 0, (bottomRightInverted && bottomRightInvertedDirection === "horizontal") ? bottomRightRadius : 0)
|
|
|
|
// Background layer
|
|
Item {
|
|
id: backgroundLayer
|
|
anchors.fill: parent
|
|
z: 0
|
|
|
|
// Simple rectangle for non-inverted corners (better performance)
|
|
Rectangle {
|
|
id: simpleBackground
|
|
anchors.fill: parent
|
|
color: root.backgroundColor
|
|
radius: topLeftRadius // Use topLeftRadius as default
|
|
border.width: borderWidth
|
|
border.color: borderColor
|
|
visible: !root.hasInvertedCorners
|
|
|
|
topLeftRadius: root.topLeftRadius
|
|
topRightRadius: root.topRightRadius
|
|
bottomLeftRadius: root.bottomLeftRadius
|
|
bottomRightRadius: root.bottomRightRadius
|
|
}
|
|
|
|
// Background with custom corners (for inverted corners)
|
|
Canvas {
|
|
id: background
|
|
anchors.fill: parent
|
|
anchors.topMargin: -root.topPadding
|
|
anchors.bottomMargin: -root.bottomPadding
|
|
anchors.leftMargin: -root.leftPadding
|
|
anchors.rightMargin: -root.rightPadding
|
|
visible: root.hasInvertedCorners
|
|
|
|
antialiasing: true
|
|
renderTarget: Canvas.FramebufferObject
|
|
smooth: true
|
|
|
|
onPaint: {
|
|
var ctx = getContext("2d")
|
|
ctx.reset()
|
|
|
|
// Adjust coordinates to account for inverted corner padding
|
|
var x = root.leftPadding
|
|
var y = root.topPadding
|
|
var w = width - root.leftPadding - root.rightPadding
|
|
var h = height - root.topPadding - root.bottomPadding
|
|
|
|
ctx.fillStyle = root.backgroundColor
|
|
ctx.beginPath()
|
|
|
|
// Start from top left
|
|
if (topLeftInverted) {
|
|
if (topLeftInvertedDirection === "vertical") {
|
|
ctx.moveTo(x, y)
|
|
} else {
|
|
ctx.moveTo(x + topLeftRadius, y)
|
|
}
|
|
} else {
|
|
ctx.moveTo(x + topLeftRadius, y)
|
|
}
|
|
|
|
// Top edge and top right corner
|
|
if (topRightInverted) {
|
|
if (topRightInvertedDirection === "horizontal") {
|
|
// Curves to the right
|
|
ctx.lineTo(x + w, y)
|
|
ctx.lineTo(x + w + topRightRadius, y)
|
|
ctx.quadraticCurveTo(x + w, y, x + w, y + topRightRadius)
|
|
} else {
|
|
// Curves upward
|
|
ctx.lineTo(x + w, y)
|
|
ctx.lineTo(x + w, y - topRightRadius)
|
|
ctx.quadraticCurveTo(x + w, y, x + w - topRightRadius, y)
|
|
ctx.lineTo(x + w, y)
|
|
ctx.lineTo(x + w, y + topRightRadius)
|
|
}
|
|
} else {
|
|
ctx.lineTo(x + w - topRightRadius, y)
|
|
ctx.arcTo(x + w, y, x + w, y + topRightRadius, topRightRadius)
|
|
}
|
|
|
|
// Right edge and bottom right corner
|
|
if (bottomRightInverted) {
|
|
if (bottomRightInvertedDirection === "horizontal") {
|
|
// Curves to the right
|
|
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
|
ctx.quadraticCurveTo(x + w, y + h, x + w + bottomRightRadius, y + h)
|
|
ctx.lineTo(x + w, y + h)
|
|
ctx.lineTo(x + w - bottomRightRadius, y + h)
|
|
} else {
|
|
// Curves downward
|
|
ctx.lineTo(x + w, y + h)
|
|
ctx.lineTo(x + w, y + h + bottomRightRadius)
|
|
ctx.quadraticCurveTo(x + w, y + h, x + w - bottomRightRadius, y + h)
|
|
}
|
|
} else {
|
|
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
|
ctx.arcTo(x + w, y + h, x + w - bottomRightRadius, y + h, bottomRightRadius)
|
|
}
|
|
|
|
// Bottom edge and bottom left corner
|
|
if (bottomLeftInverted) {
|
|
if (bottomLeftInvertedDirection === "horizontal") {
|
|
// Curves to the left
|
|
ctx.lineTo(x + bottomLeftRadius, y + h)
|
|
ctx.lineTo(x - bottomLeftRadius, y + h)
|
|
ctx.quadraticCurveTo(x, y + h, x, y + h - bottomLeftRadius)
|
|
} else {
|
|
// Curves downward
|
|
ctx.lineTo(x + bottomLeftRadius, y + h)
|
|
ctx.quadraticCurveTo(x, y + h, x, y + h + bottomLeftRadius)
|
|
ctx.lineTo(x, y + h + bottomLeftRadius)
|
|
}
|
|
} else {
|
|
ctx.lineTo(x + bottomLeftRadius, y + h)
|
|
ctx.arcTo(x, y + h, x, y + h - bottomLeftRadius, bottomLeftRadius)
|
|
}
|
|
|
|
// Left edge and back to top left corner
|
|
if (topLeftInverted) {
|
|
if (topLeftInvertedDirection === "horizontal") {
|
|
// Curves to the left
|
|
ctx.lineTo(x, y + topLeftRadius)
|
|
ctx.quadraticCurveTo(x, y, x - topLeftRadius, y)
|
|
ctx.lineTo(x, y)
|
|
ctx.lineTo(x + topLeftRadius, y)
|
|
} else {
|
|
// Curves upward
|
|
ctx.lineTo(x, y + topLeftRadius)
|
|
ctx.lineTo(x, y)
|
|
ctx.lineTo(x, y - topLeftRadius)
|
|
ctx.quadraticCurveTo(x, y, x + topLeftRadius, y)
|
|
}
|
|
} else {
|
|
ctx.lineTo(x, y + topLeftRadius)
|
|
ctx.arcTo(x, y, x + topLeftRadius, y, topLeftRadius)
|
|
}
|
|
|
|
ctx.closePath()
|
|
ctx.fill()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Content layer: for child elements
|
|
default property alias contentChildren: contentLayer.data
|
|
Item {
|
|
id: contentLayer
|
|
anchors.fill: parent
|
|
z: 1
|
|
}
|
|
|
|
// Trigger repaint when properties change
|
|
onTopLeftRadiusChanged: background.requestPaint()
|
|
onTopRightRadiusChanged: background.requestPaint()
|
|
onBottomLeftRadiusChanged: background.requestPaint()
|
|
onBottomRightRadiusChanged: background.requestPaint()
|
|
onTopLeftInvertedChanged: background.requestPaint()
|
|
onTopRightInvertedChanged: background.requestPaint()
|
|
onBottomLeftInvertedChanged: background.requestPaint()
|
|
onBottomRightInvertedChanged: background.requestPaint()
|
|
onTopLeftInvertedDirectionChanged: background.requestPaint()
|
|
onTopRightInvertedDirectionChanged: background.requestPaint()
|
|
onBottomLeftInvertedDirectionChanged: background.requestPaint()
|
|
onBottomRightInvertedDirectionChanged: background.requestPaint()
|
|
onBackgroundColorChanged: background.requestPaint()
|
|
onWidthChanged: background.requestPaint()
|
|
onHeightChanged: background.requestPaint()
|
|
}
|