1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00

Add keyboard navi and wallpaper IPCs

This commit is contained in:
bbedward
2025-08-26 11:47:48 -04:00
parent aed4c0ad17
commit 05d3fd2a19
6 changed files with 903 additions and 167 deletions

View File

@@ -1109,4 +1109,5 @@ Singleton {
target: "bar"
}
}

View File

@@ -0,0 +1,276 @@
import QtQuick
import QtQuick.Controls
import QtCore
import Qt.labs.folderlistmodel
import Quickshell.Io
import qs.Common
import qs.Widgets
Rectangle {
id: root
property bool showFileInfo: false
property int selectedIndex: -1
property var sourceFolderModel: null
property string currentPath: ""
height: 200
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
Theme.surfaceContainer.b, 0.95)
border.color: Theme.secondary
border.width: 2
opacity: showFileInfo ? 1 : 0
z: 100
onShowFileInfoChanged: {
if (showFileInfo && currentFileName && currentPath) {
var fullPath = currentPath + "/" + currentFileName
fileStatProcess.selectedFilePath = fullPath
fileStatProcess.running = true
}
}
Process {
id: fileStatProcess
command: ["stat", "-c", "%y|%A|%s|%n", selectedFilePath]
property string selectedFilePath: ""
property var fileStats: null
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text && text.trim()) {
var parts = text.trim().split('|')
if (parts.length >= 4) {
fileStatProcess.fileStats = {
modifiedTime: parts[0],
permissions: parts[1],
size: parseInt(parts[2]) || 0,
fullPath: parts[3]
}
}
}
}
}
onExited: function(exitCode) {}
}
readonly property var currentFileData: {
if (!sourceFolderModel || selectedIndex < 0 || selectedIndex >= sourceFolderModel.count) {
return {
exists: false,
name: "No selection",
type: "",
size: "",
modified: "",
permissions: "",
extension: "",
position: selectedIndex >= 0 ? (selectedIndex + 1) + " of " + (sourceFolderModel ? sourceFolderModel.count : 0) : "N/A"
}
}
var path = currentPath
if (path && selectedIndex >= 0) {
return {
exists: true,
name: "Loading...",
type: "file",
size: "Calculating...",
modified: "Loading...",
permissions: "Loading...",
extension: "",
position: (selectedIndex + 1) + " of " + sourceFolderModel.count
}
}
return {
exists: false,
name: "No selection",
type: "",
size: "",
modified: "",
permissions: "",
extension: "",
position: "N/A"
}
}
property string currentFileName: ""
property bool currentFileIsDir: false
property string currentFileExtension: ""
onCurrentFileNameChanged: {
if (showFileInfo && currentFileName && currentPath) {
var fullPath = currentPath + "/" + currentFileName
if (fullPath !== fileStatProcess.selectedFilePath) {
fileStatProcess.selectedFilePath = fullPath
fileStatProcess.running = true
}
}
}
function updateFileInfo(filePath, fileName, isDirectory) {
if (filePath && filePath !== fileStatProcess.selectedFilePath) {
fileStatProcess.selectedFilePath = filePath
currentFileName = fileName || ""
currentFileIsDir = isDirectory || false
var ext = ""
if (!isDirectory && fileName) {
var lastDot = fileName.lastIndexOf('.')
if (lastDot > 0) {
ext = fileName.substring(lastDot + 1).toLowerCase()
}
}
currentFileExtension = ext
if (showFileInfo) {
fileStatProcess.running = true
}
}
}
readonly property var currentFileDisplayData: {
if (selectedIndex < 0 || !sourceFolderModel) {
return {
exists: false,
name: "No selection",
type: "",
size: "",
modified: "",
permissions: "",
extension: "",
position: "N/A"
}
}
var hasValidFile = currentFileName !== ""
return {
exists: hasValidFile,
name: hasValidFile ? currentFileName : "Loading...",
type: currentFileIsDir ? "Directory" : "File",
size: fileStatProcess.fileStats ? formatFileSize(fileStatProcess.fileStats.size) : "Calculating...",
modified: fileStatProcess.fileStats ? formatDateTime(fileStatProcess.fileStats.modifiedTime) : "Loading...",
permissions: fileStatProcess.fileStats ? fileStatProcess.fileStats.permissions : "Loading...",
extension: currentFileExtension,
position: sourceFolderModel ? ((selectedIndex + 1) + " of " + sourceFolderModel.count) : "N/A"
}
}
Column {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingS
DankIcon {
name: "info"
size: Theme.iconSize
color: Theme.secondary
}
StyledText {
text: "File Information"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: currentFileDisplayData.name
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideMiddle
wrapMode: Text.NoWrap
font.weight: Font.Medium
}
StyledText {
text: currentFileDisplayData.type + (currentFileDisplayData.extension ? " (." + currentFileDisplayData.extension + ")" : "")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: parent.width
}
StyledText {
text: currentFileDisplayData.size
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: parent.width
visible: currentFileDisplayData.exists && !currentFileIsDir
}
StyledText {
text: currentFileDisplayData.modified
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: parent.width
elide: Text.ElideRight
visible: currentFileDisplayData.exists
}
StyledText {
text: currentFileDisplayData.permissions
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
visible: currentFileDisplayData.exists
}
StyledText {
text: currentFileDisplayData.position
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
width: parent.width
}
}
}
StyledText {
text: "F1/I: Toggle • F10: Help"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingM
horizontalAlignment: Text.AlignHCenter
}
function formatFileSize(bytes) {
if (bytes === 0 || !bytes) return "0 B"
var k = 1024
var sizes = ['B', 'KB', 'MB', 'GB', 'TB']
var i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
function formatDateTime(dateTimeString) {
if (!dateTimeString) return "Unknown"
var parts = dateTimeString.split(' ')
if (parts.length >= 2) {
return parts[0] + " " + parts[1].split('.')[0]
}
return dateTimeString
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -0,0 +1,51 @@
import QtQuick
import qs.Common
import qs.Widgets
Rectangle {
id: root
property bool showHints: false
height: 80
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
Theme.surfaceContainer.b, 0.95)
border.color: Theme.primary
border.width: 2
opacity: showHints ? 1 : 0
z: 100
Column {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingS
spacing: 2
StyledText {
text: "Tab/Shift+Tab: Nav • ←→↑↓: Grid Nav • Enter/Space: Select"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -22,6 +22,9 @@ DankModal {
property string browserIcon: "folder_open"
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
property bool showHiddenFiles: false
property int selectedIndex: -1
property bool keyboardNavigationActive: false
property bool backButtonFocused: false
FolderListModel {
id: folderModel
@@ -73,16 +76,217 @@ DankModal {
visible: false
onBackgroundClicked: close()
onOpened: {
modalFocusScope.forceActiveFocus()
}
modalFocusScope.Keys.onPressed: function(event) {
keyboardController.handleKey(event)
}
onVisibleChanged: {
if (visible) {
var startPath = getLastPath()
currentPath = startPath
selectedIndex = -1
keyboardNavigationActive = false
backButtonFocused = false
}
}
onCurrentPathChanged: {
selectedFilePath = ""
selectedFileName = ""
selectedFileIsDir = false
}
onSelectedIndexChanged: {
// Update selected file data when index changes
if (selectedIndex >= 0 && folderModel && selectedIndex < folderModel.count) {
// We need to get the file data from the model for this index
// This is a bit tricky with FolderListModel, so we'll use a different approach
selectedFilePath = ""
selectedFileName = ""
selectedFileIsDir = false
}
}
// Function to update file data from delegates
function setSelectedFileData(path, name, isDir) {
selectedFilePath = path
selectedFileName = name
selectedFileIsDir = isDir
}
QtObject {
id: keyboardController
property int totalItems: folderModel.count
property int gridColumns: 5 // Fixed number of columns for the grid (matches actual display)
function handleKey(event) {
if (event.key === Qt.Key_Escape) {
close()
event.accepted = true
return
}
// F10 toggles keyboard hints
if (event.key === Qt.Key_F10) {
showKeyboardHints = !showKeyboardHints
event.accepted = true
return
}
// F1 or I key for file information
if (event.key === Qt.Key_F1 || event.key === Qt.Key_I) {
showFileInfo = !showFileInfo
event.accepted = true
return
}
// Alt+Left or Backspace to go back
if ((event.modifiers & Qt.AltModifier && event.key === Qt.Key_Left) || event.key === Qt.Key_Backspace) {
if (currentPath !== homeDir) {
navigateUp()
event.accepted = true
}
return
}
if (!keyboardNavigationActive) {
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right) {
keyboardNavigationActive = true
if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
} else {
backButtonFocused = false
selectedIndex = 0
}
event.accepted = true
}
return
}
switch (event.key) {
case Qt.Key_Tab:
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
} else if (selectedIndex < totalItems - 1) {
selectedIndex++
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
} else {
selectedIndex = 0
}
event.accepted = true
break
case Qt.Key_Backtab:
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = totalItems - 1
} else if (selectedIndex > 0) {
selectedIndex--
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
} else {
selectedIndex = totalItems - 1
}
event.accepted = true
break
case Qt.Key_Left:
if (backButtonFocused) {
return
}
if (selectedIndex > 0) {
selectedIndex--
// Update file info for navigation
updateFileInfoForIndex(selectedIndex)
} else if (currentPath !== homeDir) {
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
break
case Qt.Key_Right:
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
updateFileInfoForIndex(selectedIndex)
} else if (selectedIndex < totalItems - 1) {
selectedIndex++
updateFileInfoForIndex(selectedIndex)
}
event.accepted = true
break
case Qt.Key_Up:
if (backButtonFocused) {
backButtonFocused = false
// Go to first row, appropriate column
var col = selectedIndex % gridColumns
selectedIndex = Math.min(col, totalItems - 1)
updateFileInfoForIndex(selectedIndex)
} else if (selectedIndex >= gridColumns) {
// Move up one row
selectedIndex -= gridColumns
updateFileInfoForIndex(selectedIndex)
} else if (currentPath !== homeDir) {
// At top row, go to back button
backButtonFocused = true
selectedIndex = -1
}
event.accepted = true
break
case Qt.Key_Down:
if (backButtonFocused) {
backButtonFocused = false
selectedIndex = 0
updateFileInfoForIndex(selectedIndex)
} else {
// Move down one row if possible
var newIndex = selectedIndex + gridColumns
if (newIndex < totalItems) {
selectedIndex = newIndex
updateFileInfoForIndex(selectedIndex)
} else {
// If can't go down a full row, go to last item in the column if exists
var lastRowStart = Math.floor((totalItems - 1) / gridColumns) * gridColumns
var col = selectedIndex % gridColumns
var targetIndex = lastRowStart + col
if (targetIndex < totalItems && targetIndex > selectedIndex) {
selectedIndex = targetIndex
updateFileInfoForIndex(selectedIndex)
}
}
}
event.accepted = true
break
case Qt.Key_Return:
case Qt.Key_Enter:
case Qt.Key_Space:
if (backButtonFocused) {
navigateUp()
} else if (selectedIndex >= 0 && selectedIndex < totalItems) {
// Trigger selection by setting the grid's current index and using signal
fileBrowserModal.keyboardFileSelection(selectedIndex)
}
event.accepted = true
break
}
// Scroll handling is done in the grid's onCurrentIndexChanged
}
}
function navigateUp() {
@@ -108,190 +312,347 @@ DankModal {
function navigateTo(path) {
currentPath = path
saveLastPath(path) // Save the path when navigating
selectedIndex = -1
backButtonFocused = false
}
function keyboardFileSelection(index) {
if (index >= 0) {
keyboardSelectionTimer.targetIndex = index
keyboardSelectionTimer.start()
}
}
function updateSelectedFileInfo(index) {
// This will be called when we need to update file info for the selected index
// The delegate will handle the actual file info updates
}
function updateFileInfoForIndex(index) {
// We can't directly access FolderListModel data by index from here
// Instead, we'll rely on the delegate's Component.onCompleted and mouse clicks
// to call setSelectedFileData() with the proper file information
// For keyboard navigation, we need a different approach
// The selectedIndex change will trigger delegate updates
}
Timer {
id: keyboardSelectionTimer
interval: 1
property int targetIndex: -1
onTriggered: {
// Access the currently selected item through model role names
// This will work because QML models expose role data
executeKeyboardSelection(targetIndex)
}
}
function executeKeyboardSelection(index) {
// This is a simplified version that just needs to work
// We'll handle this in the mouse area of each delegate
// For now, signal that keyboard selection was requested
keyboardSelectionIndex = index
keyboardSelectionRequested = true
}
property int keyboardSelectionIndex: -1
property bool keyboardSelectionRequested: false
property bool showKeyboardHints: false
property bool showFileInfo: false
property string selectedFilePath: ""
property string selectedFileName: ""
property bool selectedFileIsDir: false
content: Component {
Column {
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Item {
width: parent.width
height: 40
Row {
spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: browserIcon
size: Theme.iconSizeLarge
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: browserTitle
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
DankActionButton {
circular: false
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Theme.errorHover
onClicked: fileBrowserModal.close()
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
width: parent.width
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
StyledRect {
width: 32
height: 32
radius: Theme.cornerRadius
color: mouseArea.containsMouse
&& currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
opacity: currentPath !== homeDir ? 1.0 : 0.0
Item {
width: parent.width
height: 40
DankIcon {
anchors.centerIn: parent
name: "arrow_back"
size: Theme.iconSizeSmall
color: Theme.surfaceText
}
Row {
spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: currentPath !== homeDir
cursorShape: currentPath
!== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: currentPath !== homeDir
onClicked: navigateUp()
}
}
StyledText {
text: fileBrowserModal.currentPath.replace("file://", "")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width - 40 - Theme.spacingS
elide: Text.ElideMiddle
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
wrapMode: Text.NoWrap
}
}
DankGridView {
id: fileGrid
width: parent.width
height: parent.height - 80
clip: true
cellWidth: 150
cellHeight: 130
cacheBuffer: 260
model: folderModel
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
delegate: StyledRect {
id: delegateRoot
required property bool fileIsDir
required property string filePath
required property string fileName
required property url fileURL
width: 140
height: 120
radius: Theme.cornerRadius
color: mouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
border.color: Theme.outline
border.width: mouseArea.containsMouse ? 1 : 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
Item {
width: 80
height: 60
anchors.horizontalCenter: parent.horizontalCenter
CachingImage {
anchors.fill: parent
source: (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
fillMode: Image.PreserveAspectCrop
visible: !delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)
maxCacheSize: 80
}
DankIcon {
anchors.centerIn: parent
name: "description"
size: Theme.iconSizeLarge
color: Theme.primary
visible: !delegateRoot.fileIsDir
&& !isImageFile(delegateRoot.fileName)
}
DankIcon {
anchors.centerIn: parent
name: "folder"
size: Theme.iconSizeLarge
color: Theme.primary
visible: delegateRoot.fileIsDir
}
DankIcon {
name: browserIcon
size: Theme.iconSizeLarge
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: delegateRoot.fileName || ""
font.pixelSize: Theme.fontSizeSmall
text: browserTitle
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
width: 120
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
maximumLineCount: 2
wrapMode: Text.WordWrap
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (delegateRoot.fileIsDir) {
navigateTo(delegateRoot.filePath)
} else {
fileSelected(delegateRoot.filePath)
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankActionButton {
circular: false
iconName: "help"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Theme.surfacePressed
onClicked: fileBrowserModal.showKeyboardHints = !fileBrowserModal.showKeyboardHints
}
DankActionButton {
circular: false
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Theme.errorHover
onClicked: fileBrowserModal.close()
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: 32
height: 32
radius: Theme.cornerRadius
color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive))
&& currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
opacity: currentPath !== homeDir ? 1.0 : 0.0
DankIcon {
anchors.centerIn: parent
name: "arrow_back"
size: Theme.iconSizeSmall
color: Theme.surfaceText
}
MouseArea {
id: backButtonMouseArea
anchors.fill: parent
hoverEnabled: currentPath !== homeDir
cursorShape: currentPath
!== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: currentPath !== homeDir
onClicked: navigateUp()
}
}
StyledText {
text: fileBrowserModal.currentPath.replace("file://", "")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width - 40 - Theme.spacingS
elide: Text.ElideMiddle
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
wrapMode: Text.NoWrap
}
}
DankGridView {
id: fileGrid
width: parent.width
height: parent.height - 80
clip: true
cellWidth: 150
cellHeight: 130
cacheBuffer: 260
model: folderModel
currentIndex: selectedIndex
onCurrentIndexChanged: {
if (keyboardNavigationActive && currentIndex >= 0) {
positionViewAtIndex(currentIndex, GridView.Contain)
}
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
delegate: StyledRect {
id: delegateRoot
required property bool fileIsDir
required property string filePath
required property string fileName
required property url fileURL
required property int index
width: 140
height: 120
radius: Theme.cornerRadius
color: {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex) {
return Theme.surfacePressed
}
return mouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
}
border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : Theme.outline
border.width: (mouseArea.containsMouse || (keyboardNavigationActive && delegateRoot.index === selectedIndex)) ? 1 : 0
// Update file info when this item gets selected via keyboard or initially
Component.onCompleted: {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex) {
setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
}
}
// Watch for selectedIndex changes to update file info during keyboard navigation
Connections {
target: fileBrowserModal
function onSelectedIndexChanged() {
if (keyboardNavigationActive && selectedIndex === delegateRoot.index) {
setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
}
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
Item {
width: 80
height: 60
anchors.horizontalCenter: parent.horizontalCenter
CachingImage {
anchors.fill: parent
source: (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
fillMode: Image.PreserveAspectCrop
visible: !delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)
maxCacheSize: 80
}
DankIcon {
anchors.centerIn: parent
name: "description"
size: Theme.iconSizeLarge
color: Theme.primary
visible: !delegateRoot.fileIsDir
&& !isImageFile(delegateRoot.fileName)
}
DankIcon {
anchors.centerIn: parent
name: "folder"
size: Theme.iconSizeLarge
color: Theme.primary
visible: delegateRoot.fileIsDir
}
}
StyledText {
text: delegateRoot.fileName || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
width: 120
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
maximumLineCount: 2
wrapMode: Text.WordWrap
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// Update selected file info and index first
selectedIndex = delegateRoot.index
setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
if (delegateRoot.fileIsDir) {
navigateTo(delegateRoot.filePath)
} else {
fileSelected(delegateRoot.filePath)
fileBrowserModal.close() // Close modal after file selection
}
}
}
// Handle keyboard selection
Connections {
target: fileBrowserModal
function onKeyboardSelectionRequestedChanged() {
if (fileBrowserModal.keyboardSelectionRequested && fileBrowserModal.keyboardSelectionIndex === delegateRoot.index) {
// Reset the flag first
fileBrowserModal.keyboardSelectionRequested = false
// Update selected file info and index first
selectedIndex = delegateRoot.index
setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
// Trigger the same action as mouse click
if (delegateRoot.fileIsDir) {
navigateTo(delegateRoot.filePath)
} else {
fileSelected(delegateRoot.filePath)
fileBrowserModal.close()
}
}
}
}
}
}
}
FileBrowserKeyboardHints {
id: keyboardHints
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingL
showHints: fileBrowserModal.showKeyboardHints
}
FileBrowserFileInfo {
id: fileInfo
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Theme.spacingL
width: 300
showFileInfo: fileBrowserModal.showFileInfo
selectedIndex: fileBrowserModal.selectedIndex
sourceFolderModel: folderModel
currentPath: fileBrowserModal.currentPath
// Bind directly to the modal's selected file properties
currentFileName: fileBrowserModal.selectedFileName
currentFileIsDir: fileBrowserModal.selectedFileIsDir
currentFileExtension: {
if (fileBrowserModal.selectedFileIsDir || !fileBrowserModal.selectedFileName) return ""
var lastDot = fileBrowserModal.selectedFileName.lastIndexOf('.')
return lastDot > 0 ? fileBrowserModal.selectedFileName.substring(lastDot + 1).toLowerCase() : ""
}
}
}
}

View File

@@ -56,6 +56,20 @@ DankModal {
target: "settings"
}
IpcHandler {
function browse(type: string) {
if (type === "wallpaper") {
wallpaperBrowser.allowStacking = false
wallpaperBrowser.open()
} else if (type === "profile") {
profileBrowser.allowStacking = false
profileBrowser.open()
}
}
target: "file"
}
settingsContent: Component {
Item {
anchors.fill: parent
@@ -561,6 +575,7 @@ DankModal {
FileBrowserModal {
id: profileBrowser
allowStacking: true
browserTitle: "Select Profile Image"
browserIcon: "person"
browserType: "profile"
@@ -576,6 +591,24 @@ DankModal {
return settingsModal.shouldBeVisible
})
}
allowStacking = true
}
}
FileBrowserModal {
id: wallpaperBrowser
allowStacking: true
browserTitle: "Select Wallpaper"
browserIcon: "wallpaper"
browserType: "wallpaper"
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => {
SessionData.setWallpaper(path)
close()
}
onDialogClosed: {
allowStacking = true
}
}
}

View File

@@ -369,6 +369,16 @@ Power menu modal control for system power actions.
- `close` - Hide power menu modal
- `toggle` - Toggle power menu modal visibility
### Target: `file`
File browser controls for selecting wallpapers and profile images.
**Functions:**
- `browse <type>` - Open file browser for specific file type
- Parameters: `type` - Either "wallpaper" or "profile"
- `wallpaper` - Opens wallpaper file browser in Pictures directory
- `profile` - Opens profile image file browser in Pictures directory
- Both browsers support common image formats (jpg, jpeg, png, bmp, gif, webp)
### Modal Examples
```bash
# Open application launcher
@@ -388,6 +398,10 @@ qs -c dms ipc call processlist toggle
# Show power menu
qs -c dms ipc call powermenu toggle
# Open file browsers
qs -c dms ipc call file browse wallpaper
qs -c dms ipc call file browse profile
```
## Common Usage Patterns