1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-07 06:12:08 -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);
}
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: root.radius
}
Rectangle {
id: indexBadge
anchors.left: parent.left
@@ -138,6 +144,10 @@ Rectangle {
anchors.rightMargin: 80
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
const pos = mouseArea.mapToItem(root, mouse.x, mouse.y);
rippleLayer.trigger(pos.x, pos.y);
}
onClicked: copyRequested()
}
}

View File

@@ -53,6 +53,12 @@ Rectangle {
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
DankRipple {
id: rippleLayer
rippleColor: Theme.surfaceText
cornerRadius: root.radius
}
Column {
anchors.centerIn: parent
anchors.margins: Theme.spacingS
@@ -99,6 +105,10 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton)
rippleLayer.trigger(mouse.x, mouse.y);
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
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 {
id: itemMouseArea
anchors.fill: parent
@@ -484,6 +490,7 @@ Popup {
root.keyboardNavigation = false;
root.selectedMenuIndex = menuItemDelegate.itemIndex;
}
onPressed: mouse => menuItemRipple.trigger(mouse.x, mouse.y)
onClicked: {
var menuItem = menuItemDelegate.modelData;
if (menuItem.action)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,12 +22,18 @@ Row {
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)
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea {
id: iconArea
anchors.fill: parent
visible: defaultSink !== null
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: {
if (defaultSink) {
SessionData.suppressOSDTemporarily();

View File

@@ -91,12 +91,18 @@ Row {
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)
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea {
id: iconArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DisplayService.devices && DisplayService.devices.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: {
if (DisplayService.devices && DisplayService.devices.length > 1) {
root.iconClicked();

View File

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

View File

@@ -22,12 +22,18 @@ Row {
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)
DankRipple {
id: iconRipple
cornerRadius: parent.radius
}
MouseArea {
id: iconArea
anchors.fill: parent
visible: defaultSource !== null
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
onClicked: {
if (defaultSource) {
SessionData.suppressOSDTemporarily();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,8 @@ BasePill {
property bool compactMode: false
signal clockClicked
onClicked: clockClicked()
content: Component {
Item {
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
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: {
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
root.colorPickerRequested();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,31 +12,101 @@ BasePill {
property var widgetData: null
property bool compactMode: widgetData?.keyboardLayoutNameCompactMode !== undefined ? widgetData.keyboardLayoutNameCompactMode : SettingsData.keyboardLayoutNameCompactMode
readonly property var langCodes: ({
"afrikaans": "af", "albanian": "sq", "amharic": "am", "arabic": "ar",
"armenian": "hy", "azerbaijani": "az", "basque": "eu", "belarusian": "be",
"bengali": "bn", "bosnian": "bs", "bulgarian": "bg", "burmese": "my",
"catalan": "ca", "chinese": "zh", "croatian": "hr", "czech": "cs",
"danish": "da", "dutch": "nl", "english": "en", "esperanto": "eo",
"estonian": "et", "filipino": "fil", "finnish": "fi", "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"
})
"afrikaans": "af",
"albanian": "sq",
"amharic": "am",
"arabic": "ar",
"armenian": "hy",
"azerbaijani": "az",
"basque": "eu",
"belarusian": "be",
"bengali": "bn",
"bosnian": "bs",
"bulgarian": "bg",
"burmese": "my",
"catalan": "ca",
"chinese": "zh",
"croatian": "hr",
"czech": "cs",
"danish": "da",
"dutch": "nl",
"english": "en",
"esperanto": "eo",
"estonian": "et",
"filipino": "fil",
"finnish": "fi",
"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"]
property string currentLayout: {
if (CompositorService.isNiri) {
@@ -119,6 +189,9 @@ BasePill {
z: 1
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
}
onClicked: {
if (CompositorService.isNiri) {
NiriService.cycleKeyboardLayout();

View File

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

View File

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

View File

@@ -1,247 +1,171 @@
import QtQuick
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Item {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
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
section: "right"
property bool showMicIcon: SettingsData.privacyShowMicIcon
property bool showCameraIcon: SettingsData.privacyShowCameraIcon
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 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 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
opacity: hasActivePrivacy ? 1 : 0
enabled: hasActivePrivacy
Item {
id: visualContent
width: root.visualWidth
height: root.visualHeight
anchors.centerIn: parent
content: Component {
Item {
implicitWidth: root.hasActivePrivacy ? root.contentWidth : 0
implicitHeight: root.hasActivePrivacy ? root.contentHeight : 0
Rectangle {
id: outline
anchors.centerIn: parent
width: {
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);
}
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: root.isVerticalOrientation && root.hasActivePrivacy
Rectangle {
id: background
anchors.fill: parent
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: {
if (barConfig?.noBackground ?? false) {
return "transparent";
}
Item {
width: 18
height: 18
visible: PrivacyService.microphoneActive
anchors.horizontalCenter: parent.horizontalCenter
const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
const isHovered = privacyArea.containsMouse;
const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency;
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
return Theme.withAlpha(baseColor, transparency);
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: root.isVertical && root.hasActivePrivacy
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";
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
}
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 {
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
}
}
}
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
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 {
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 {
width: 18
height: 18
visible: showScreenSharingIcon || PrivacyService.screensharingActive
anchors.verticalCenter: parent.verticalCenter
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
visible: !root.isVerticalOrientation && root.hasActivePrivacy
DankIcon {
name: "screen_share"
size: Theme.iconSizeSmall
color: PrivacyService.screensharingActive ? Theme.warning : Theme.surfaceText
filled: true
anchors.centerIn: parent
Item {
width: 18
height: 18
visible: root.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: 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 {
id: tooltip
width: tooltipText.contentWidth + Theme.spacingM * 2
@@ -251,7 +175,7 @@ Item {
border.color: Theme.outlineMedium
border.width: 1
visible: false
opacity: privacyArea.containsMouse && hasActivePrivacy ? 1 : 0
opacity: root.isMouseHovered && hasActivePrivacy ? 1 : 0
z: 100
x: (parent.width - width) / 2
y: -height - Theme.spacingXS
@@ -287,7 +211,7 @@ Item {
}
Behavior on width {
enabled: hasActivePrivacy && visible && !isVertical
enabled: hasActivePrivacy && visible && !isVerticalOrientation
NumberAnimation {
duration: Theme.mediumDuration
@@ -296,7 +220,7 @@ Item {
}
Behavior on height {
enabled: hasActivePrivacy && visible && isVertical
enabled: hasActivePrivacy && visible && isVerticalOrientation
NumberAnimation {
duration: Theme.mediumDuration

View File

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

View File

@@ -5,25 +5,21 @@ import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Item {
BasePill {
id: root
enableBackgroundHover: false
enableCursor: false
section: "left"
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 topBar: null
property real widgetThickness: 30
property real barThickness: 48
property real barSpacing: 4
property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
readonly property real effectiveBarThickness: {
@@ -52,7 +48,7 @@ Item {
readonly property real barY: barBounds.y
readonly property real minTooltipY: {
if (!parentScreen || !isVertical) {
if (!parentScreen || !isVerticalOrientation) {
return 0;
}
@@ -139,108 +135,42 @@ Item {
}
}
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
Item {
id: visualBackground
width: root.isVertical ? root.widgetThickness : root.calculatedSize
height: root.isVertical ? root.calculatedSize : root.widgetThickness
anchors.centerIn: parent
clip: false
property real scrollAccumulator: 0
property real touchpadThreshold: 500
Rectangle {
id: outline
anchors.centerIn: parent
width: {
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);
onWheel: function (wheelEvent) {
const deltaY = wheelEvent.angleDelta.y;
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
const windows = root.sortedToplevels;
if (windows.length < 2)
return;
if (isMouseWheel) {
let currentIndex = -1;
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i;
break;
}
}
}
Rectangle {
id: background
anchors.fill: parent
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
color: {
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;
let nextIndex;
if (deltaY < 0) {
nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, windows.length - 1);
} else {
nextIndex = currentIndex === -1 ? windows.length - 1 : Math.max(currentIndex - 1, 0);
}
if (isMouseWheel) {
// Direct mouse wheel action
const nextWindow = windows[nextIndex];
if (nextWindow)
nextWindow.activate();
} else {
scrollAccumulator += deltaY;
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
let currentIndex = -1;
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
@@ -250,69 +180,32 @@ Item {
}
let nextIndex;
if (deltaY < 0) {
if (currentIndex === -1) {
nextIndex = 0;
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1);
}
if (scrollAccumulator < 0) {
nextIndex = currentIndex === -1 ? 0 : Math.min(currentIndex + 1, windows.length - 1);
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1;
} else {
nextIndex = Math.max(currentIndex - 1, 0);
}
nextIndex = currentIndex === -1 ? windows.length - 1 : Math.max(currentIndex - 1, 0);
}
const nextWindow = windows[nextIndex];
if (nextWindow) {
if (nextWindow)
nextWindow.activate();
}
} else {
// Touchpad - accumulate small deltas
scrollAccumulator += deltaY;
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
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;
}
scrollAccumulator = 0;
}
wheel.accepted = true;
}
}
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVertical ? columnLayout : rowLayout
content: Component {
Item {
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVerticalOrientation ? columnLayout : rowLayout
}
}
}
Component {
@@ -466,6 +359,7 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
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.triggerBarThickness = root.barThickness;
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
if (root.isVertical) {
if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
@@ -526,7 +420,7 @@ Item {
root.hoveredItem = delegateItem;
tooltipLoader.active = true;
if (tooltipLoader.item) {
if (root.isVertical) {
if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
@@ -711,6 +605,7 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
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.triggerBarThickness = root.barThickness;
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
if (root.isVertical) {
if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
@@ -765,7 +660,7 @@ Item {
root.hoveredItem = delegateItem;
tooltipLoader.active = true;
if (tooltipLoader.item) {
if (root.isVertical) {
if (root.isVerticalOrientation) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;

View File

@@ -6,23 +6,19 @@ import Quickshell.Services.SystemTray
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
Item {
BasePill {
id: root
property bool isVertical: axis?.isVertical ?? false
property var axis: null
enableBackgroundHover: false
enableCursor: false
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 var barConfig: null
property bool isAutoHideBar: false
readonly property real horizontalPadding: (barConfig?.noBackground ?? false) ? 2 : Theme.spacingS
readonly property var hiddenTrayIds: {
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || "";
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : [];
@@ -102,21 +98,10 @@ Item {
property int dropTargetIndex: -1
property bool suppressShiftAnimation: false
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
readonly property real minTooltipY: {
if (!parentScreen || !isVertical) {
if (!parentScreen || !isVerticalOrientation) {
return 0;
}
@@ -135,77 +120,17 @@ Item {
property bool menuOpen: false
property var currentTrayMenu: null
Item {
id: visualBackground
width: root.visualWidth
height: root.visualHeight
anchors.centerIn: parent
content: Component {
Item {
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
Rectangle {
id: outline
anchors.centerIn: parent
width: {
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);
}
Loader {
id: layoutLoader
anchors.centerIn: parent
sourceComponent: root.isVerticalOrientation ? columnComp : rowComp
}
}
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 {
@@ -334,6 +259,11 @@ Item {
font.pixelSize: 10
color: Theme.widgetTextColor
}
DankRipple {
id: itemRipple
cornerRadius: Theme.cornerRadius
}
}
MouseArea {
@@ -344,6 +274,8 @@ Item {
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
onPressed: mouse => {
const pos = mapToItem(visualContent, mouse.x, mouse.y);
itemRipple.trigger(pos.x, pos.y);
if (mouse.button === Qt.LeftButton) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
@@ -379,7 +311,7 @@ Item {
if (!delegateRoot.trayItem.hasMenu)
return;
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 => {
@@ -412,7 +344,7 @@ Item {
if (!delegateRoot.trayItem?.hasMenu)
return;
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
}
DankRipple {
id: caretRipple
cornerRadius: Theme.cornerRadius
}
MouseArea {
id: caretArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
caretRipple.trigger(mouse.x, mouse.y);
}
onClicked: root.menuOpen = !root.menuOpen
}
}
@@ -576,6 +516,11 @@ Item {
font.pixelSize: 10
color: Theme.widgetTextColor
}
DankRipple {
id: itemRipple
cornerRadius: Theme.cornerRadius
}
}
MouseArea {
@@ -586,6 +531,8 @@ Item {
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
onPressed: mouse => {
const pos = mapToItem(visualContent, mouse.x, mouse.y);
itemRipple.trigger(pos.x, pos.y);
if (mouse.button === Qt.LeftButton) {
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
@@ -621,7 +568,7 @@ Item {
if (!delegateRoot.trayItem.hasMenu)
return;
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 => {
@@ -654,7 +601,7 @@ Item {
if (!delegateRoot.trayItem?.hasMenu)
return;
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
}
DankRipple {
id: caretRippleVert
cornerRadius: Theme.cornerRadius
}
MouseArea {
id: caretAreaVert
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => {
caretRippleVert.trigger(mouse.x, mouse.y);
}
onClicked: root.menuOpen = !root.menuOpen
}
}
@@ -862,7 +817,7 @@ Item {
const relativeX = globalPos.x - screenX;
const relativeY = globalPos.y - screenY;
if (root.isVertical) {
if (root.isVerticalOrientation) {
const edge = root.axis?.edge;
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;
@@ -900,7 +855,7 @@ Item {
height: alignedHeight
x: Theme.snap((() => {
if (root.isVertical) {
if (root.isVerticalOrientation) {
const edge = root.axis?.edge;
if (edge === "left") {
const targetX = overflowMenu.anchorPos.x;
@@ -918,7 +873,7 @@ Item {
})(), overflowMenu.dpr)
y: Theme.snap((() => {
if (root.isVertical) {
if (root.isVerticalOrientation) {
const top = Math.max(overflowMenu.barY, 10);
const bottom = overflowMenu.height - alignedHeight - 10;
const want = overflowMenu.anchorPos.y - alignedHeight / 2;
@@ -1074,7 +1029,7 @@ Item {
if (!trayItem.hasMenu)
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
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: {
onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y);
if (popoutTarget && popoutTarget.setTriggerPosition) {
const globalPos = root.visualContent.mapToItem(null, 0, 0);
const currentScreen = parentScreen || Screen;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import QtQuick
import QtQuick.Effects
import qs.Common
// Material Design 3 ripple effect component
MouseArea {
Item {
id: root
property color rippleColor: Theme.primary
@@ -11,10 +11,10 @@ MouseArea {
property real _rippleX: 0
property real _rippleY: 0
property real _rippleRadius: 0
property real _rippleSize: 0
readonly property alias animating: rippleAnim.running
enabled: false
hoverEnabled: false
anchors.fill: parent
function trigger(x, y) {
if (!enableRipple || Theme.currentAnimationSpeed === SettingsData.AnimationSpeed.None)
@@ -24,7 +24,7 @@ MouseArea {
_rippleY = y;
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();
}
@@ -42,10 +42,20 @@ MouseArea {
property: "y"
value: root._rippleY
}
PropertyAction {
target: ripple
property: "implicitWidth"
value: 0
}
PropertyAction {
target: ripple
property: "implicitHeight"
value: 0
}
PropertyAction {
target: ripple
property: "opacity"
value: 0.08
value: 0.10
}
ParallelAnimation {
@@ -53,39 +63,74 @@ MouseArea {
target: ripple
property: "implicitWidth"
from: 0
to: root._rippleRadius * 2
duration: Theme.expressiveDurations.expressiveEffects
to: root._rippleSize
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.bezierCurve: Theme.expressiveCurves.standardDecel
}
DankAnim {
target: ripple
property: "implicitHeight"
from: 0
to: root._rippleRadius * 2
duration: Theme.expressiveDurations.expressiveEffects
to: root._rippleSize
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.bezierCurve: Theme.expressiveCurves.standardDecel
}
}
DankAnim {
target: ripple
property: "opacity"
to: 0
duration: Theme.expressiveDurations.expressiveEffects
easing.bezierCurve: Theme.expressiveCurves.standard
SequentialAnimation {
PauseAnimation {
duration: Math.round(Theme.expressiveDurations.expressiveDefaultSpatial * 0.6)
}
DankAnim {
target: ripple
property: "opacity"
to: 0
duration: Theme.expressiveDurations.expressiveDefaultSpatial
easing.bezierCurve: Theme.expressiveCurves.standard
}
}
}
}
Rectangle {
id: ripple
Item {
id: rippleContainer
anchors.fill: parent
visible: root.cornerRadius <= 0
radius: Math.min(width, height) / 2
color: root.rippleColor
opacity: 0
Rectangle {
id: ripple
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
radius: Math.min(width, height) / 2
color: root.rippleColor
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.up: previousFocusTarget
Keys.onPressed: (event) => {
Keys.onPressed: event => {
if (!tabBar.activeFocus || tabRepeater.count === 0)
return
return;
function findSelectableIndex(startIndex, step) {
let idx = startIndex
let idx = startIndex;
for (let i = 0; i < tabRepeater.count; i++) {
idx = (idx + step + tabRepeater.count) % tabRepeater.count
const item = tabRepeater.itemAt(idx)
idx = (idx + step + tabRepeater.count) % tabRepeater.count;
const item = tabRepeater.itemAt(idx);
if (item && !item.isAction)
return idx
return idx;
}
return -1
return -1;
}
const goToIndex = (nextIndex) => {
const goToIndex = nextIndex => {
if (nextIndex >= 0 && nextIndex !== tabBar.currentIndex) {
tabBar.currentIndex = nextIndex
tabBar.tabClicked(nextIndex)
tabBar.currentIndex = nextIndex;
tabBar.tabClicked(nextIndex);
}
}
};
const resolveTarget = (item) => {
const resolveTarget = item => {
if (!item)
return null
return null;
if (item.focusTarget)
return resolveTarget(item.focusTarget)
return resolveTarget(item.focusTarget);
return item
}
return item;
};
const focusItem = (item) => {
const target = resolveTarget(item)
const focusItem = item => {
const target = resolveTarget(item);
if (!target)
return false
return false;
if (target.requestFocus) {
Qt.callLater(() => target.requestFocus())
return true
Qt.callLater(() => target.requestFocus());
return true;
}
if (target.forceActiveFocus) {
Qt.callLater(() => target.forceActiveFocus())
return true
Qt.callLater(() => target.forceActiveFocus());
return true;
}
return false
}
return false;
};
if (event.key === Qt.Key_Right && tabBar.enableArrowNavigation) {
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1
const nextIndex = findSelectableIndex(baseIndex, 1)
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : -1;
const nextIndex = findSelectableIndex(baseIndex, 1);
if (nextIndex >= 0) {
goToIndex(nextIndex)
event.accepted = true
goToIndex(nextIndex);
event.accepted = true;
}
} else if (event.key === Qt.Key_Left && tabBar.enableArrowNavigation) {
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0
const nextIndex = findSelectableIndex(baseIndex, -1)
const baseIndex = (tabBar.currentIndex >= 0 && tabBar.currentIndex < tabRepeater.count) ? tabBar.currentIndex : 0;
const nextIndex = findSelectableIndex(baseIndex, -1);
if (nextIndex >= 0) {
goToIndex(nextIndex)
event.accepted = true
goToIndex(nextIndex);
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) {
if (focusItem(tabBar.previousFocusTarget)) {
event.accepted = true
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down) {
if (focusItem(tabBar.nextFocusTarget)) {
event.accepted = true
event.accepted = true;
}
} else if (event.key === Qt.Key_Up) {
if (focusItem(tabBar.previousFocusTarget)) {
event.accepted = true
event.accepted = true;
}
}
}
@@ -142,7 +141,7 @@ FocusScope {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: Theme.fontSizeMedium
color: tabItem.isActive ? Theme.primary : Theme.surfaceText
font.weight: tabItem.isActive ? Font.Medium : Font.Normal
font.weight: Font.Medium
visible: hasText
}
}
@@ -154,7 +153,17 @@ FocusScope {
opacity: tabArea.pressed ? 0.12 : (tabArea.containsMouse ? 0.08 : 0)
visible: opacity > 0
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 {
@@ -162,15 +171,15 @@ FocusScope {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => tabRipple.trigger(mouse.x, mouse.y)
onClicked: {
if (tabItem.isAction) {
tabBar.actionTriggered(index)
tabBar.actionTriggered(index);
} else {
tabBar.tabClicked(index)
tabBar.tabClicked(index);
}
}
}
}
}
}
@@ -216,39 +225,39 @@ FocusScope {
function updateIndicator() {
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) {
return
return;
}
const tabPos = item.mapToItem(tabBar, 0, 0)
const tabCenterX = tabPos.x + item.width / 2
const indicatorWidth = 60
const tabPos = item.mapToItem(tabBar, 0, 0);
const tabCenterX = tabPos.x + item.width / 2;
const indicatorWidth = 60;
if (tabPos.x < 10 && currentIndex > 0) {
Qt.callLater(updateIndicator)
return
Qt.callLater(updateIndicator);
return;
}
if (!indicator.initialSetupComplete) {
indicator.animationEnabled = false
indicator.width = indicatorWidth
indicator.x = tabCenterX - indicatorWidth / 2
indicator.visible = true
indicator.initialSetupComplete = true
indicator.animationEnabled = true
indicator.animationEnabled = false;
indicator.width = indicatorWidth;
indicator.x = tabCenterX - indicatorWidth / 2;
indicator.visible = true;
indicator.initialSetupComplete = true;
indicator.animationEnabled = true;
} else {
indicator.width = indicatorWidth
indicator.x = tabCenterX - indicatorWidth / 2
indicator.visible = true
indicator.width = indicatorWidth;
indicator.x = tabCenterX - indicatorWidth / 2;
indicator.visible = true;
}
}
onCurrentIndexChanged: {
Qt.callLater(updateIndicator)
Qt.callLater(updateIndicator);
}
onWidthChanged: Qt.callLater(updateIndicator)
}