Panels: beautifull NRectangleCurved shape - conditionnal with a new settings, default is true.

This commit is contained in:
ItsLemmy
2025-10-30 22:42:12 -04:00
parent 129609ec2c
commit 64dcb0d34e
11 changed files with 346 additions and 35 deletions
+4
View File
@@ -872,6 +872,10 @@
"tooltips": {
"description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren.",
"label": "Tooltips anzeigen"
},
"panels-attached-to-bar": {
"description": "Wenn aktiviert, werden die Panels mit einem schönen, umgekehrten Eckdesign an der Leiste befestigt.",
"label": "Paneele an Stange befestigen"
}
},
"lock-screen": {
+4
View File
@@ -869,6 +869,10 @@
"label": "Disable UI Animations",
"description": "Disable all animations for a faster, more responsive experience."
},
"panels-attached-to-bar": {
"label": "Attach panels to bar",
"description": "When enabled, panels will be attached to the bar with beautiful inverted corner design"
},
"panels-overlay": {
"label": "Open panels in overlay layer",
"description": "Panels will appear above fullscreen windows"
+4
View File
@@ -872,6 +872,10 @@
"tooltips": {
"description": "Activar o desactivar los avisos emergentes en toda la interfaz.",
"label": "Mostrar sugerencias"
},
"panels-attached-to-bar": {
"description": "Cuando está habilitado, los paneles se adjuntarán a la barra con un hermoso diseño de esquina invertida.",
"label": "Adjuntar paneles a la barra"
}
},
"lock-screen": {
+4
View File
@@ -872,6 +872,10 @@
"tooltips": {
"description": "Activer ou désactiver les info-bulles dans toute l'interface.",
"label": "Afficher les infobulles"
},
"panels-attached-to-bar": {
"description": "Lorsque cette option est activée, les panneaux seront attachés à la barre avec un design élégant de coin inversé.",
"label": "Fixer les panneaux à la barre."
}
},
"lock-screen": {
+4
View File
@@ -872,6 +872,10 @@
"tooltips": {
"description": "Ativar ou desativar dicas de ferramentas em toda a interface.",
"label": "Mostrar dicas de ferramenta"
},
"panels-attached-to-bar": {
"description": "Quando ativado, os painéis serão anexados à barra com um belo design de canto invertido.",
"label": "Anexar painéis à barra"
}
},
"lock-screen": {
+4
View File
@@ -872,6 +872,10 @@
"tooltips": {
"description": "启用或禁用整个界面的工具提示。",
"label": "显示工具提示"
},
"panels-attached-to-bar": {
"description": "启用后,面板将以美观的倒角设计附加到栏上。",
"label": "将面板连接到杆上"
}
},
"lock-screen": {
+1
View File
@@ -217,6 +217,7 @@
"fontDefaultScale": 1,
"fontFixedScale": 1,
"tooltipsEnabled": true,
"panelsAttachedToBar": true,
"panelsOverlayLayer": true
},
"brightness": {
+1
View File
@@ -354,6 +354,7 @@ Singleton {
property real fontDefaultScale: 1.0
property real fontFixedScale: 1.0
property bool tooltipsEnabled: true
property bool panelsAttachedToBar: true
property bool panelsOverlayLayer: true
}
@@ -33,6 +33,13 @@ ColumnLayout {
onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
}
NToggle {
label: I18n.tr("settings.user-interface.panels-attached-to-bar.label")
description: I18n.tr("settings.user-interface.panels-attached-to-bar.description")
checked: Settings.data.ui.panelsAttachedToBar
onToggled: checked => Settings.data.ui.panelsAttachedToBar = checked
}
NToggle {
label: I18n.tr("settings.user-interface.panels-overlay.label")
description: I18n.tr("settings.user-interface.panels-overlay.description")
+126 -35
View File
@@ -9,6 +9,7 @@ Loader {
property ShellScreen screen
property bool attachedToBar: Settings.data.ui.panelsAttachedToBar
property bool useOverlay: Settings.data.ui.panelsOverlayLayer
property Component panelContent: null
@@ -39,10 +40,20 @@ Loader {
property bool backgroundClickEnabled: true
// Animation properties
readonly property real originalScale: 0.0
property real scaleValue: originalScale
property real panelBackgroundOpacity: 0
property real panelContentOpacity: 0
property real dimmingOpacity: 0
readonly property string barPosition: Settings.data.bar.position
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property real verticalBarWidth: Style.barHeight
// Effective anchor properties - combines explicit anchors with implicit anchoring from useButtonPosition
readonly property bool effectivePanelAnchorTop: panelAnchorTop || (useButtonPosition && barPosition === "top")
readonly property bool effectivePanelAnchorBottom: panelAnchorBottom || (useButtonPosition && barPosition === "bottom")
readonly property bool effectivePanelAnchorLeft: panelAnchorLeft || (useButtonPosition && barPosition === "left")
readonly property bool effectivePanelAnchorRight: panelAnchorRight || (useButtonPosition && barPosition === "right")
signal opened
signal closed
@@ -97,7 +108,8 @@ Loader {
// -----------------------------------------
function close() {
dimmingOpacity = 0
scaleValue = originalScale
panelBackgroundOpacity = 0
panelContentOpacity = 0
root.closed()
active = false
useButtonPosition = false
@@ -132,10 +144,7 @@ Loader {
PanelWindow {
id: panelWindow
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0))
readonly property real verticalBarWidth: Style.barHeight
Component.onCompleted: {
Logger.d("NPanel", "Opened", root.objectName, "on", screen.name)
@@ -195,12 +204,73 @@ Loader {
}
// The actual panel's content
Rectangle {
NRectangleCurved {
id: panelBackground
color: panelBackgroundColor
radius: Style.radiusL
border.color: panelBorderColor
border.width: Style.borderS
backgroundColor: panelBackgroundColor
topLeftRadius: Style.radiusL
topRightRadius: Style.radiusL
bottomLeftRadius: Style.radiusL
bottomRightRadius: Style.radiusL
// Set inverted corners based on panel anchors and bar position
// Top-left corner
topLeftInverted: {
if (!attachedToBar) return false
// Inverted if panel is anchored to top edge (bar is at top)
if (effectivePanelAnchorTop)
return true
// Or if panel is anchored to left edge (bar is at left)
if (effectivePanelAnchorLeft)
return true
return false
}
topLeftInvertedDirection: effectivePanelAnchorTop ? "horizontal" : "vertical"
// Top-right corner
topRightInverted: {
if (!attachedToBar) return false
// Inverted if panel is anchored to top edge (bar is at top)
if (effectivePanelAnchorTop)
return true
// Or if panel is anchored to right edge (bar is at right)
if (effectivePanelAnchorRight)
return true
return false
}
topRightInvertedDirection: effectivePanelAnchorTop ? "horizontal" : "vertical"
// Bottom-left corner
bottomLeftInverted: {
if (!attachedToBar) return false
// Inverted if panel is anchored to bottom edge (bar is at bottom)
if (effectivePanelAnchorBottom)
return true
// Or if panel is anchored to left edge (bar is at left)
if (effectivePanelAnchorLeft)
return true
return false
}
bottomLeftInvertedDirection: effectivePanelAnchorBottom ? "horizontal" : "vertical"
// Bottom-right corner
bottomRightInverted: {
if (!attachedToBar) return false
// Inverted if panel is anchored to bottom edge (bar is at bottom)
if (effectivePanelAnchorBottom)
return true
// Or if panel is anchored to right edge (bar is at right)
if (effectivePanelAnchorRight)
return true
return false
}
bottomRightInvertedDirection: effectivePanelAnchorBottom ? "horizontal" : "vertical"
// Dragging support
property bool draggable: root.draggable
@@ -229,7 +299,7 @@ Loader {
return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2)
}
scale: root.scaleValue
opacity: root.panelBackgroundOpacity
x: isDragged ? manualX : calculatedX
y: isDragged ? manualY : calculatedY
@@ -240,11 +310,12 @@ Loader {
if (!barIsVisible) {
return 0
}
switch (barPosition) {
case "top":
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
return (Style.barHeight + (attachedToBar ? 0 : Style.marginS)) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
default:
return Style.marginS
return attachedToBar ? 0 : Style.marginS
}
}
@@ -254,9 +325,9 @@ Loader {
}
switch (barPosition) {
case "bottom":
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
return (Style.barHeight + (attachedToBar ? 0 : Style.marginS)) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
default:
return Style.marginS
return attachedToBar ? 0 : Style.marginS
}
}
@@ -266,9 +337,9 @@ Loader {
}
switch (barPosition) {
case "left":
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
return (Style.barHeight + (attachedToBar ? 0 : Style.marginS)) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
default:
return Style.marginS
return attachedToBar ? 0 : Style.marginS
}
}
@@ -278,9 +349,9 @@ Loader {
}
switch (barPosition) {
case "right":
return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
return (Style.barHeight + (attachedToBar ? 0 : Style.marginS)) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
default:
return Style.marginS
return attachedToBar ? 0 : Style.marginS
}
}
@@ -300,7 +371,7 @@ Loader {
}
// No fixed anchoring
if (isVertical) {
if (barIsVertical) {
// Vertical bar
if (barPosition === "right") {
// To the left of the right bar
@@ -341,7 +412,7 @@ Loader {
}
// No fixed anchoring
if (isVertical) {
if (barIsVertical) {
// Vertical bar
if (useButtonPosition) {
// Position panel relative to button
@@ -368,7 +439,28 @@ Loader {
// Animate in when component is completed
Component.onCompleted: {
root.scaleValue = 1.0
// Start invisible
// Use a timer to delay the animation start, allowing QML to properly set up initial state
fadeInTimer.start()
}
Timer {
id: fadeInTimer
interval: 1
repeat: false
onTriggered: {
// Fade in background
root.panelBackgroundOpacity = 1.0
}
}
// Timer to fade in content after slide animation completes
Timer {
id: contentFadeInTimer
interval: Style.animationFast
repeat: false
running: true
onTriggered: root.panelContentOpacity = 1.0
}
// Reset drag position when panel closes
@@ -384,17 +476,10 @@ Loader {
anchors.fill: parent
}
// Animation behaviors
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutExpo
}
}
// Animation behavior
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
duration: Style.animationFast
easing.type: Easing.OutQuad
}
}
@@ -403,6 +488,14 @@ Loader {
id: panelContentLoader
anchors.fill: parent
sourceComponent: root.panelContent
opacity: root.panelContentOpacity
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.OutQuad
}
}
}
// Handle drag move on the whole panel area
@@ -460,8 +553,7 @@ Loader {
anchors.margins: 0
color: Color.transparent
border.color: Color.mPrimary
border.width: Style.borderL
radius: parent.radius
border.width: Style.borderM
visible: panelBackground.isDragged && dragHandler.active
opacity: 0.8
z: 3000
@@ -473,7 +565,6 @@ Loader {
color: Color.transparent
border.color: Color.mPrimary
border.width: Style.borderS
radius: parent.radius
opacity: 0.3
}
}
+187
View File
@@ -0,0 +1,187 @@
import QtQuick
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
property color backgroundColor: "white"
// 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)
// Simple rectangle for non-inverted corners (better performance)
Rectangle {
id: simpleBackground
anchors.fill: parent
color: root.backgroundColor
radius: topLeftRadius // Use topLeftRadius as default
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
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, y + h)
ctx.lineTo(x, y + h + bottomLeftRadius)
ctx.quadraticCurveTo(x, y + h, x + bottomLeftRadius, y + h)
ctx.lineTo(x, y + h)
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()
}
}
// 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()
}