1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-14 01:32:29 -04:00

animations/ripple: clean up effect and apply more universally

This commit is contained in:
bbedward
2026-02-10 12:48:12 -05:00
parent 5a0bb260b4
commit 3d0ee9d72b
49 changed files with 980 additions and 805 deletions

View File

@@ -30,6 +30,12 @@ Rectangle {
return mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency); return mouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
} }
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: root.radius
}
Rectangle { Rectangle {
id: indexBadge id: indexBadge
anchors.left: parent.left anchors.left: parent.left
@@ -138,6 +144,10 @@ Rectangle {
anchors.rightMargin: 80 anchors.rightMargin: 80
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
const pos = mouseArea.mapToItem(root, mouse.x, mouse.y);
rippleLayer.trigger(pos.x, pos.y);
}
onClicked: copyRequested() onClicked: copyRequested()
} }
} }

View File

@@ -53,6 +53,12 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent" color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: root.radius
}
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
@@ -99,6 +105,10 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton)
rippleLayer.trigger(mouse.x, mouse.y);
}
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
var scenePos = mapToItem(null, mouse.x, mouse.y); var scenePos = mapToItem(null, mouse.x, mouse.y);

View File

@@ -475,6 +475,12 @@ Popup {
} }
} }
DankRipple {
id: menuItemRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: itemMouseArea id: itemMouseArea
anchors.fill: parent anchors.fill: parent
@@ -484,6 +490,7 @@ Popup {
root.keyboardNavigation = false; root.keyboardNavigation = false;
root.selectedMenuIndex = menuItemDelegate.itemIndex; root.selectedMenuIndex = menuItemDelegate.itemIndex;
} }
onPressed: mouse => menuItemRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
var menuItem = menuItemDelegate.modelData; var menuItem = menuItemDelegate.modelData;
if (menuItem.action) if (menuItem.action)

View File

@@ -53,6 +53,12 @@ Rectangle {
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent" color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
radius: Theme.cornerRadius radius: Theme.cornerRadius
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: itemArea id: itemArea
z: 1 z: 1
@@ -62,6 +68,10 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton)
rippleLayer.trigger(mouse.x, mouse.y);
}
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
var scenePos = mapToItem(null, mouse.x, mouse.y); var scenePos = mapToItem(null, mouse.x, mouse.y);

View File

@@ -82,6 +82,12 @@ Rectangle {
return ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].indexOf(ext) >= 0; return ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].indexOf(ext) >= 0;
} }
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: root.radius
}
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: 4 anchors.margins: 4
@@ -193,6 +199,10 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton)
rippleLayer.trigger(mouse.x, mouse.y);
}
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
var scenePos = mapToItem(null, mouse.x, mouse.y); var scenePos = mapToItem(null, mouse.x, mouse.y);

View File

@@ -239,7 +239,6 @@ Rectangle {
"icon": "computer", "icon": "computer",
"collapsedByDefault": true, "collapsedByDefault": true,
"children": [ "children": [
{ {
"id": "audio", "id": "audio",
"text": I18n.tr("Audio"), "text": I18n.tr("Audio"),
@@ -702,6 +701,12 @@ Rectangle {
return "transparent"; return "transparent";
} }
DankRipple {
id: resultRipple
rippleColor: root.searchSelectedIndex === resultDelegate.index ? Theme.buttonText : Theme.surfaceText
cornerRadius: resultDelegate.radius
}
Row { Row {
id: resultContent id: resultContent
anchors.left: parent.left anchors.left: parent.left
@@ -749,6 +754,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => resultRipple.trigger(mouse.x, mouse.y)
onClicked: root.selectSearchResult(resultDelegate.modelData) onClicked: root.selectSearchResult(resultDelegate.modelData)
} }
@@ -825,6 +831,12 @@ Rectangle {
return "transparent"; return "transparent";
} }
DankRipple {
id: categoryRipple
rippleColor: categoryRow.isActive ? Theme.buttonText : Theme.surfaceText
cornerRadius: categoryRow.radius
}
Row { Row {
id: categoryRowContent id: categoryRowContent
anchors.left: parent.left anchors.left: parent.left
@@ -864,6 +876,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => categoryRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
root.keyboardHighlightIndex = -1; root.keyboardHighlightIndex = -1;
if (categoryDelegate.modelData.children) { if (categoryDelegate.modelData.children) {
@@ -915,6 +928,12 @@ Rectangle {
return "transparent"; return "transparent";
} }
DankRipple {
id: childRipple
rippleColor: childDelegate.isActive ? Theme.buttonText : Theme.surfaceText
cornerRadius: childDelegate.radius
}
Row { Row {
id: childRowContent id: childRowContent
anchors.left: parent.left anchors.left: parent.left
@@ -943,6 +962,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => childRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
root.keyboardHighlightIndex = -1; root.keyboardHighlightIndex = -1;
root.tabChangeRequested(childDelegate.modelData.tabIndex); root.tabChangeRequested(childDelegate.modelData.tabIndex);

View File

@@ -101,12 +101,18 @@ Rectangle {
} }
} }
DankRipple {
id: ripple
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: root.enabled enabled: root.enabled
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
onClicked: root.clicked() onClicked: root.clicked()
} }

View File

@@ -11,19 +11,11 @@ Rectangle {
property string iconName: "" property string iconName: ""
property string text: "" property string text: ""
signal pressed() signal pressed
height: 34 height: 34
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: mouseArea.containsMouse ? Qt.rgba( color: mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
Theme.primary.r,
Theme.primary.g,
Theme.primary.b,
0.12) : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.5)
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
@@ -44,12 +36,20 @@ Rectangle {
} }
} }
DankRipple {
id: ripple
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: root.pressed() onPressed: mouse => {
ripple.trigger(mouse.x, mouse.y);
root.pressed();
}
} }
} }

View File

@@ -61,11 +61,17 @@ Rectangle {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (AudioService.source && AudioService.source.audio) { if (AudioService.source && AudioService.source.audio) {
AudioService.source.audio.muted = !AudioService.source.audio.muted; AudioService.source.audio.muted = !AudioService.source.audio.muted;
@@ -126,15 +132,15 @@ Rectangle {
function normalizePinList(value) { function normalizePinList(value) {
if (Array.isArray(value)) if (Array.isArray(value))
return value.filter(v => v) return value.filter(v => v);
if (typeof value === "string" && value.length > 0) if (typeof value === "string" && value.length > 0)
return [value] return [value];
return [] return [];
} }
function getPinnedInputs() { function getPinnedInputs() {
const pins = SettingsData.audioInputDevicePins || {} const pins = SettingsData.audioInputDevicePins || {};
return normalizePinList(pins["preferredInput"]) return normalizePinList(pins["preferredInput"]);
} }
Column { Column {
@@ -153,14 +159,14 @@ Rectangle {
let sorted = [...nodes]; let sorted = [...nodes];
sorted.sort((a, b) => { sorted.sort((a, b) => {
// Pinned device first // Pinned device first
const aPinnedIndex = pinnedList.indexOf(a.name) const aPinnedIndex = pinnedList.indexOf(a.name);
const bPinnedIndex = pinnedList.indexOf(b.name) const bPinnedIndex = pinnedList.indexOf(b.name);
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) { if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
if (aPinnedIndex === -1) if (aPinnedIndex === -1)
return 1 return 1;
if (bPinnedIndex === -1) if (bPinnedIndex === -1)
return -1 return -1;
return aPinnedIndex - bPinnedIndex return aPinnedIndex - bPinnedIndex;
} }
// Then active device // Then active device
if (a === AudioService.source && b !== AudioService.source) if (a === AudioService.source && b !== AudioService.source)
@@ -276,38 +282,53 @@ Rectangle {
} }
} }
DankRipple {
id: pinRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
let pinnedList = audioContent.normalizePinList(pins["preferredInput"]) let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
const pinIndex = pinnedList.indexOf(modelData.name) const pinIndex = pinnedList.indexOf(modelData.name);
if (pinIndex !== -1) { if (pinIndex !== -1) {
pinnedList.splice(pinIndex, 1) pinnedList.splice(pinIndex, 1);
} else { } else {
pinnedList.unshift(modelData.name) pinnedList.unshift(modelData.name);
if (pinnedList.length > audioContent.maxPinnedInputs) if (pinnedList.length > audioContent.maxPinnedInputs)
pinnedList = pinnedList.slice(0, audioContent.maxPinnedInputs) pinnedList = pinnedList.slice(0, audioContent.maxPinnedInputs);
} }
if (pinnedList.length > 0) if (pinnedList.length > 0)
pins["preferredInput"] = pinnedList pins["preferredInput"] = pinnedList;
else else
delete pins["preferredInput"] delete pins["preferredInput"];
SettingsData.set("audioInputDevicePins", pins) SettingsData.set("audioInputDevicePins", pins);
} }
} }
} }
DankRipple {
id: deviceRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: deviceMouseArea id: deviceMouseArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: pinInputRow.width + Theme.spacingS * 4 anchors.rightMargin: pinInputRow.width + Theme.spacingS * 4
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
let mapped = mapToItem(parent, mouse.x, mouse.y);
deviceRipple.trigger(mapped.x, mapped.y);
}
onClicked: { onClicked: {
if (modelData) { if (modelData) {
Pipewire.preferredDefaultAudioSource = modelData; Pipewire.preferredDefaultAudioSource = modelData;

View File

@@ -61,11 +61,17 @@ Rectangle {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
DankRipple {
id: muteRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => muteRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (AudioService.sink && AudioService.sink.audio) { if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = !AudioService.sink.audio.muted; AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
@@ -184,6 +190,7 @@ Rectangle {
} }
delegate: Rectangle { delegate: Rectangle {
id: outputDelegate
required property var modelData required property var modelData
required property int index required property int index
@@ -194,6 +201,11 @@ Rectangle {
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0 border.width: 0
DankRipple {
id: deviceRipple
cornerRadius: outputDelegate.radius
}
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -288,9 +300,15 @@ Rectangle {
} }
} }
DankRipple {
id: pinRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {})); const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]); let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
@@ -320,6 +338,10 @@ Rectangle {
anchors.rightMargin: pinOutputRow.width + Theme.spacingS * 4 anchors.rightMargin: pinOutputRow.width + Theme.spacingS * 4
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
let mapped = deviceMouseArea.mapToItem(outputDelegate, mouse.x, mouse.y);
deviceRipple.trigger(mapped.x, mapped.y);
}
onClicked: { onClicked: {
if (modelData) { if (modelData) {
Pipewire.preferredDefaultAudioSink = modelData; Pipewire.preferredDefaultAudioSink = modelData;
@@ -428,12 +450,18 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: appIconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0) color: appIconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
DankRipple {
id: appMuteRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: appIconArea id: appIconArea
anchors.fill: parent anchors.fill: parent
visible: modelData !== null visible: modelData !== null
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => appMuteRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (modelData) { if (modelData) {
SessionData.suppressOSDTemporarily(); SessionData.suppressOSDTemporarily();

View File

@@ -293,6 +293,11 @@ Item {
visible: modelData.name === currentCodec visible: modelData.name === currentCodec
} }
DankRipple {
id: codecRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: codecMouseArea id: codecMouseArea
@@ -300,6 +305,7 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: modelData.name !== currentCodec && !isLoading enabled: modelData.name !== currentCodec && !isLoading
onPressed: mouse => codecRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
selectCodec(modelData.profile); selectCodec(modelData.profile);
} }

View File

@@ -139,12 +139,18 @@ Rectangle {
} }
} }
DankRipple {
id: scanRipple
cornerRadius: scanButton.radius
}
MouseArea { MouseArea {
id: scanMouseArea id: scanMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
enabled: scanButton.adapterEnabled enabled: scanButton.adapterEnabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onPressed: mouse => scanRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (!BluetoothService.adapter) if (!BluetoothService.adapter)
return; return;
@@ -399,12 +405,21 @@ Rectangle {
} }
} }
DankRipple {
id: deviceRipple
cornerRadius: pairedDelegate.radius
}
MouseArea { MouseArea {
id: deviceMouseArea id: deviceMouseArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: pairedOptionsButton.width + Theme.spacingM + pinBluetoothRow.width + Theme.spacingS * 4 anchors.rightMargin: pairedOptionsButton.width + Theme.spacingM + pinBluetoothRow.width + Theme.spacingS * 4
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
const pos = mapToItem(pairedDelegate, mouse.x, mouse.y);
deviceRipple.trigger(pos.x, pos.y);
}
onClicked: { onClicked: {
if (pairedDelegate.isConnected) { if (pairedDelegate.isConnected) {
pairedDelegate.modelData.disconnect(); pairedDelegate.modelData.disconnect();
@@ -547,12 +562,18 @@ Rectangle {
font.weight: Font.Medium font.weight: Font.Medium
} }
DankRipple {
id: availableRipple
cornerRadius: availableDelegate.radius
}
MouseArea { MouseArea {
id: availableMouseArea id: availableMouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: availableDelegate.isInteractive ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: availableDelegate.isInteractive ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: availableDelegate.isInteractive enabled: availableDelegate.isInteractive
onPressed: mouse => availableRipple.trigger(mouse.x, mouse.y)
onClicked: root.handlePairDevice(availableDelegate.modelData) onClicked: root.handlePairDevice(availableDelegate.modelData)
} }
} }

View File

@@ -211,9 +211,15 @@ Rectangle {
} }
} }
DankRipple {
id: pinRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: root.togglePinToScreen() onClicked: root.togglePinToScreen()
} }
} }
@@ -443,9 +449,15 @@ Rectangle {
} }
} }
DankRipple {
id: expToggleRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => expToggleRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const currentState = SessionData.getBrightnessExponential(modelData.name); const currentState = SessionData.getBrightnessExponential(modelData.name);
SessionData.setBrightnessExponential(modelData.name, !currentState); SessionData.setBrightnessExponential(modelData.name, !currentState);
@@ -454,12 +466,18 @@ Rectangle {
} }
} }
DankRipple {
id: deviceRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: 28 anchors.bottomMargin: 28
anchors.rightMargin: SessionData.getBrightnessExponential(modelData.name) ? 145 : 0 anchors.rightMargin: SessionData.getBrightnessExponential(modelData.name) ? 145 : 0
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => deviceRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pinKey = root.getScreenPinKey(); const pinKey = root.getScreenPinKey();
if (pinKey.length > 0 && modelData.name !== currentDeviceName) { if (pinKey.length > 0 && modelData.name !== currentDeviceName) {

View File

@@ -155,10 +155,16 @@ Rectangle {
} }
} }
DankRipple {
id: mountRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => mountRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
currentMountPath = modelData.mount; currentMountPath = modelData.mount;
mountPathChanged(modelData.mount); mountPathChanged(modelData.mount);

View File

@@ -344,12 +344,18 @@ Rectangle {
} }
} }
DankRipple {
id: wiredRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: wiredNetworkMouseArea id: wiredNetworkMouseArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => wiredRipple.trigger(mouse.x, mouse.y)
onClicked: function (event) { onClicked: function (event) {
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) { if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
NetworkService.connectToSpecificWiredConfig(modelData.uuid); NetworkService.connectToSpecificWiredConfig(modelData.uuid);
@@ -467,15 +473,15 @@ Rectangle {
function normalizePinList(value) { function normalizePinList(value) {
if (Array.isArray(value)) if (Array.isArray(value))
return value.filter(v => v) return value.filter(v => v);
if (typeof value === "string" && value.length > 0) if (typeof value === "string" && value.length > 0)
return [value] return [value];
return [] return [];
} }
function getPinnedNetworks() { function getPinnedNetworks() {
const pins = SettingsData.wifiNetworkPins || {} const pins = SettingsData.wifiNetworkPins || {};
return normalizePinList(pins["preferredWifi"]) return normalizePinList(pins["preferredWifi"]);
} }
property var frozenNetworks: [] property var frozenNetworks: []
@@ -483,18 +489,18 @@ Rectangle {
property var sortedNetworks: { property var sortedNetworks: {
const ssid = NetworkService.currentWifiSSID; const ssid = NetworkService.currentWifiSSID;
const networks = NetworkService.wifiNetworks; const networks = NetworkService.wifiNetworks;
const pinnedList = getPinnedNetworks() const pinnedList = getPinnedNetworks();
let sorted = [...networks]; let sorted = [...networks];
sorted.sort((a, b) => { sorted.sort((a, b) => {
const aPinnedIndex = pinnedList.indexOf(a.ssid) const aPinnedIndex = pinnedList.indexOf(a.ssid);
const bPinnedIndex = pinnedList.indexOf(b.ssid) const bPinnedIndex = pinnedList.indexOf(b.ssid);
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) { if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
if (aPinnedIndex === -1) if (aPinnedIndex === -1)
return 1 return 1;
if (bPinnedIndex === -1) if (bPinnedIndex === -1)
return -1 return -1;
return aPinnedIndex - bPinnedIndex return aPinnedIndex - bPinnedIndex;
} }
if (a.ssid === ssid) if (a.ssid === ssid)
return -1; return -1;
@@ -677,38 +683,50 @@ Rectangle {
} }
} }
DankRipple {
id: pinRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
let pinnedList = wifiContent.normalizePinList(pins["preferredWifi"]) let pinnedList = wifiContent.normalizePinList(pins["preferredWifi"]);
const pinIndex = pinnedList.indexOf(modelData.ssid) const pinIndex = pinnedList.indexOf(modelData.ssid);
if (pinIndex !== -1) { if (pinIndex !== -1) {
pinnedList.splice(pinIndex, 1) pinnedList.splice(pinIndex, 1);
} else { } else {
pinnedList.unshift(modelData.ssid) pinnedList.unshift(modelData.ssid);
if (pinnedList.length > wifiContent.maxPinnedNetworks) if (pinnedList.length > wifiContent.maxPinnedNetworks)
pinnedList = pinnedList.slice(0, wifiContent.maxPinnedNetworks) pinnedList = pinnedList.slice(0, wifiContent.maxPinnedNetworks);
} }
if (pinnedList.length > 0) if (pinnedList.length > 0)
pins["preferredWifi"] = pinnedList pins["preferredWifi"] = pinnedList;
else else
delete pins["preferredWifi"] delete pins["preferredWifi"];
SettingsData.set("wifiNetworkPins", pins) SettingsData.set("wifiNetworkPins", pins);
} }
} }
} }
DankRipple {
id: wifiRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: networkMouseArea id: networkMouseArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS + pinWifiRow.width + Theme.spacingS * 4 anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS + pinWifiRow.width + Theme.spacingS * 4
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => wifiRipple.trigger(mouse.x, mouse.y)
onClicked: function (event) { onClicked: function (event) {
if (modelData.ssid !== NetworkService.currentWifiSSID) { if (modelData.ssid !== NetworkService.currentWifiSSID) {
if (modelData.secured && !modelData.saved) { if (modelData.secured && !modelData.saved) {

View File

@@ -22,12 +22,18 @@ Row {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0) color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
visible: defaultSink !== null visible: defaultSink !== null
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (defaultSink) { if (defaultSink) {
SessionData.suppressOSDTemporarily(); SessionData.suppressOSDTemporarily();

View File

@@ -91,12 +91,18 @@ Row {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0) color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: DisplayService.devices && DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: DisplayService.devices && DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (DisplayService.devices && DisplayService.devices.length > 1) { if (DisplayService.devices && DisplayService.devices.length > 1) {
root.iconClicked(); root.iconClicked();

View File

@@ -72,6 +72,11 @@ Rectangle {
} }
} }
DankRipple {
id: bodyRipple
cornerRadius: root.radius
}
Row { Row {
id: row id: row
anchors.fill: parent anchors.fill: parent
@@ -112,11 +117,17 @@ Rectangle {
color: isActive ? _tileIconActive : _tileIconInactive color: isActive ? _tileIconActive : _tileIconInactive
} }
DankRipple {
id: tileRipple
cornerRadius: _tileRadius
}
MouseArea { MouseArea {
id: tileMouse id: tileMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => tileRipple.trigger(mouse.x, mouse.y)
onClicked: root.toggled() onClicked: root.toggled()
} }
} }
@@ -167,7 +178,11 @@ Rectangle {
rightHoverOverlay.opacity = 0.0; rightHoverOverlay.opacity = 0.0;
rightHoverOverlay.visible = false; rightHoverOverlay.visible = false;
} }
onPressed: rightHoverOverlay.opacity = 0.16 onPressed: mouse => {
const pos = mapToItem(root, mouse.x, mouse.y);
bodyRipple.trigger(pos.x, pos.y);
rightHoverOverlay.opacity = 0.16;
}
onReleased: rightHoverOverlay.opacity = containsMouse ? 0.08 : 0.0 onReleased: rightHoverOverlay.opacity = containsMouse ? 0.08 : 0.0
onClicked: root.expandClicked() onClicked: root.expandClicked()
onWheel: function (ev) { onWheel: function (ev) {

View File

@@ -22,12 +22,18 @@ Row {
radius: (Theme.iconSize + Theme.spacingS * 2) / 2 radius: (Theme.iconSize + Theme.spacingS * 2) / 2
color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0) color: iconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea { MouseArea {
id: iconArea id: iconArea
anchors.fill: parent anchors.fill: parent
visible: defaultSource !== null visible: defaultSource !== null
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (defaultSource) { if (defaultSource) {
SessionData.suppressOSDTemporarily(); SessionData.suppressOSDTemporarily();

View File

@@ -89,12 +89,18 @@ Rectangle {
} }
} }
DankRipple {
id: ripple
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: root.enabled enabled: root.enabled
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
onClicked: root.clicked() onClicked: root.clicked()
} }

View File

@@ -105,12 +105,18 @@ Rectangle {
} }
} }
DankRipple {
id: ripple
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: root.enabled enabled: root.enabled
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
onClicked: root.clicked() onClicked: root.clicked()
} }

View File

@@ -66,12 +66,18 @@ Rectangle {
onRotationCompleted: root.iconRotationCompleted() onRotationCompleted: root.iconRotationCompleted()
} }
DankRipple {
id: ripple
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: root.enabled enabled: root.enabled
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
onClicked: root.clicked() onClicked: root.clicked()
} }

View File

@@ -110,12 +110,18 @@ Rectangle {
} }
} }
DankRipple {
id: ripple
cornerRadius: root.radius
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: root.enabled enabled: root.enabled
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
onClicked: root.clicked() onClicked: root.clicked()
} }

View File

@@ -1,29 +1,23 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { BasePill {
id: root id: root
enableBackgroundHover: false
enableCursor: false
section: "left"
property var widgetData: null property var widgetData: null
property var barConfig: null
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "left"
property var parentScreen
property var hoveredItem: null property var hoveredItem: null
property var topBar: null property var topBar: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property bool isAutoHideBar: false property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null) property Item windowRoot: (Window.window ? Window.window.contentItem : null)
property int draggedIndex: -1 property int draggedIndex: -1
@@ -66,7 +60,7 @@ Item {
readonly property real barY: barBounds.y readonly property real barY: barBounds.y
readonly property real minTooltipY: { readonly property real minTooltipY: {
if (!parentScreen || !isVertical) { if (!parentScreen || !isVerticalOrientation) {
return 0; return 0;
} }
@@ -322,7 +316,9 @@ Item {
} }
function applyOverflow(baseResult) { function applyOverflow(baseResult) {
const { items } = baseResult; const {
items
} = baseResult;
const maxPinned = root.maxVisibleApps; const maxPinned = root.maxVisibleApps;
const maxRunning = root.maxVisibleRunningApps; const maxRunning = root.maxVisibleRunningApps;
@@ -403,110 +399,19 @@ Item {
Component.onCompleted: updateModel() Component.onCompleted: updateModel()
readonly property int calculatedSize: {
const count = dockItems.length;
if (count === 0)
return 0;
if (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) {
return count * 24 + (count - 1) * Theme.spacingXS + horizontalPadding * 2;
} else {
return count * (24 + Theme.spacingXS + 120) + (count - 1) * Theme.spacingXS + horizontalPadding * 2;
}
}
readonly property real realCalculatedSize: {
let total = horizontalPadding * 2;
const compact = (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode);
for (let i = 0; i < dockItems.length; i++) {
const item = dockItems[i];
const isInOverflow = item.isInOverflow === true;
if (isInOverflow && !root.overflowExpanded)
continue;
let itemSize = 0;
if (item.type === "separator") {
itemSize = 8;
} else {
itemSize = compact ? 24 : (24 + Theme.spacingXS + 120);
}
total += itemSize;
if (i < dockItems.length - 1)
total += Theme.spacingXS;
}
return total;
}
width: dockItems.length > 0 ? (isVertical ? barThickness : realCalculatedSize) : 0
height: dockItems.length > 0 ? (isVertical ? realCalculatedSize : barThickness) : 0
visible: dockItems.length > 0 visible: dockItems.length > 0
Item { content: Component {
id: visualBackground Item {
width: root.isVertical ? root.widgetThickness : root.realCalculatedSize implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
height: root.isVertical ? root.realCalculatedSize : root.widgetThickness implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
anchors.centerIn: parent
clip: false
Rectangle { Loader {
id: outline id: layoutLoader
anchors.centerIn: parent anchors.centerIn: parent
width: { sourceComponent: root.isVerticalOrientation ? columnLayout : rowLayout
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.width + borderWidth * 2;
}
height: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: "transparent"
border.width: (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0
border.color: {
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
return "transparent";
}
const colorOption = barConfig?.widgetOutlineColor || "primary";
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
switch (colorOption) {
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
default:
return Theme.withAlpha(Theme.primary, opacity);
}
} }
} }
Rectangle {
id: background
anchors.fill: parent
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: {
if (dockItems.length === 0)
return "transparent";
if ((barConfig?.noBackground ?? false))
return "transparent";
const baseColor = Theme.widgetBaseBackgroundColor;
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
if (Theme.widgetBackgroundHasAlpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
}
return Theme.withAlpha(baseColor, transparency);
}
}
}
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVertical ? columnLayout : rowLayout
} }
Component { Component {
@@ -556,8 +461,8 @@ Item {
readonly property bool isInOverflow: modelData.isInOverflow === true readonly property bool isInOverflow: modelData.isInOverflow === true
readonly property real visualSize: isSeparator ? 8 : ((widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? 24 : (24 + Theme.spacingXS + 120)) readonly property real visualSize: isSeparator ? 8 : ((widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? 24 : (24 + Theme.spacingXS + 120))
readonly property real visualWidth: root.isVertical ? root.barThickness : visualSize readonly property real visualWidth: root.isVerticalOrientation ? root.barThickness : visualSize
readonly property real visualHeight: root.isVertical ? visualSize : root.barThickness readonly property real visualHeight: root.isVerticalOrientation ? visualSize : root.barThickness
visible: !isInOverflow || root.overflowExpanded visible: !isInOverflow || root.overflowExpanded
opacity: (isInOverflow && !root.overflowExpanded) ? 0 : 1 opacity: (isInOverflow && !root.overflowExpanded) ? 0 : 1
@@ -604,8 +509,8 @@ Item {
} }
transform: Translate { transform: Translate {
x: root.isVertical ? 0 : delegateItem.shiftOffset x: root.isVerticalOrientation ? 0 : delegateItem.shiftOffset
y: root.isVertical ? delegateItem.shiftOffset : 0 y: root.isVerticalOrientation ? delegateItem.shiftOffset : 0
Behavior on x { Behavior on x {
enabled: !root.suppressShiftAnimation enabled: !root.suppressShiftAnimation
@@ -625,8 +530,8 @@ Item {
Rectangle { Rectangle {
visible: isSeparator visible: isSeparator
width: root.isVertical ? root.barThickness * 0.6 : 2 width: root.isVerticalOrientation ? root.barThickness * 0.6 : 2
height: root.isVertical ? 2 : root.barThickness * 0.6 height: root.isVerticalOrientation ? 2 : root.barThickness * 0.6
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3) color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
radius: 1 radius: 1
anchors.centerIn: parent anchors.centerIn: parent
@@ -640,7 +545,7 @@ Item {
iconSize: 24 iconSize: 24
overflowCount: modelData.overflowCount || 0 overflowCount: modelData.overflowCount || 0
overflowExpanded: root.overflowExpanded overflowExpanded: root.overflowExpanded
isVertical: root.isVertical isVertical: root.isVerticalOrientation
showBadge: root.showOverflowBadge showBadge: root.showOverflowBadge
z: 10 z: 10
onClicked: { onClicked: {
@@ -709,14 +614,14 @@ Item {
} }
transform: Translate { transform: Translate {
x: (dragHandler.dragging && !root.isVertical) ? dragHandler.dragAxisOffset : 0 x: (dragHandler.dragging && !root.isVerticalOrientation) ? dragHandler.dragAxisOffset : 0
y: (dragHandler.dragging && root.isVertical) ? dragHandler.dragAxisOffset : 0 y: (dragHandler.dragging && root.isVerticalOrientation) ? dragHandler.dragAxisOffset : 0
} }
Rectangle { Rectangle {
id: visualContent id: visualContent
width: root.isVertical ? 24 : delegateItem.visualSize width: root.isVerticalOrientation ? 24 : delegateItem.visualSize
height: root.isVertical ? delegateItem.visualSize : 24 height: root.isVerticalOrientation ? delegateItem.visualSize : 24
anchors.centerIn: parent anchors.centerIn: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
@@ -735,11 +640,11 @@ Item {
AppIconRenderer { AppIconRenderer {
id: coreIcon id: coreIcon
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left anchors.left: (root.isVerticalOrientation || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS anchors.leftMargin: (root.isVerticalOrientation || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined anchors.top: (root.isVerticalOrientation && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0 anchors.topMargin: (root.isVerticalOrientation && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined anchors.centerIn: (root.isVerticalOrientation || isCompact) ? parent : undefined
iconSize: appItem.effectiveIconSize iconSize: appItem.effectiveIconSize
materialIconSizeAdjustment: 0 materialIconSizeAdjustment: 0
@@ -770,11 +675,11 @@ Item {
IconImage { IconImage {
id: iconImg id: iconImg
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left anchors.left: (root.isVerticalOrientation || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS anchors.leftMargin: (root.isVerticalOrientation || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined anchors.top: (root.isVerticalOrientation && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0 anchors.topMargin: (root.isVerticalOrientation && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined anchors.centerIn: (root.isVerticalOrientation || isCompact) ? parent : undefined
width: appItem.effectiveIconSize width: appItem.effectiveIconSize
height: appItem.effectiveIconSize height: appItem.effectiveIconSize
@@ -815,11 +720,11 @@ Item {
DankIcon { DankIcon {
readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) readonly property bool isCompact: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: (root.isVertical || isCompact) ? undefined : parent.left anchors.left: (root.isVerticalOrientation || isCompact) ? undefined : parent.left
anchors.leftMargin: (root.isVertical || isCompact) ? 0 : Theme.spacingXS anchors.leftMargin: (root.isVerticalOrientation || isCompact) ? 0 : Theme.spacingXS
anchors.top: (root.isVertical && !isCompact) ? parent.top : undefined anchors.top: (root.isVerticalOrientation && !isCompact) ? parent.top : undefined
anchors.topMargin: (root.isVertical && !isCompact) ? Theme.spacingXS : 0 anchors.topMargin: (root.isVerticalOrientation && !isCompact) ? Theme.spacingXS : 0
anchors.centerIn: (root.isVertical || isCompact) ? parent : undefined anchors.centerIn: (root.isVerticalOrientation || isCompact) ? parent : undefined
size: appItem.effectiveIconSize size: appItem.effectiveIconSize
name: "sports_esports" name: "sports_esports"
@@ -882,7 +787,7 @@ Item {
} }
StyledText { StyledText {
visible: !root.isVertical && !(widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) visible: !root.isVerticalOrientation && !(widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode)
anchors.left: iconImg.right anchors.left: iconImg.right
anchors.leftMargin: Theme.spacingXS anchors.leftMargin: Theme.spacingXS
anchors.right: parent.right anchors.right: parent.right
@@ -897,16 +802,16 @@ Item {
Rectangle { Rectangle {
visible: modelData.isRunning && !(widgetData?.appsDockHideIndicators !== undefined ? widgetData.appsDockHideIndicators : SettingsData.appsDockHideIndicators) visible: modelData.isRunning && !(widgetData?.appsDockHideIndicators !== undefined ? widgetData.appsDockHideIndicators : SettingsData.appsDockHideIndicators)
width: root.isVertical ? 2 : 20 width: root.isVerticalOrientation ? 2 : 20
height: root.isVertical ? 20 : 2 height: root.isVerticalOrientation ? 20 : 2
radius: 1 radius: 1
color: appItem.isFocused ? Theme.primary : Theme.surfaceText color: appItem.isFocused ? Theme.primary : Theme.surfaceText
opacity: appItem.isFocused ? 1 : 0.5 opacity: appItem.isFocused ? 1 : 0.5
anchors.bottom: root.isVertical ? undefined : parent.bottom anchors.bottom: root.isVerticalOrientation ? undefined : parent.bottom
anchors.right: root.isVertical ? parent.right : undefined anchors.right: root.isVerticalOrientation ? parent.right : undefined
anchors.horizontalCenter: root.isVertical ? undefined : parent.horizontalCenter anchors.horizontalCenter: root.isVerticalOrientation ? undefined : parent.horizontalCenter
anchors.verticalCenter: root.isVertical ? parent.verticalCenter : undefined anchors.verticalCenter: root.isVerticalOrientation ? parent.verticalCenter : undefined
anchors.margins: 0 anchors.margins: 0
z: 5 z: 5
@@ -1009,10 +914,10 @@ Item {
if (!dragHandler.dragging) if (!dragHandler.dragging)
return; return;
const axisOffset = root.isVertical ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x); const axisOffset = root.isVerticalOrientation ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x);
dragHandler.dragAxisOffset = axisOffset; dragHandler.dragAxisOffset = axisOffset;
const itemSize = (root.isVertical ? delegateItem.height : delegateItem.width) + Theme.spacingXS; const itemSize = (root.isVerticalOrientation ? delegateItem.height : delegateItem.width) + Theme.spacingXS;
const slotOffset = Math.round(axisOffset / itemSize); const slotOffset = Math.round(axisOffset / itemSize);
const newTargetIndex = Math.max(0, Math.min(root.pinnedAppCount - 1, index + slotOffset)); const newTargetIndex = Math.max(0, Math.min(root.pinnedAppCount - 1, index + slotOffset));
@@ -1028,7 +933,7 @@ Item {
tooltipLoader.active = true; tooltipLoader.active = true;
if (tooltipLoader.item) { if (tooltipLoader.item) {
if (root.isVertical) { if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(0, delegateItem.height / 2); const globalPos = delegateItem.mapToGlobal(0, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0; const screenY = root.parentScreen ? root.parentScreen.y : 0;

View File

@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Services import qs.Services
@@ -213,12 +212,19 @@ PanelWindow {
} }
} }
DankRipple {
id: windowRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: windowArea id: windowArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 24 anchors.rightMargin: 24
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => windowRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (modelData && modelData.activate) { if (modelData && modelData.activate) {
modelData.activate(); modelData.activate();
@@ -285,11 +291,18 @@ PanelWindow {
} }
} }
DankRipple {
id: actionRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: actionArea id: actionArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => actionRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (modelData) { if (modelData) {
SessionService.launchDesktopAction(root.desktopEntry, modelData); SessionService.launchDesktopAction(root.desktopEntry, modelData);
@@ -333,11 +346,18 @@ PanelWindow {
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
DankRipple {
id: pinRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: pinArea id: pinArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (!root.appData) { if (!root.appData) {
return; return;
@@ -386,11 +406,18 @@ PanelWindow {
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
DankRipple {
id: nvidiaRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: nvidiaArea id: nvidiaArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => nvidiaRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (root.desktopEntry) { if (root.desktopEntry) {
SessionService.launchDesktopEntry(root.desktopEntry, true); SessionService.launchDesktopEntry(root.desktopEntry, true);
@@ -426,11 +453,18 @@ PanelWindow {
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
DankRipple {
id: closeRipple
rippleColor: Theme.error
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: closeArea id: closeArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => closeRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (root.appData?.type === "window") { if (root.appData?.type === "window") {
root.appData?.toplevel?.close(); root.appData?.toplevel?.close();

View File

@@ -115,7 +115,8 @@ BasePill {
height: battery.height + battery.topMargin + battery.bottomMargin height: battery.height + battery.topMargin + battery.bottomMargin
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed: { onPressed: mouse => {
battery.triggerRipple(this, mouse.x, mouse.y);
toggleBatteryPopup(); toggleBatteryPopup();
} }
} }

View File

@@ -65,6 +65,7 @@ BasePill {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
onClicked: function (mouse) { onClicked: function (mouse) {
switch (mouse.button) { switch (mouse.button) {
case Qt.RightButton: case Qt.RightButton:

View File

@@ -11,6 +11,8 @@ BasePill {
property bool compactMode: false property bool compactMode: false
signal clockClicked signal clockClicked
onClicked: clockClicked()
content: Component { content: Component {
Item { Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : clockRow.implicitWidth implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : clockRow.implicitWidth
@@ -326,15 +328,4 @@ BasePill {
} }
} }
} }
MouseArea {
x: -root.leftMargin
y: -root.topMargin
width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin
cursorShape: Qt.PointingHandCursor
onPressed: {
root.clockClicked();
}
}
} }

View File

@@ -29,7 +29,8 @@ BasePill {
z: 1 z: 1
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
root.colorPickerRequested(); root.colorPickerRequested();
} }
} }

View File

@@ -138,7 +138,8 @@ BasePill {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
DgopService.setSortBy("cpu"); DgopService.setSortBy("cpu");
cpuClicked(); cpuClicked();
} }

View File

@@ -138,7 +138,8 @@ BasePill {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
DgopService.setSortBy("cpu"); DgopService.setSortBy("cpu");
cpuTempClicked(); cpuTempClicked();
} }

View File

@@ -206,7 +206,8 @@ BasePill {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
DgopService.setSortBy("cpu"); DgopService.setSortBy("cpu");
gpuTempClicked(); gpuTempClicked();
} }

View File

@@ -26,6 +26,9 @@ BasePill {
z: 1 z: 1
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
}
onClicked: { onClicked: {
SessionService.toggleIdleInhibit(); SessionService.toggleIdleInhibit();
} }

View File

@@ -12,31 +12,101 @@ BasePill {
property var widgetData: null property var widgetData: null
property bool compactMode: widgetData?.keyboardLayoutNameCompactMode !== undefined ? widgetData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode property bool compactMode: widgetData?.keyboardLayoutNameCompactMode !== undefined ? widgetData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode
readonly property var langCodes: ({ readonly property var langCodes: ({
"afrikaans": "af", "albanian": "sq", "amharic": "am", "arabic": "ar", "afrikaans": "af",
"armenian": "hy", "azerbaijani": "az", "basque": "eu", "belarusian": "be", "albanian": "sq",
"bengali": "bn", "bosnian": "bs", "bulgarian": "bg", "burmese": "my", "amharic": "am",
"catalan": "ca", "chinese": "zh", "croatian": "hr", "czech": "cs", "arabic": "ar",
"danish": "da", "dutch": "nl", "english": "en", "esperanto": "eo", "armenian": "hy",
"estonian": "et", "filipino": "fil", "finnish": "fi", "french": "fr", "azerbaijani": "az",
"galician": "gl", "georgian": "ka", "german": "de", "greek": "el", "basque": "eu",
"gujarati": "gu", "hausa": "ha", "hebrew": "he", "hindi": "hi", "belarusian": "be",
"hungarian": "hu", "icelandic": "is", "igbo": "ig", "indonesian": "id", "bengali": "bn",
"irish": "ga", "italian": "it", "japanese": "ja", "javanese": "jv", "bosnian": "bs",
"kannada": "kn", "kazakh": "kk", "khmer": "km", "korean": "ko", "bulgarian": "bg",
"kurdish": "ku", "kyrgyz": "ky", "lao": "lo", "latvian": "lv", "burmese": "my",
"lithuanian": "lt", "luxembourgish": "lb", "macedonian": "mk", "malay": "ms", "catalan": "ca",
"malayalam": "ml", "maltese": "mt", "maori": "mi", "marathi": "mr", "chinese": "zh",
"mongolian": "mn", "nepali": "ne", "norwegian": "no", "pashto": "ps", "croatian": "hr",
"persian": "fa", "iranian": "fa", "farsi": "fa", "polish": "pl", "czech": "cs",
"portuguese": "pt", "punjabi": "pa", "romanian": "ro", "russian": "ru", "danish": "da",
"serbian": "sr", "sindhi": "sd", "sinhala": "si", "slovak": "sk", "dutch": "nl",
"slovenian": "sl", "somali": "so", "spanish": "es", "swahili": "sw", "english": "en",
"swedish": "sv", "tajik": "tg", "tamil": "ta", "tatar": "tt", "esperanto": "eo",
"telugu": "te", "thai": "th", "tibetan": "bo", "turkish": "tr", "estonian": "et",
"turkmen": "tk", "ukrainian": "uk", "urdu": "ur", "uyghur": "ug", "filipino": "fil",
"uzbek": "uz", "vietnamese": "vi", "welsh": "cy", "yiddish": "yi", "finnish": "fi",
"yoruba": "yo", "zulu": "zu" "french": "fr",
}) "galician": "gl",
"georgian": "ka",
"german": "de",
"greek": "el",
"gujarati": "gu",
"hausa": "ha",
"hebrew": "he",
"hindi": "hi",
"hungarian": "hu",
"icelandic": "is",
"igbo": "ig",
"indonesian": "id",
"irish": "ga",
"italian": "it",
"japanese": "ja",
"javanese": "jv",
"kannada": "kn",
"kazakh": "kk",
"khmer": "km",
"korean": "ko",
"kurdish": "ku",
"kyrgyz": "ky",
"lao": "lo",
"latvian": "lv",
"lithuanian": "lt",
"luxembourgish": "lb",
"macedonian": "mk",
"malay": "ms",
"malayalam": "ml",
"maltese": "mt",
"maori": "mi",
"marathi": "mr",
"mongolian": "mn",
"nepali": "ne",
"norwegian": "no",
"pashto": "ps",
"persian": "fa",
"iranian": "fa",
"farsi": "fa",
"polish": "pl",
"portuguese": "pt",
"punjabi": "pa",
"romanian": "ro",
"russian": "ru",
"serbian": "sr",
"sindhi": "sd",
"sinhala": "si",
"slovak": "sk",
"slovenian": "sl",
"somali": "so",
"spanish": "es",
"swahili": "sw",
"swedish": "sv",
"tajik": "tg",
"tamil": "ta",
"tatar": "tt",
"telugu": "te",
"thai": "th",
"tibetan": "bo",
"turkish": "tr",
"turkmen": "tk",
"ukrainian": "uk",
"urdu": "ur",
"uyghur": "ug",
"uzbek": "uz",
"vietnamese": "vi",
"welsh": "cy",
"yiddish": "yi",
"yoruba": "yo",
"zulu": "zu"
})
readonly property var validVariants: ["US", "UK", "GB", "AZERTY", "QWERTY", "Dvorak", "Colemak", "Mac", "Intl", "International"] readonly property var validVariants: ["US", "UK", "GB", "AZERTY", "QWERTY", "Dvorak", "Colemak", "Mac", "Intl", "International"]
property string currentLayout: { property string currentLayout: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
@@ -119,6 +189,9 @@ BasePill {
z: 1 z: 1
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
}
onClicked: { onClicked: {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.cycleKeyboardLayout(); NiriService.cycleKeyboardLayout();

View File

@@ -171,6 +171,9 @@ BasePill {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
}
onClicked: { onClicked: {
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) { if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = parent.mapToItem(null, 0, 0); const globalPos = parent.mapToItem(null, 0, 0);
@@ -326,7 +329,8 @@ BasePill {
anchors.fill: parent anchors.fill: parent
enabled: root.playerAvailable enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
if (root.popoutTarget && root.popoutTarget.setTriggerPosition) { if (root.popoutTarget && root.popoutTarget.setTriggerPosition) {
const globalPos = mapToItem(null, 0, 0); const globalPos = mapToItem(null, 0, 0);
const currentScreen = root.parentScreen || Screen; const currentScreen = root.parentScreen || Screen;

View File

@@ -130,6 +130,9 @@ BasePill {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
}
onClicked: function (mouse) { onClicked: function (mouse) {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
openContextMenu(); openContextMenu();

View File

@@ -1,247 +1,171 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { BasePill {
id: root id: root
property bool isVertical: axis?.isVertical ?? false section: "right"
property var axis: null
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property var barConfig: null
property bool showMicIcon: SettingsData.privacyShowMicIcon property bool showMicIcon: SettingsData.privacyShowMicIcon
property bool showCameraIcon: SettingsData.privacyShowCameraIcon property bool showCameraIcon: SettingsData.privacyShowCameraIcon
property bool showScreenSharingIcon: SettingsData.privacyShowScreenShareIcon property bool showScreenSharingIcon: SettingsData.privacyShowScreenShareIcon
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
readonly property bool hasActivePrivacy: showMicIcon || showCameraIcon || showScreenSharingIcon || PrivacyService.anyPrivacyActive readonly property bool hasActivePrivacy: showMicIcon || showCameraIcon || showScreenSharingIcon || PrivacyService.anyPrivacyActive
readonly property int activeCount: (showMicIcon ? 1 : PrivacyService.microphoneActive) + (showCameraIcon ? 1 : PrivacyService.cameraActive) + (showScreenSharingIcon ? 1 : PrivacyService.screensharingActive) readonly property int activeCount: (showMicIcon ? 1 : PrivacyService.microphoneActive) + (showCameraIcon ? 1 : PrivacyService.cameraActive) + (showScreenSharingIcon ? 1 : PrivacyService.screensharingActive)
readonly property real contentWidth: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0 readonly property real contentWidth: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0
readonly property real contentHeight: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0 readonly property real contentHeight: hasActivePrivacy ? (activeCount * 18 + (activeCount - 1) * Theme.spacingXS) : 0
readonly property real visualWidth: isVertical ? widgetThickness : (hasActivePrivacy ? (contentWidth + horizontalPadding * 2) : 0)
readonly property real visualHeight: isVertical ? (hasActivePrivacy ? (contentHeight + horizontalPadding * 2) : 0) : widgetThickness
width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness
visible: hasActivePrivacy visible: hasActivePrivacy
opacity: hasActivePrivacy ? 1 : 0 opacity: hasActivePrivacy ? 1 : 0
enabled: hasActivePrivacy enabled: hasActivePrivacy
Item { content: Component {
id: visualContent Item {
width: root.visualWidth implicitWidth: root.hasActivePrivacy ? root.contentWidth : 0
height: root.visualHeight implicitHeight: root.hasActivePrivacy ? root.contentHeight : 0
anchors.centerIn: parent
Rectangle { Column {
id: outline anchors.centerIn: parent
anchors.centerIn: parent spacing: Theme.spacingXS
width: { visible: root.isVerticalOrientation && root.hasActivePrivacy
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.width + borderWidth * 2;
}
height: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: "transparent"
border.width: {
if (barConfig?.widgetOutlineEnabled ?? false) {
return barConfig?.widgetOutlineThickness ?? 1;
}
return 0;
}
border.color: {
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
return "transparent";
}
const colorOption = barConfig?.widgetOutlineColor || "primary";
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
switch (colorOption) {
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
default:
return Theme.withAlpha(Theme.primary, opacity);
}
}
}
Rectangle { Item {
id: background width: 18
anchors.fill: parent height: 18
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius visible: PrivacyService.microphoneActive
color: { anchors.horizontalCenter: parent.horizontalCenter
if (barConfig?.noBackground ?? false) {
return "transparent";
}
const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0; DankIcon {
const isHovered = privacyArea.containsMouse; name: {
const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency; const sourceAudio = AudioService.source?.audio;
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor; const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0;
return Theme.withAlpha(baseColor, transparency); if (muted)
} return "mic_off";
} return "mic";
}
Column { size: Theme.iconSizeSmall
anchors.centerIn: parent color: Theme.error
spacing: Theme.spacingXS filled: true
visible: root.isVertical && root.hasActivePrivacy anchors.centerIn: parent
Item {
width: 18
height: 18
visible: PrivacyService.microphoneActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: {
const sourceAudio = AudioService.source?.audio;
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0;
if (muted)
return "mic_off";
return "mic";
} }
size: Theme.iconSizeSmall
color: Theme.error
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: PrivacyService.cameraActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: Theme.widgetTextColor
filled: true
anchors.centerIn: parent
} }
Rectangle { Item {
width: 6 width: 18
height: 6 height: 18
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
}
}
Item {
width: 18
height: 18
visible: PrivacyService.screensharingActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: Theme.warning
filled: true
anchors.centerIn: parent
}
}
}
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVertical && root.hasActivePrivacy
Item {
width: 18
height: 18
visible: showMicIcon || PrivacyService.microphoneActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: {
const sourceAudio = AudioService.source?.audio;
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0;
if (muted)
return "mic_off";
return "mic";
}
size: Theme.iconSizeSmall
color: PrivacyService.microphoneActive ? Theme.error : Theme.surfaceText
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: showCameraIcon || PrivacyService.cameraActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: PrivacyService.cameraActive ? Theme.error : Theme.surfaceText
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
visible: PrivacyService.cameraActive visible: PrivacyService.cameraActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: Theme.widgetTextColor
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
}
}
Item {
width: 18
height: 18
visible: PrivacyService.screensharingActive
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: Theme.warning
filled: true
anchors.centerIn: parent
}
} }
} }
Item { Row {
width: 18 anchors.centerIn: parent
height: 18 spacing: Theme.spacingXS
visible: showScreenSharingIcon || PrivacyService.screensharingActive visible: !root.isVerticalOrientation && root.hasActivePrivacy
anchors.verticalCenter: parent.verticalCenter
DankIcon { Item {
name: "screen_share" width: 18
size: Theme.iconSizeSmall height: 18
color: PrivacyService.screensharingActive ? Theme.warning : Theme.surfaceText visible: root.showMicIcon || PrivacyService.microphoneActive
filled: true anchors.verticalCenter: parent.verticalCenter
anchors.centerIn: parent
DankIcon {
name: {
const sourceAudio = AudioService.source?.audio;
const muted = !sourceAudio || sourceAudio.muted || sourceAudio.volume === 0.0;
if (muted)
return "mic_off";
return "mic";
}
size: Theme.iconSizeSmall
color: PrivacyService.microphoneActive ? Theme.error : Theme.surfaceText
filled: true
anchors.centerIn: parent
}
}
Item {
width: 18
height: 18
visible: root.showCameraIcon || PrivacyService.cameraActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "camera_video"
size: Theme.iconSizeSmall
color: PrivacyService.cameraActive ? Theme.error : Theme.surfaceText
filled: true
anchors.centerIn: parent
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.error
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -2
anchors.topMargin: -1
visible: PrivacyService.cameraActive
}
}
Item {
width: 18
height: 18
visible: root.showScreenSharingIcon || PrivacyService.screensharingActive
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: PrivacyService.screensharingActive ? Theme.warning : Theme.surfaceText
filled: true
anchors.centerIn: parent
}
} }
} }
} }
} }
MouseArea {
id: privacyArea
z: -1
anchors.fill: parent
hoverEnabled: hasActivePrivacy
enabled: hasActivePrivacy
cursorShape: Qt.PointingHandCursor
onClicked: {}
}
Rectangle { Rectangle {
id: tooltip id: tooltip
width: tooltipText.contentWidth + Theme.spacingM * 2 width: tooltipText.contentWidth + Theme.spacingM * 2
@@ -251,7 +175,7 @@ Item {
border.color: Theme.outlineMedium border.color: Theme.outlineMedium
border.width: 1 border.width: 1
visible: false visible: false
opacity: privacyArea.containsMouse && hasActivePrivacy ? 1 : 0 opacity: root.isMouseHovered && hasActivePrivacy ? 1 : 0
z: 100 z: 100
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: -height - Theme.spacingXS y: -height - Theme.spacingXS
@@ -287,7 +211,7 @@ Item {
} }
Behavior on width { Behavior on width {
enabled: hasActivePrivacy && visible && !isVertical enabled: hasActivePrivacy && visible && !isVerticalOrientation
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration
@@ -296,7 +220,7 @@ Item {
} }
Behavior on height { Behavior on height {
enabled: hasActivePrivacy && visible && isVertical enabled: hasActivePrivacy && visible && isVerticalOrientation
NumberAnimation { NumberAnimation {
duration: Theme.mediumDuration duration: Theme.mediumDuration

View File

@@ -161,7 +161,8 @@ BasePill {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
DgopService.setSortBy("memory"); DgopService.setSortBy("memory");
ramClicked(); ramClicked();
} }

View File

@@ -5,25 +5,21 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { BasePill {
id: root id: root
enableBackgroundHover: false
enableCursor: false
section: "left"
property var widgetData: null property var widgetData: null
property var barConfig: null
property bool isVertical: axis?.isVertical ?? false
property var axis: null
property string section: "left"
property var parentScreen
property var hoveredItem: null property var hoveredItem: null
property var topBar: null property var topBar: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property bool isAutoHideBar: false property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null) property Item windowRoot: (Window.window ? Window.window.contentItem : null)
readonly property real effectiveBarThickness: { readonly property real effectiveBarThickness: {
@@ -52,7 +48,7 @@ Item {
readonly property real barY: barBounds.y readonly property real barY: barBounds.y
readonly property real minTooltipY: { readonly property real minTooltipY: {
if (!parentScreen || !isVertical) { if (!parentScreen || !isVerticalOrientation) {
return 0; return 0;
} }
@@ -139,108 +135,42 @@ Item {
} }
} }
readonly property int windowCount: _groupByApp ? (groupedWindows?.length || 0) : (sortedToplevels?.length || 0) readonly property int windowCount: _groupByApp ? (groupedWindows?.length || 0) : (sortedToplevels?.length || 0)
readonly property int calculatedSize: {
if (windowCount === 0) {
return 0;
}
if (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) {
return windowCount * 24 + (windowCount - 1) * Theme.spacingXS + horizontalPadding * 2;
} else {
return windowCount * (24 + Theme.spacingXS + 120) + (windowCount - 1) * Theme.spacingXS + horizontalPadding * 2;
}
}
width: windowCount > 0 ? (isVertical ? barThickness : calculatedSize) : 0
height: windowCount > 0 ? (isVertical ? calculatedSize : barThickness) : 0
visible: windowCount > 0 visible: windowCount > 0
Item { property real scrollAccumulator: 0
id: visualBackground property real touchpadThreshold: 500
width: root.isVertical ? root.widgetThickness : root.calculatedSize
height: root.isVertical ? root.calculatedSize : root.widgetThickness
anchors.centerIn: parent
clip: false
Rectangle { onWheel: function (wheelEvent) {
id: outline const deltaY = wheelEvent.angleDelta.y;
anchors.centerIn: parent const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
width: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0; const windows = root.sortedToplevels;
return parent.width + borderWidth * 2; if (windows.length < 2)
} return;
height: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0; if (isMouseWheel) {
return parent.height + borderWidth * 2; let currentIndex = -1;
} for (var i = 0; i < windows.length; i++) {
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius if (windows[i].activated) {
color: "transparent" currentIndex = i;
border.width: { break;
if (barConfig?.widgetOutlineEnabled ?? false) {
return barConfig?.widgetOutlineThickness ?? 1;
}
return 0;
}
border.color: {
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
return "transparent";
}
const colorOption = barConfig?.widgetOutlineColor || "primary";
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
switch (colorOption) {
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
default:
return Theme.withAlpha(Theme.primary, opacity);
} }
} }
}
Rectangle { let nextIndex;
id: background if (deltaY < 0) {
anchors.fill: parent nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, windows.length - 1);
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius } else {
color: { nextIndex = currentIndex === -1 ? windows.length - 1 : Math.max(currentIndex - 1, 0);
if (windowCount === 0) {
return "transparent";
}
if ((barConfig?.noBackground ?? false)) {
return "transparent";
}
const baseColor = Theme.widgetBaseBackgroundColor;
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
if (Theme.widgetBackgroundHasAlpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
}
return Theme.withAlpha(baseColor, transparency);
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
property real scrollAccumulator: 0
property real touchpadThreshold: 500
onWheel: wheel => {
const deltaY = wheel.angleDelta.y;
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
const windows = root.sortedToplevels;
if (windows.length < 2) {
return;
} }
if (isMouseWheel) { const nextWindow = windows[nextIndex];
// Direct mouse wheel action if (nextWindow)
nextWindow.activate();
} else {
scrollAccumulator += deltaY;
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
let currentIndex = -1; let currentIndex = -1;
for (var i = 0; i < windows.length; i++) { for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) { if (windows[i].activated) {
@@ -250,69 +180,32 @@ Item {
} }
let nextIndex; let nextIndex;
if (deltaY < 0) { if (scrollAccumulator < 0) {
if (currentIndex === -1) { nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, windows.length - 1);
nextIndex = 0;
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1);
}
} else { } else {
if (currentIndex === -1) { nextIndex = currentIndex === -1 ? windows.length - 1 : Math.max(currentIndex - 1, 0);
nextIndex = windows.length - 1;
} else {
nextIndex = Math.max(currentIndex - 1, 0);
}
} }
const nextWindow = windows[nextIndex]; const nextWindow = windows[nextIndex];
if (nextWindow) { if (nextWindow)
nextWindow.activate(); nextWindow.activate();
}
} else {
// Touchpad - accumulate small deltas
scrollAccumulator += deltaY;
if (Math.abs(scrollAccumulator) >= touchpadThreshold) { scrollAccumulator = 0;
let currentIndex = -1;
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i;
break;
}
}
let nextIndex;
if (scrollAccumulator < 0) {
if (currentIndex === -1) {
nextIndex = 0;
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1);
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1;
} else {
nextIndex = Math.max(currentIndex - 1, 0);
}
}
const nextWindow = windows[nextIndex];
if (nextWindow) {
nextWindow.activate();
}
scrollAccumulator = 0;
}
} }
wheel.accepted = true;
} }
} }
Loader { content: Component {
id: layoutLoader Item {
anchors.centerIn: parent implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
sourceComponent: root.isVertical ? columnLayout : rowLayout implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVerticalOrientation ? columnLayout : rowLayout
}
}
} }
Component { Component {
@@ -466,6 +359,7 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
if (isGrouped && windowCount > 1) { if (isGrouped && windowCount > 1) {
@@ -495,7 +389,7 @@ Item {
windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1)); windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1));
windowContextMenuLoader.item.triggerBarThickness = root.barThickness; windowContextMenuLoader.item.triggerBarThickness = root.barThickness;
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing; windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
if (root.isVertical) { if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2); const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0; const screenY = root.parentScreen ? root.parentScreen.y : 0;
@@ -526,7 +420,7 @@ Item {
root.hoveredItem = delegateItem; root.hoveredItem = delegateItem;
tooltipLoader.active = true; tooltipLoader.active = true;
if (tooltipLoader.item) { if (tooltipLoader.item) {
if (root.isVertical) { if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2); const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0; const screenY = root.parentScreen ? root.parentScreen.y : 0;
@@ -711,6 +605,7 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
if (isGrouped && windowCount > 1) { if (isGrouped && windowCount > 1) {
@@ -740,7 +635,7 @@ Item {
windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1)); windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1));
windowContextMenuLoader.item.triggerBarThickness = root.barThickness; windowContextMenuLoader.item.triggerBarThickness = root.barThickness;
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing; windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
if (root.isVertical) { if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2); const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0; const screenY = root.parentScreen ? root.parentScreen.y : 0;
@@ -765,7 +660,7 @@ Item {
root.hoveredItem = delegateItem; root.hoveredItem = delegateItem;
tooltipLoader.active = true; tooltipLoader.active = true;
if (tooltipLoader.item) { if (tooltipLoader.item) {
if (root.isVertical) { if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2); const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0; const screenY = root.parentScreen ? root.parentScreen.y : 0;

View File

@@ -6,23 +6,19 @@ import Quickshell.Services.SystemTray
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.Plugins
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { BasePill {
id: root id: root
property bool isVertical: axis?.isVertical ?? false enableBackgroundHover: false
property var axis: null enableCursor: false
property var parentWindow: null property var parentWindow: null
property var parentScreen: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property bool isAtBottom: false property bool isAtBottom: false
property var barConfig: null
property bool isAutoHideBar: false property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
readonly property var hiddenTrayIds: { readonly property var hiddenTrayIds: {
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || ""; const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || "";
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : []; return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : [];
@@ -102,21 +98,10 @@ Item {
property int dropTargetIndex: -1 property int dropTargetIndex: -1
property bool suppressShiftAnimation: false property bool suppressShiftAnimation: false
readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length readonly property bool hasHiddenItems: allTrayItems.length > mainBarItems.length
readonly property int calculatedSize: {
if (allTrayItems.length === 0)
return 0;
const itemCount = mainBarItems.length + (hasHiddenItems ? 1 : 0);
return itemCount * 24 + horizontalPadding * 2;
}
readonly property real visualWidth: isVertical ? widgetThickness : calculatedSize
readonly property real visualHeight: isVertical ? calculatedSize : widgetThickness
width: isVertical ? barThickness : visualWidth
height: isVertical ? visualHeight : barThickness
visible: allTrayItems.length > 0 visible: allTrayItems.length > 0
readonly property real minTooltipY: { readonly property real minTooltipY: {
if (!parentScreen || !isVertical) { if (!parentScreen || !isVerticalOrientation) {
return 0; return 0;
} }
@@ -135,77 +120,17 @@ Item {
property bool menuOpen: false property bool menuOpen: false
property var currentTrayMenu: null property var currentTrayMenu: null
Item { content: Component {
id: visualBackground Item {
width: root.visualWidth implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
height: root.visualHeight implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
anchors.centerIn: parent
Rectangle { Loader {
id: outline id: layoutLoader
anchors.centerIn: parent anchors.centerIn: parent
width: { sourceComponent: root.isVerticalOrientation ? columnComp : rowComp
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.width + borderWidth * 2;
}
height: {
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return parent.height + borderWidth * 2;
}
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: "transparent"
border.width: {
if (barConfig?.widgetOutlineEnabled ?? false) {
return barConfig?.widgetOutlineThickness ?? 1;
}
return 0;
}
border.color: {
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
return "transparent";
}
const colorOption = barConfig?.widgetOutlineColor || "primary";
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
switch (colorOption) {
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
default:
return Theme.withAlpha(Theme.primary, opacity);
}
} }
} }
Rectangle {
id: background
anchors.fill: parent
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: {
if (allTrayItems.length === 0) {
return "transparent";
}
if ((barConfig?.noBackground ?? false)) {
return "transparent";
}
const baseColor = Theme.widgetBaseBackgroundColor;
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
if (Theme.widgetBackgroundHasAlpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
}
return Theme.withAlpha(baseColor, transparency);
}
}
}
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVertical ? columnComp : rowComp
} }
Component { Component {
@@ -334,6 +259,11 @@ Item {
font.pixelSize: 10 font.pixelSize: 10
color: Theme.widgetTextColor color: Theme.widgetTextColor
} }
DankRipple {
id: itemRipple
cornerRadius: Theme.cornerRadius
}
} }
MouseArea { MouseArea {
@@ -344,6 +274,8 @@ Item {
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
onPressed: mouse => { onPressed: mouse => {
const pos = mapToItem(visualContent, mouse.x, mouse.y);
itemRipple.trigger(pos.x, pos.y);
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y); dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start(); longPressTimer.start();
@@ -379,7 +311,7 @@ Item {
if (!delegateRoot.trayItem.hasMenu) if (!delegateRoot.trayItem.hasMenu)
return; return;
root.menuOpen = false; root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
@@ -412,7 +344,7 @@ Item {
if (!delegateRoot.trayItem?.hasMenu) if (!delegateRoot.trayItem?.hasMenu)
return; return;
root.menuOpen = false; root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
} }
} }
@@ -438,11 +370,19 @@ Item {
color: Theme.widgetTextColor color: Theme.widgetTextColor
} }
DankRipple {
id: caretRipple
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: caretArea id: caretArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
caretRipple.trigger(mouse.x, mouse.y);
}
onClicked: root.menuOpen = !root.menuOpen onClicked: root.menuOpen = !root.menuOpen
} }
} }
@@ -576,6 +516,11 @@ Item {
font.pixelSize: 10 font.pixelSize: 10
color: Theme.widgetTextColor color: Theme.widgetTextColor
} }
DankRipple {
id: itemRipple
cornerRadius: Theme.cornerRadius
}
} }
MouseArea { MouseArea {
@@ -586,6 +531,8 @@ Item {
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
onPressed: mouse => { onPressed: mouse => {
const pos = mapToItem(visualContent, mouse.x, mouse.y);
itemRipple.trigger(pos.x, pos.y);
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y); dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start(); longPressTimer.start();
@@ -621,7 +568,7 @@ Item {
if (!delegateRoot.trayItem.hasMenu) if (!delegateRoot.trayItem.hasMenu)
return; return;
root.menuOpen = false; root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
@@ -654,7 +601,7 @@ Item {
if (!delegateRoot.trayItem?.hasMenu) if (!delegateRoot.trayItem?.hasMenu)
return; return;
root.menuOpen = false; root.menuOpen = false;
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis); root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
} }
} }
@@ -687,11 +634,19 @@ Item {
color: Theme.widgetTextColor color: Theme.widgetTextColor
} }
DankRipple {
id: caretRippleVert
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: caretAreaVert id: caretAreaVert
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
caretRippleVert.trigger(mouse.x, mouse.y);
}
onClicked: root.menuOpen = !root.menuOpen onClicked: root.menuOpen = !root.menuOpen
} }
} }
@@ -862,7 +817,7 @@ Item {
const relativeX = globalPos.x - screenX; const relativeX = globalPos.x - screenX;
const relativeY = globalPos.y - screenY; const relativeY = globalPos.y - screenY;
if (root.isVertical) { if (root.isVerticalOrientation) {
const edge = root.axis?.edge; const edge = root.axis?.edge;
let targetX = edge === "left" ? root.barThickness + root.barSpacing + Theme.popupDistance : screen.width - (root.barThickness + root.barSpacing + Theme.popupDistance); let targetX = edge === "left" ? root.barThickness + root.barSpacing + Theme.popupDistance : screen.width - (root.barThickness + root.barSpacing + Theme.popupDistance);
const adjustedY = relativeY + root.height / 2 + root.minTooltipY; const adjustedY = relativeY + root.height / 2 + root.minTooltipY;
@@ -900,7 +855,7 @@ Item {
height: alignedHeight height: alignedHeight
x: Theme.snap((() => { x: Theme.snap((() => {
if (root.isVertical) { if (root.isVerticalOrientation) {
const edge = root.axis?.edge; const edge = root.axis?.edge;
if (edge === "left") { if (edge === "left") {
const targetX = overflowMenu.anchorPos.x; const targetX = overflowMenu.anchorPos.x;
@@ -918,7 +873,7 @@ Item {
})(), overflowMenu.dpr) })(), overflowMenu.dpr)
y: Theme.snap((() => { y: Theme.snap((() => {
if (root.isVertical) { if (root.isVerticalOrientation) {
const top = Math.max(overflowMenu.barY, 10); const top = Math.max(overflowMenu.barY, 10);
const bottom = overflowMenu.height - alignedHeight - 10; const bottom = overflowMenu.height - alignedHeight - 10;
const want = overflowMenu.anchorPos.y - alignedHeight / 2; const want = overflowMenu.anchorPos.y - alignedHeight / 2;
@@ -1074,7 +1029,7 @@ Item {
if (!trayItem.hasMenu) if (!trayItem.hasMenu)
return; return;
root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVertical, root.axis); root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
} }
} }
} }

View File

@@ -133,7 +133,8 @@ BasePill {
z: 1 z: 1
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
if (popoutTarget && popoutTarget.setTriggerPosition) { if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToItem(null, 0, 0); const globalPos = root.visualContent.mapToItem(null, 0, 0);
const currentScreen = parentScreen || Screen; const currentScreen = parentScreen || Screen;

View File

@@ -71,6 +71,7 @@ BasePill {
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: !DMSNetworkService.isBusy enabled: !DMSNetworkService.isBusy
onPressed: event => { onPressed: event => {
root.triggerRipple(this, event.x, event.y);
switch (event.button) { switch (event.button) {
case Qt.RightButton: case Qt.RightButton:
DMSNetworkService.toggleVpn(); DMSNetworkService.toggleVpn();

View File

@@ -268,12 +268,19 @@ PanelWindow {
} }
} }
DankRipple {
id: windowRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: windowArea id: windowArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 24 anchors.rightMargin: 24
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => windowRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (modelData && modelData.activate) { if (modelData && modelData.activate) {
modelData.activate(); modelData.activate();
@@ -340,11 +347,18 @@ PanelWindow {
} }
} }
DankRipple {
id: actionRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: actionArea id: actionArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => actionRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (modelData) { if (modelData) {
SessionService.launchDesktopAction(root.desktopEntry, modelData); SessionService.launchDesktopAction(root.desktopEntry, modelData);
@@ -388,11 +402,18 @@ PanelWindow {
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
DankRipple {
id: pinRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: pinArea id: pinArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (!root.appData) if (!root.appData)
return; return;
@@ -441,11 +462,18 @@ PanelWindow {
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
DankRipple {
id: nvidiaRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: nvidiaArea id: nvidiaArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => nvidiaRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (root.desktopEntry) { if (root.desktopEntry) {
SessionService.launchDesktopEntry(root.desktopEntry, true); SessionService.launchDesktopEntry(root.desktopEntry, true);
@@ -481,11 +509,18 @@ PanelWindow {
wrapMode: Text.NoWrap wrapMode: Text.NoWrap
} }
DankRipple {
id: closeRipple
rippleColor: Theme.error
cornerRadius: Theme.cornerRadius
}
MouseArea { MouseArea {
id: closeArea id: closeArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => closeRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (root.appData?.type === "window") { if (root.appData?.type === "window") {
root.appData?.toplevel?.close(); root.appData?.toplevel?.close();

View File

@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets
Item { Item {
id: root id: root
@@ -18,6 +19,9 @@ Item {
property bool isFirst: false property bool isFirst: false
property bool isLast: false property bool isLast: false
property real sectionSpacing: 0 property real sectionSpacing: 0
property bool enableBackgroundHover: true
property bool enableCursor: true
readonly property bool isMouseHovered: mouseArea.containsMouse
property bool isLeftBarEdge: false property bool isLeftBarEdge: false
property bool isRightBarEdge: false property bool isRightBarEdge: false
property bool isTopBarEdge: false property bool isTopBarEdge: false
@@ -38,6 +42,11 @@ Item {
signal rightClicked(real rootX, real rootY) signal rightClicked(real rootX, real rootY)
signal wheel(var wheelEvent) signal wheel(var wheelEvent)
function triggerRipple(sourceItem, mouseX, mouseY) {
const pos = sourceItem.mapToItem(visualContent, mouseX, mouseY);
rippleLayer.trigger(pos.x, pos.y);
}
width: isVerticalOrientation ? barThickness : visualWidth width: isVerticalOrientation ? barThickness : visualWidth
height: isVerticalOrientation ? visualHeight : barThickness height: isVerticalOrientation ? visualHeight : barThickness
@@ -95,7 +104,7 @@ Item {
} }
const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0; const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
const isHovered = mouseArea.containsMouse || (root.isHovered || false); const isHovered = root.enableBackgroundHover && (mouseArea.containsMouse || (root.isHovered || false));
const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency; const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency;
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor; const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
@@ -106,6 +115,12 @@ Item {
} }
} }
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: background.radius
}
Loader { Loader {
id: contentLoader id: contentLoader
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -121,7 +136,7 @@ Item {
width: root.width + root.leftMargin + root.rightMargin width: root.width + root.leftMargin + root.rightMargin
height: root.height + root.topMargin + root.bottomMargin height: root.height + root.topMargin + root.bottomMargin
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: root.enableCursor ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: function (mouse) { onPressed: function (mouse) {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
@@ -129,6 +144,8 @@ Item {
root.rightClicked(rPos.x, rPos.y); root.rightClicked(rPos.x, rPos.y);
return; return;
} }
const ripplePos = mouseArea.mapToItem(visualContent, mouse.x, mouse.y);
rippleLayer.trigger(ripplePos.x, ripplePos.y);
if (popoutTarget) { if (popoutTarget) {
// Ensure bar context is set first if supported // Ensure bar context is set first if supported
if (popoutTarget.setBarContext) { if (popoutTarget.setBarContext) {

View File

@@ -313,6 +313,12 @@ Popup {
} }
} }
DankRipple {
id: menuItemRipple
rippleColor: modelData.dangerous ? Theme.error : Theme.surfaceText
cornerRadius: menuItem.radius
}
MouseArea { MouseArea {
id: menuItemArea id: menuItemArea
anchors.fill: parent anchors.fill: parent
@@ -323,6 +329,7 @@ Popup {
keyboardNavigation = false; keyboardNavigation = false;
selectedIndex = index; selectedIndex = index;
} }
onPressed: mouse => menuItemRipple.trigger(mouse.x, mouse.y)
onClicked: modelData.action() onClicked: modelData.action()
} }
} }

View File

@@ -16,7 +16,7 @@ Rectangle {
property int buttonHeight: 40 property int buttonHeight: 40
property int horizontalPadding: Theme.spacingL property int horizontalPadding: Theme.spacingL
property bool enableScaleAnimation: false property bool enableScaleAnimation: false
property bool enableRipple: false property bool enableRipple: typeof SettingsData !== "undefined" ? (SettingsData.enableRippleEffects ?? true) : true
signal clicked signal clicked
@@ -55,6 +55,13 @@ Rectangle {
} }
} }
DankRipple {
id: rippleLayer
rippleColor: root.textColor
cornerRadius: root.radius
enableRipple: root.enableRipple
}
Row { Row {
id: contentRow id: contentRow
anchors.centerIn: parent anchors.centerIn: parent
@@ -83,6 +90,10 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: root.enabled enabled: root.enabled
onPressed: mouse => {
if (root.enableRipple)
rippleLayer.trigger(mouse.x, mouse.y);
}
onClicked: root.clicked() onClicked: root.clicked()
} }
} }

View File

@@ -169,6 +169,12 @@ Flow {
} }
} }
DankRipple {
id: segmentRipple
cornerRadius: Theme.cornerRadius
rippleColor: segment.selected ? Theme.buttonText : Theme.surfaceVariantText
}
Item { Item {
id: contentItem id: contentItem
anchors.centerIn: parent anchors.centerIn: parent
@@ -222,6 +228,7 @@ Flow {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => segmentRipple.trigger(mouse.x, mouse.y)
onClicked: root.selectItem(index) onClicked: root.selectItem(index)
} }
} }

View File

@@ -1,8 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Effects
import qs.Common import qs.Common
// Material Design 3 ripple effect component Item {
MouseArea {
id: root id: root
property color rippleColor: Theme.primary property color rippleColor: Theme.primary
@@ -11,10 +11,10 @@ MouseArea {
property real _rippleX: 0 property real _rippleX: 0
property real _rippleY: 0 property real _rippleY: 0
property real _rippleRadius: 0 property real _rippleSize: 0
readonly property alias animating: rippleAnim.running
enabled: false anchors.fill: parent
hoverEnabled: false
function trigger(x, y) { function trigger(x, y) {
if (!enableRipple || Theme.currentAnimationSpeed === SettingsData.AnimationSpeed.None) if (!enableRipple || Theme.currentAnimationSpeed === SettingsData.AnimationSpeed.None)
@@ -24,7 +24,7 @@ MouseArea {
_rippleY = y; _rippleY = y;
const dist = (ox, oy) => ox * ox + oy * oy; const dist = (ox, oy) => ox * ox + oy * oy;
_rippleRadius = Math.sqrt(Math.max(dist(x, y), dist(x, height - y), dist(width - x, y), dist(width - x, height - y))); _rippleSize = Math.sqrt(Math.max(dist(x, y), dist(x, height - y), dist(width - x, y), dist(width - x, height - y))) * 2;
rippleAnim.restart(); rippleAnim.restart();
} }
@@ -42,10 +42,20 @@ MouseArea {
property: "y" property: "y"
value: root._rippleY value: root._rippleY
} }
PropertyAction {
target: ripple
property: "implicitWidth"
value: 0
}
PropertyAction {
target: ripple
property: "implicitHeight"
value: 0
}
PropertyAction { PropertyAction {
target: ripple target: ripple
property: "opacity" property: "opacity"
value: 0.08 value: 0.10
} }
ParallelAnimation { ParallelAnimation {
@@ -53,39 +63,74 @@ MouseArea {
target: ripple target: ripple
property: "implicitWidth" property: "implicitWidth"
from: 0 from: 0
to: root._rippleRadius * 2 to: root._rippleSize
duration: Theme.expressiveDurations.expressiveEffects duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.bezierCurve: Theme.expressiveCurves.standardDecel easing.bezierCurve: Theme.expressiveCurves.standardDecel
} }
DankAnim { DankAnim {
target: ripple target: ripple
property: "implicitHeight" property: "implicitHeight"
from: 0 from: 0
to: root._rippleRadius * 2 to: root._rippleSize
duration: Theme.expressiveDurations.expressiveEffects duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.bezierCurve: Theme.expressiveCurves.standardDecel easing.bezierCurve: Theme.expressiveCurves.standardDecel
} }
} SequentialAnimation {
PauseAnimation {
DankAnim { duration: Math.round(Theme.expressiveDurations.expressiveDefaultSpatial * 0.6)
target: ripple }
property: "opacity" DankAnim {
to: 0 target: ripple
duration: Theme.expressiveDurations.expressiveEffects property: "opacity"
easing.bezierCurve: Theme.expressiveCurves.standard to: 0
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.bezierCurve: Theme.expressiveCurves.standard
}
}
} }
} }
Rectangle { Item {
id: ripple id: rippleContainer
anchors.fill: parent
visible: root.cornerRadius <= 0
radius: Math.min(width, height) / 2 Rectangle {
color: root.rippleColor id: ripple
opacity: 0
transform: Translate { radius: Math.min(width, height) / 2
x: -ripple.width / 2 color: root.rippleColor
y: -ripple.height / 2 opacity: 0
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
} }
} }
Item {
id: rippleMask
anchors.fill: parent
layer.enabled: root.cornerRadius > 0
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: root.cornerRadius
color: "black"
antialiasing: true
}
}
MultiEffect {
anchors.fill: parent
source: rippleContainer
maskEnabled: true
maskSource: rippleMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
visible: root.cornerRadius > 0 && rippleAnim.running
}
} }

View File

@@ -27,81 +27,80 @@ FocusScope {
KeyNavigation.backtab: previousFocusTarget KeyNavigation.backtab: previousFocusTarget
KeyNavigation.up: previousFocusTarget KeyNavigation.up: previousFocusTarget
Keys.onPressed: (event) => { Keys.onPressed: event => {
if (!tabBar.activeFocus || tabRepeater.count === 0) if (!tabBar.activeFocus || tabRepeater.count === 0)
return return;
function findSelectableIndex(startIndex, step) { function findSelectableIndex(startIndex, step) {
let idx = startIndex let idx = startIndex;
for (let i = 0; i < tabRepeater.count; i++) { for (let i = 0; i < tabRepeater.count; i++) {
idx = (idx + step + tabRepeater.count) % tabRepeater.count idx = (idx + step + tabRepeater.count) % tabRepeater.count;
const item = tabRepeater.itemAt(idx) const item = tabRepeater.itemAt(idx);
if (item && !item.isAction) if (item && !item.isAction)
return idx return idx;
} }
return -1 return -1;
} }
const goToIndex = (nextIndex) => { const goToIndex = nextIndex => {
if (nextIndex >= 0 && nextIndex !== tabBar.currentIndex) { if (nextIndex >= 0 && nextIndex !== tabBar.currentIndex) {
tabBar.currentIndex = nextIndex tabBar.currentIndex = nextIndex;
tabBar.tabClicked(nextIndex) tabBar.tabClicked(nextIndex);
} }
} };
const resolveTarget = (item) => { const resolveTarget = item => {
if (!item) if (!item)
return null return null;
if (item.focusTarget) if (item.focusTarget)
return resolveTarget(item.focusTarget) return resolveTarget(item.focusTarget);
return item return item;
} };
const focusItem = (item) => { const focusItem = item => {
const target = resolveTarget(item) const target = resolveTarget(item);
if (!target) if (!target)
return false return false;
if (target.requestFocus) { if (target.requestFocus) {
Qt.callLater(() => target.requestFocus()) Qt.callLater(() => target.requestFocus());
return true return true;
} }
if (target.forceActiveFocus) { if (target.forceActiveFocus) {
Qt.callLater(() => target.forceActiveFocus()) Qt.callLater(() => target.forceActiveFocus());
return true return true;
} }
return false return false;
} };
if (event.key === Qt.Key_Right && tabBar.enableArrowNavigation) { if (event.key === Qt.Key_Right && tabBar.enableArrowNavigation) {
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1 const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1;
const nextIndex = findSelectableIndex(baseIndex, 1) const nextIndex = findSelectableIndex(baseIndex, 1);
if (nextIndex >= 0) { if (nextIndex >= 0) {
goToIndex(nextIndex) goToIndex(nextIndex);
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_Left && tabBar.enableArrowNavigation) { } else if (event.key === Qt.Key_Left && tabBar.enableArrowNavigation) {
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0 const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0;
const nextIndex = findSelectableIndex(baseIndex, -1) const nextIndex = findSelectableIndex(baseIndex, -1);
if (nextIndex >= 0) { if (nextIndex >= 0) {
goToIndex(nextIndex) goToIndex(nextIndex);
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) { } else if (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) {
if (focusItem(tabBar.previousFocusTarget)) { if (focusItem(tabBar.previousFocusTarget)) {
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down) { } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down) {
if (focusItem(tabBar.nextFocusTarget)) { if (focusItem(tabBar.nextFocusTarget)) {
event.accepted = true event.accepted = true;
} }
} else if (event.key === Qt.Key_Up) { } else if (event.key === Qt.Key_Up) {
if (focusItem(tabBar.previousFocusTarget)) { if (focusItem(tabBar.previousFocusTarget)) {
event.accepted = true event.accepted = true;
} }
} }
} }
@@ -142,7 +141,7 @@ FocusScope {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: tabItem.isActive ? Theme.primary : Theme.surfaceText color: tabItem.isActive ? Theme.primary : Theme.surfaceText
font.weight: tabItem.isActive ? Font.Medium : Font.Normal font.weight: Font.Medium
visible: hasText visible: hasText
} }
} }
@@ -154,7 +153,17 @@ FocusScope {
opacity: tabArea.pressed ? 0.12 : (tabArea.containsMouse ? 0.08 : 0) opacity: tabArea.pressed ? 0.12 : (tabArea.containsMouse ? 0.08 : 0)
visible: opacity > 0 visible: opacity > 0
radius: Theme.cornerRadius radius: Theme.cornerRadius
Behavior on opacity { NumberAnimation { duration: Theme.shortDuration; easing.type: Theme.standardEasing } } Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
DankRipple {
id: tabRipple
cornerRadius: Theme.cornerRadius
} }
MouseArea { MouseArea {
@@ -162,15 +171,15 @@ FocusScope {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => tabRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
if (tabItem.isAction) { if (tabItem.isAction) {
tabBar.actionTriggered(index) tabBar.actionTriggered(index);
} else { } else {
tabBar.tabClicked(index) tabBar.tabClicked(index);
} }
} }
} }
} }
} }
} }
@@ -216,39 +225,39 @@ FocusScope {
function updateIndicator() { function updateIndicator() {
if (tabRepeater.count === 0 || currentIndex < 0 || currentIndex >= tabRepeater.count) { if (tabRepeater.count === 0 || currentIndex < 0 || currentIndex >= tabRepeater.count) {
return return;
} }
const item = tabRepeater.itemAt(currentIndex) const item = tabRepeater.itemAt(currentIndex);
if (!item || item.isAction) { if (!item || item.isAction) {
return return;
} }
const tabPos = item.mapToItem(tabBar, 0, 0) const tabPos = item.mapToItem(tabBar, 0, 0);
const tabCenterX = tabPos.x + item.width / 2 const tabCenterX = tabPos.x + item.width / 2;
const indicatorWidth = 60 const indicatorWidth = 60;
if (tabPos.x < 10 && currentIndex > 0) { if (tabPos.x < 10 && currentIndex > 0) {
Qt.callLater(updateIndicator) Qt.callLater(updateIndicator);
return return;
} }
if (!indicator.initialSetupComplete) { if (!indicator.initialSetupComplete) {
indicator.animationEnabled = false indicator.animationEnabled = false;
indicator.width = indicatorWidth indicator.width = indicatorWidth;
indicator.x = tabCenterX - indicatorWidth / 2 indicator.x = tabCenterX - indicatorWidth / 2;
indicator.visible = true indicator.visible = true;
indicator.initialSetupComplete = true indicator.initialSetupComplete = true;
indicator.animationEnabled = true indicator.animationEnabled = true;
} else { } else {
indicator.width = indicatorWidth indicator.width = indicatorWidth;
indicator.x = tabCenterX - indicatorWidth / 2 indicator.x = tabCenterX - indicatorWidth / 2;
indicator.visible = true indicator.visible = true;
} }
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
Qt.callLater(updateIndicator) Qt.callLater(updateIndicator);
} }
onWidthChanged: Qt.callLater(updateIndicator) onWidthChanged: Qt.callLater(updateIndicator)
} }