1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 14:02:53 -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
clip: true
ScrollView {
ListView {
id: clipboardListView
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
model: filteredClipboardModel
spacing: Theme.spacingXS
ListView {
id: clipboardListView
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
width: parent.availableWidth
model: filteredClipboardModel
spacing: Theme.spacingXS
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
StyledText {
text: "No clipboard entries found"
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: filteredClipboardModel.count === 0
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (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 entryPreview: getEntryPreview(model.entry)
property int entryIndex: index + 1
@@ -729,8 +746,6 @@ DankModal {
}
}
}
}

View File

@@ -198,21 +198,41 @@ DankModal {
}
// File grid
ScrollView {
GridView {
id: fileGrid
width: parent.width
height: parent.height - 80
clip: true
cellWidth: 150
cellHeight: 130
cacheBuffer: 260
model: folderModel
GridView {
id: fileGrid
cellWidth: 150
cellHeight: 130
cacheBuffer: 260 // Only cache ~2 rows worth of items
model: folderModel
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
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
required property bool fileIsDir
@@ -292,7 +312,6 @@ DankModal {
}
}
}
}
}
}
}

View File

@@ -91,18 +91,36 @@ DankModal {
}
// Network Details
ScrollView {
Flickable {
width: parent.width
height: parent.height - 140
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentWidth: width
contentHeight: detailsRect.height
Flickable {
contentWidth: parent.width
contentHeight: detailsRect.height
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
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
width: parent.width
@@ -124,8 +142,6 @@ DankModal {
lineHeight: 1.5
}
}
}
}

View File

@@ -9,7 +9,7 @@ Rectangle {
property var process: null
property var contextMenu: null
width: parent.width
width: parent ? parent.width : 0
height: 40
radius: Theme.cornerRadiusLarge
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
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("name")
onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("name");
processListView.restoreAnchor();
}
}
Behavior on color {
@@ -76,7 +80,11 @@ Column {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("cpu")
onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("cpu");
processListView.restoreAnchor();
}
}
Behavior on color {
@@ -107,7 +115,11 @@ Column {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("memory")
onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("memory");
processListView.restoreAnchor();
}
}
Behavior on color {
@@ -139,7 +151,11 @@ Column {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.setSortBy("pid")
onClicked: {
processListView.captureAnchor();
SysMonitorService.setSortBy("pid");
processListView.restoreAnchor();
}
}
Behavior on color {
@@ -169,7 +185,11 @@ Column {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SysMonitorService.toggleSortOrder()
onClicked: {
processListView.captureAnchor();
SysMonitorService.toggleSortOrder();
processListView.restoreAnchor();
}
}
Behavior on color {
@@ -183,23 +203,74 @@ Column {
}
ScrollView {
ListView {
id: processListView
width: parent.width
height: parent.height - 24 // Subtract header height
height: parent.height - columnHeaders.height
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
spacing: 4
model: SysMonitorService.processes
ListView {
id: processListView
delegate: ProcessListItem {
process: modelData
contextMenu: root.contextMenu
}
anchors.fill: parent
model: SysMonitorService.processes
spacing: 4
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
delegate: ProcessListItem {
process: modelData
contextMenu: root.contextMenu
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) * 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()
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 {
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
}

View File

@@ -145,16 +145,36 @@ Rectangle {
border.width: 1
radius: Theme.cornerRadiusSmall
ScrollView {
ListView {
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
model: root.options
spacing: 2
ListView {
model: root.options
spacing: 2
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
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
height: 32
radius: Theme.cornerRadiusSmall
@@ -196,8 +216,6 @@ Rectangle {
}
}
}
}
}

View File

@@ -4,10 +4,8 @@ import Quickshell
import Quickshell.Widgets
import qs.Common
ScrollView {
GridView {
id: gridView
property alias model: grid.model
property int currentIndex: 0
property int columns: 4
property bool adaptiveColumns: false
@@ -26,15 +24,15 @@ ScrollView {
// Ensure the current item is visible
function ensureVisible(index) {
if (index < 0 || index >= grid.count)
if (index < 0 || index >= gridView.count)
return ;
var itemY = Math.floor(index / grid.actualColumns) * grid.cellHeight;
var itemBottom = itemY + grid.cellHeight;
if (itemY < grid.contentY)
grid.contentY = itemY;
else if (itemBottom > grid.contentY + grid.height)
grid.contentY = itemBottom - grid.height;
var itemY = Math.floor(index / gridView.actualColumns) * gridView.cellHeight;
var itemBottom = itemY + gridView.cellHeight;
if (itemY < gridView.contentY)
gridView.contentY = itemY;
else if (itemBottom > gridView.contentY + gridView.height)
gridView.contentY = itemBottom - gridView.height;
}
onCurrentIndexChanged: {
@@ -43,31 +41,47 @@ ScrollView {
}
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
GridView {
id: grid
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
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)
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
anchors.fill: parent
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
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * gridView.wheelBaseStep;
if (ev.inverted) dy = -dy;
delegate: Rectangle {
width: grid.cellWidth - cellPadding
height: grid.cellHeight - cellPadding
const maxY = Math.max(0, gridView.contentHeight - gridView.height);
gridView.contentY = Math.max(0, Math.min(maxY,
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
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
@@ -78,7 +92,7 @@ ScrollView {
spacing: Theme.spacingS
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
height: iconSize
@@ -116,7 +130,7 @@ ScrollView {
Text {
anchors.horizontalCenter: parent.horizontalCenter
width: grid.cellWidth - 12
width: gridView.cellWidth - 12
text: model.name || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
@@ -153,6 +167,4 @@ ScrollView {
}
}
}

View File

@@ -4,10 +4,8 @@ import Quickshell
import Quickshell.Widgets
import qs.Common
ScrollView {
ListView {
id: listView
property alias model: list.model
property int currentIndex: 0
property int itemHeight: 72
property int iconSize: 56
@@ -22,15 +20,15 @@ ScrollView {
// Ensure the current item is visible
function ensureVisible(index) {
if (index < 0 || index >= list.count)
if (index < 0 || index >= listView.count)
return ;
var itemY = index * (itemHeight + itemSpacing);
var itemBottom = itemY + itemHeight;
if (itemY < list.contentY)
list.contentY = itemY;
else if (itemBottom > list.contentY + list.height)
list.contentY = itemBottom - list.height;
if (itemY < listView.contentY)
listView.contentY = itemY;
else if (itemBottom > listView.contentY + listView.height)
listView.contentY = itemBottom - listView.height;
}
onCurrentIndexChanged: {
@@ -39,24 +37,39 @@ ScrollView {
}
clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: list
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOn }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
anchors.fill: parent
anchors.margins: itemSpacing
spacing: listView.itemSpacing
focus: true
interactive: true
currentIndex: listView.currentIndex
flickDeceleration: 600
maximumFlickVelocity: 30000
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
delegate: Rectangle {
width: list.width
height: itemHeight
WheelHandler {
target: null
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
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
@@ -153,6 +166,4 @@ ScrollView {
}
}
}