1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 16:02:51 -05:00

de-dupe scrollview

This commit is contained in:
bbedward
2025-07-27 20:56:09 -04:00
parent fae7f36a24
commit 77051d0d62
9 changed files with 333 additions and 122 deletions

View File

@@ -501,29 +501,46 @@ DankModal {
border.width: 1 border.width: 1
clip: true clip: true
ScrollView { ListView {
id: clipboardListView
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded model: filteredClipboardModel
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff spacing: Theme.spacingXS
ListView { ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
id: clipboardListView ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
width: parent.availableWidth property real wheelMultiplier: 1.8
model: filteredClipboardModel property int wheelBaseStep: 160
spacing: Theme.spacingXS
StyledText { WheelHandler {
text: "No clipboard entries found" target: null
anchors.centerIn: parent onWheel: (ev) => {
font.pixelSize: Theme.fontSizeMedium let dy = ev.pixelDelta.y !== 0
color: Theme.surfaceVariantText ? ev.pixelDelta.y
visible: filteredClipboardModel.count === 0 : (ev.angleDelta.y / 120) * clipboardListView.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, clipboardListView.contentHeight - clipboardListView.height);
clipboardListView.contentY = Math.max(0, Math.min(maxY,
clipboardListView.contentY - dy * clipboardListView.wheelMultiplier));
ev.accepted = true;
} }
}
delegate: Rectangle { StyledText {
text: "No clipboard entries found"
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: filteredClipboardModel.count === 0
}
delegate: Rectangle {
property string entryType: getEntryType(model.entry) property string entryType: getEntryType(model.entry)
property string entryPreview: getEntryPreview(model.entry) property string entryPreview: getEntryPreview(model.entry)
property int entryIndex: index + 1 property int entryIndex: index + 1
@@ -729,8 +746,6 @@ DankModal {
} }
}
} }
} }

View File

@@ -198,21 +198,41 @@ DankModal {
} }
// File grid // File grid
ScrollView { GridView {
id: fileGrid
width: parent.width width: parent.width
height: parent.height - 80 height: parent.height - 80
clip: true clip: true
cellWidth: 150
cellHeight: 130
cacheBuffer: 260
model: folderModel
GridView { ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
id: fileGrid ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
cellWidth: 150
cellHeight: 130
cacheBuffer: 260 // Only cache ~2 rows worth of items
model: folderModel
delegate: StyledRect { property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * fileGrid.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, fileGrid.contentHeight - fileGrid.height);
fileGrid.contentY = Math.max(0, Math.min(maxY,
fileGrid.contentY - dy * fileGrid.wheelMultiplier));
ev.accepted = true;
}
}
delegate: StyledRect {
id: delegateRoot id: delegateRoot
required property bool fileIsDir required property bool fileIsDir
@@ -292,7 +312,6 @@ DankModal {
} }
} }
} }
}
} }
} }
} }

View File

@@ -91,18 +91,36 @@ DankModal {
} }
// Network Details // Network Details
ScrollView { Flickable {
width: parent.width width: parent.width
height: parent.height - 140 height: parent.height - 140
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded contentWidth: width
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff contentHeight: detailsRect.height
Flickable { ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
contentWidth: parent.width ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
contentHeight: detailsRect.height
Rectangle { property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * parent.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, parent.contentHeight - parent.height);
parent.contentY = Math.max(0, Math.min(maxY,
parent.contentY - dy * parent.wheelMultiplier));
ev.accepted = true;
}
}
Rectangle {
id: detailsRect id: detailsRect
width: parent.width width: parent.width
@@ -124,8 +142,6 @@ DankModal {
lineHeight: 1.5 lineHeight: 1.5
} }
}
} }
} }

View File

@@ -9,7 +9,7 @@ Rectangle {
property var process: null property var process: null
property var contextMenu: null property var contextMenu: null
width: parent.width width: parent ? parent.width : 0
height: 40 height: 40
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"

View File

@@ -45,7 +45,11 @@ Column {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("name") onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("name");
processListView.restoreAnchor();
}
} }
Behavior on color { Behavior on color {
@@ -76,7 +80,11 @@ Column {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("cpu") onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("cpu");
processListView.restoreAnchor();
}
} }
Behavior on color { Behavior on color {
@@ -107,7 +115,11 @@ Column {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("memory") onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("memory");
processListView.restoreAnchor();
}
} }
Behavior on color { Behavior on color {
@@ -139,7 +151,11 @@ Column {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("pid") onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("pid");
processListView.restoreAnchor();
}
} }
Behavior on color { Behavior on color {
@@ -169,7 +185,11 @@ Column {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.toggleSortOrder() onClicked: {
processListView.captureAnchor();
SysMonitorService.toggleSortOrder();
processListView.restoreAnchor();
}
} }
Behavior on color { Behavior on color {
@@ -183,23 +203,74 @@ Column {
} }
ScrollView { ListView {
id: processListView
width: parent.width width: parent.width
height: parent.height - 24 // Subtract header height height: parent.height - columnHeaders.height
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded spacing: 4
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff model: SysMonitorService.processes
ListView { delegate: ProcessListItem {
id: processListView process: modelData
contextMenu: root.contextMenu
}
anchors.fill: parent ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
model: SysMonitorService.processes ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
spacing: 4
delegate: ProcessListItem { property real wheelMultiplier: 1.8
process: modelData property int wheelBaseStep: 160
contextMenu: root.contextMenu
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * processListView.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, processListView.contentHeight - processListView.height);
processListView.contentY = Math.max(0, Math.min(maxY,
processListView.contentY - dy * processListView.wheelMultiplier));
ev.accepted = true;
}
}
property string keyRoleName: "pid"
property var _anchorKey: undefined
property real _anchorOffset: 0
function captureAnchor() {
const y = contentY + 1;
const idx = indexAt(0, y);
if (idx < 0 || !model || idx >= model.length) return;
_anchorKey = model[idx][keyRoleName];
const it = itemAtIndex(idx);
_anchorOffset = it ? (y - it.y) : 0;
}
function restoreAnchor() {
Qt.callLater(function() {
if (_anchorKey === undefined || !model) return;
var i = -1;
for (var j = 0; j < model.length; ++j) {
if (model[j][keyRoleName] === _anchorKey) { i = j; break; }
}
if (i < 0) return;
positionViewAtIndex(i, ListView.Beginning);
const maxY = Math.max(0, contentHeight - height);
contentY = Math.max(0, Math.min(maxY, contentY + _anchorOffset - 1));
});
}
onModelChanged: {
if (model && model.length > 0) {
restoreAnchor();
} }
} }
} }

View File

@@ -14,6 +14,55 @@ Singleton {
signal volumeChanged() signal volumeChanged()
function displayName(node) {
if (!node) return ""
if (node.properties && node.properties["device.description"]) {
return node.properties["device.description"]
}
if (node.description && node.description !== node.name) {
return node.description
}
if (node.nickname && node.nickname !== node.name) {
return node.nickname
}
if (node.name.includes("analog-stereo")) return "Built-in Speakers"
else if (node.name.includes("bluez")) return "Bluetooth Audio"
else if (node.name.includes("usb")) return "USB Audio"
else if (node.name.includes("hdmi")) return "HDMI Audio"
return node.name
}
function subtitle(name) {
if (!name) return ""
if (name.includes('usb-')) {
if (name.includes('SteelSeries')) {
return "USB Gaming Headset"
} else if (name.includes('Generic')) {
return "USB Audio Device"
}
return "USB Audio"
} else if (name.includes('pci-')) {
if (name.includes('01_00.1') || name.includes('01:00.1')) {
return "NVIDIA GPU Audio"
}
return "PCI Audio"
} else if (name.includes('bluez')) {
return "Bluetooth Audio"
} else if (name.includes('analog')) {
return "Built-in Audio"
} else if (name.includes('hdmi')) {
return "HDMI Audio"
}
return ""
}
PwObjectTracker { PwObjectTracker {
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource] objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
} }

View File

@@ -145,16 +145,36 @@ Rectangle {
border.width: 1 border.width: 1
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
ScrollView { ListView {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
clip: true clip: true
model: root.options
spacing: 2
ListView { ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
model: root.options ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
spacing: 2
delegate: Rectangle { property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * parent.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, parent.contentHeight - parent.height);
parent.contentY = Math.max(0, Math.min(maxY,
parent.contentY - dy * parent.wheelMultiplier));
ev.accepted = true;
}
}
delegate: Rectangle {
width: ListView.view.width width: ListView.view.width
height: 32 height: 32
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
@@ -196,8 +216,6 @@ Rectangle {
} }
} }
}
} }
} }

View File

@@ -4,10 +4,8 @@ import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
ScrollView { GridView {
id: gridView id: gridView
property alias model: grid.model
property int currentIndex: 0 property int currentIndex: 0
property int columns: 4 property int columns: 4
property bool adaptiveColumns: false property bool adaptiveColumns: false
@@ -26,15 +24,15 @@ ScrollView {
// Ensure the current item is visible // Ensure the current item is visible
function ensureVisible(index) { function ensureVisible(index) {
if (index < 0 || index >= grid.count) if (index < 0 || index >= gridView.count)
return ; return ;
var itemY = Math.floor(index / grid.actualColumns) * grid.cellHeight; var itemY = Math.floor(index / gridView.actualColumns) * gridView.cellHeight;
var itemBottom = itemY + grid.cellHeight; var itemBottom = itemY + gridView.cellHeight;
if (itemY < grid.contentY) if (itemY < gridView.contentY)
grid.contentY = itemY; gridView.contentY = itemY;
else if (itemBottom > grid.contentY + grid.height) else if (itemBottom > gridView.contentY + gridView.height)
grid.contentY = itemBottom - grid.height; gridView.contentY = itemBottom - gridView.height;
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
@@ -43,31 +41,47 @@ ScrollView {
} }
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
GridView { ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
id: grid ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns property real wheelMultiplier: 1.8
property int baseCellHeight: baseCellWidth + 20 property int wheelBaseStep: 160
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
anchors.fill: parent WheelHandler {
anchors.margins: Theme.spacingS target: null
cellWidth: baseCellWidth onWheel: (ev) => {
cellHeight: baseCellHeight let dy = ev.pixelDelta.y !== 0
leftMargin: Math.max(Theme.spacingS, remainingSpace / 2) ? ev.pixelDelta.y
rightMargin: leftMargin : (ev.angleDelta.y / 120) * gridView.wheelBaseStep;
focus: true if (ev.inverted) dy = -dy;
interactive: true
flickDeceleration: 300
maximumFlickVelocity: 30000
delegate: Rectangle { const maxY = Math.max(0, gridView.contentHeight - gridView.height);
width: grid.cellWidth - cellPadding gridView.contentY = Math.max(0, Math.min(maxY,
height: grid.cellHeight - cellPadding gridView.contentY - dy * gridView.wheelMultiplier));
ev.accepted = true;
}
}
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
anchors.margins: Theme.spacingS
cellWidth: baseCellWidth
cellHeight: baseCellHeight
leftMargin: Math.max(Theme.spacingS, remainingSpace / 2)
rightMargin: leftMargin
focus: true
interactive: true
flickDeceleration: 300
maximumFlickVelocity: 30000
delegate: Rectangle {
width: gridView.cellWidth - cellPadding
height: gridView.cellHeight - cellPadding
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: currentIndex === index ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: currentIndex === index ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
border.color: currentIndex === index ? Theme.primarySelected : Theme.outlineMedium border.color: currentIndex === index ? Theme.primarySelected : Theme.outlineMedium
@@ -78,7 +92,7 @@ ScrollView {
spacing: Theme.spacingS spacing: Theme.spacingS
Item { Item {
property int iconSize: Math.min(maxIconSize, Math.max(minIconSize, grid.cellWidth * iconSizeRatio)) property int iconSize: Math.min(maxIconSize, Math.max(minIconSize, gridView.cellWidth * iconSizeRatio))
width: iconSize width: iconSize
height: iconSize height: iconSize
@@ -116,7 +130,7 @@ ScrollView {
Text { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: grid.cellWidth - 12 width: gridView.cellWidth - 12
text: model.name || "" text: model.name || ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
@@ -153,6 +167,4 @@ ScrollView {
} }
}
} }

View File

@@ -4,10 +4,8 @@ import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
ScrollView { ListView {
id: listView id: listView
property alias model: list.model
property int currentIndex: 0 property int currentIndex: 0
property int itemHeight: 72 property int itemHeight: 72
property int iconSize: 56 property int iconSize: 56
@@ -22,15 +20,15 @@ ScrollView {
// Ensure the current item is visible // Ensure the current item is visible
function ensureVisible(index) { function ensureVisible(index) {
if (index < 0 || index >= list.count) if (index < 0 || index >= listView.count)
return ; return ;
var itemY = index * (itemHeight + itemSpacing); var itemY = index * (itemHeight + itemSpacing);
var itemBottom = itemY + itemHeight; var itemBottom = itemY + itemHeight;
if (itemY < list.contentY) if (itemY < listView.contentY)
list.contentY = itemY; listView.contentY = itemY;
else if (itemBottom > list.contentY + list.height) else if (itemBottom > listView.contentY + listView.height)
list.contentY = itemBottom - list.height; listView.contentY = itemBottom - listView.height;
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
@@ -39,24 +37,39 @@ ScrollView {
} }
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView { ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOn }
id: list ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
anchors.fill: parent property real wheelMultiplier: 1.8
anchors.margins: itemSpacing property int wheelBaseStep: 160
spacing: listView.itemSpacing
focus: true
interactive: true
currentIndex: listView.currentIndex
flickDeceleration: 600
maximumFlickVelocity: 30000
delegate: Rectangle { WheelHandler {
width: list.width target: null
height: itemHeight onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * listView.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, listView.contentHeight - listView.height);
listView.contentY = Math.max(0, Math.min(maxY,
listView.contentY - dy * listView.wheelMultiplier));
ev.accepted = true;
}
}
anchors.margins: itemSpacing
spacing: itemSpacing
focus: true
interactive: true
flickDeceleration: 600
maximumFlickVelocity: 30000
delegate: Rectangle {
width: listView.width
height: itemHeight
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: ListView.isCurrentItem ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: ListView.isCurrentItem ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium
@@ -153,6 +166,4 @@ ScrollView {
} }
}
} }