From ae6a1b77c200e239a8237cbcaadd83f59f412ce7 Mon Sep 17 00:00:00 2001 From: bbedward Date: Wed, 3 Sep 2025 00:00:25 -0400 Subject: [PATCH] systematic cleanups and qmlfmt file browser modal --- Modals/Clipboard/ClipboardContent.qml | 3 +- .../{ => Clipboard}/ClipboardHistoryModal.qml | 5 +- Modals/ConfirmModal.qml | 65 +-- Modals/DankModal.qml | 77 ++- Modals/{ => FileBrowser}/FileBrowserModal.qml | 532 ++++++++---------- .../FileInfo.qml} | 125 ++-- .../KeyboardHints.qml} | 5 +- Modals/NotepadModal.qml | 1 + Modals/SettingsModal.qml | 1 + Modules/Settings/PersonalizationTab.qml | 1 + Modules/Settings/ThemeColorsTab.qml | 1 + Widgets/DankIcon.qml | 12 +- shell.qml | 1 + 13 files changed, 354 insertions(+), 475 deletions(-) rename Modals/{ => Clipboard}/ClipboardHistoryModal.qml (97%) rename Modals/{ => FileBrowser}/FileBrowserModal.qml (66%) rename Modals/{FileBrowserFileInfo.qml => FileBrowser/FileInfo.qml} (65%) rename Modals/{FileBrowserKeyboardHints.qml => FileBrowser/KeyboardHints.qml} (95%) diff --git a/Modals/Clipboard/ClipboardContent.qml b/Modals/Clipboard/ClipboardContent.qml index b38fbc5b..ec03ce27 100644 --- a/Modals/Clipboard/ClipboardContent.qml +++ b/Modals/Clipboard/ClipboardContent.qml @@ -34,8 +34,7 @@ Item { clearConfirmDialog.show("Clear All History?", "This will permanently delete all clipboard history.", function () { modal.clearAll() modal.hide() - }, function () {} // No action on cancel - ) + }, function () {}) } onCloseClicked: modal.hide() } diff --git a/Modals/ClipboardHistoryModal.qml b/Modals/Clipboard/ClipboardHistoryModal.qml similarity index 97% rename from Modals/ClipboardHistoryModal.qml rename to Modals/Clipboard/ClipboardHistoryModal.qml index a29bbc1e..5596c4fc 100644 --- a/Modals/ClipboardHistoryModal.qml +++ b/Modals/Clipboard/ClipboardHistoryModal.qml @@ -7,7 +7,7 @@ import Quickshell.Io import qs.Common import qs.Services import qs.Widgets -import qs.Modals.Clipboard +import qs.Modals DankModal { id: clipboardHistoryModal @@ -170,6 +170,7 @@ DankModal { property alias filteredClipboardModel: filteredClipboardModel property alias clipboardModel: clipboardModel + property var confirmDialog: clearConfirmDialog ListModel { id: clipboardModel @@ -209,7 +210,7 @@ DankModal { ClipboardContent { modal: clipboardHistoryModal filteredModel: filteredClipboardModel - clearConfirmDialog: clipboardHistoryModal.clearConfirmDialog + clearConfirmDialog: clipboardHistoryModal.confirmDialog } } } diff --git a/Modals/ConfirmModal.qml b/Modals/ConfirmModal.qml index 904da7e7..688364c1 100644 --- a/Modals/ConfirmModal.qml +++ b/Modals/ConfirmModal.qml @@ -1,9 +1,5 @@ import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Io import qs.Common -import qs.Services import qs.Widgets DankModal { @@ -14,10 +10,9 @@ DankModal { property string confirmButtonText: "Confirm" property string cancelButtonText: "Cancel" property color confirmButtonColor: Theme.primary - property var onConfirm: function() {} - property var onCancel: function() {} - - property int selectedButton: -1 // -1 = none, 0 = Cancel, 1 = Confirm + property var onConfirm: function () {} + property var onCancel: function () {} + property int selectedButton: -1 property bool keyboardNavigation: false function show(title, message, onConfirmCallback, onCancelCallback) { @@ -26,33 +21,36 @@ DankModal { confirmButtonText = "Confirm" cancelButtonText = "Cancel" confirmButtonColor = Theme.primary - onConfirm = onConfirmCallback || function() {} - onCancel = onCancelCallback || function() {} + onConfirm = onConfirmCallback || (() => {}) + onCancel = onCancelCallback || (() => {}) selectedButton = -1 keyboardNavigation = false open() } - + function showWithOptions(options) { confirmTitle = options.title || "" confirmMessage = options.message || "" confirmButtonText = options.confirmText || "Confirm" cancelButtonText = options.cancelText || "Cancel" confirmButtonColor = options.confirmColor || Theme.primary - onConfirm = options.onConfirm || function() {} - onCancel = options.onCancel || function() {} + onConfirm = options.onConfirm || (() => {}) + onCancel = options.onCancel || (() => {}) selectedButton = -1 keyboardNavigation = false open() } function selectButton() { + close() if (selectedButton === 0) { - close() - if (onCancel) onCancel() + if (onCancel) { + onCancel() + } } else { - close() - if (onConfirm) onConfirm() + if (onConfirm) { + onConfirm() + } } } @@ -64,18 +62,22 @@ DankModal { shouldHaveFocus: true onBackgroundClicked: { close() - if (onCancel) onCancel() + if (onCancel) { + onCancel() + } } onOpened: { modalFocusScope.forceActiveFocus() modalFocusScope.focus = true shouldHaveFocus = true } - modalFocusScope.Keys.onPressed: function(event) { + modalFocusScope.Keys.onPressed: function (event) { switch (event.key) { case Qt.Key_Escape: close() - if (onCancel) onCancel() + if (onCancel) { + onCancel() + } event.accepted = true break case Qt.Key_Left: @@ -148,12 +150,13 @@ DankModal { height: 40 radius: Theme.cornerRadius color: { - if (keyboardNavigation && selectedButton === 0) + if (keyboardNavigation && selectedButton === 0) { return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) - else if (cancelButton.containsMouse) + } else if (cancelButton.containsMouse) { return Theme.surfacePressed - else + } else { return Theme.surfaceVariantAlpha + } } border.color: (keyboardNavigation && selectedButton === 0) ? Theme.primary : "transparent" border.width: (keyboardNavigation && selectedButton === 0) ? 1 : 0 @@ -177,7 +180,6 @@ DankModal { selectButton() } } - } Rectangle { @@ -185,13 +187,14 @@ DankModal { height: 40 radius: Theme.cornerRadius color: { - let baseColor = confirmButtonColor - if (keyboardNavigation && selectedButton === 1) + const baseColor = confirmButtonColor + if (keyboardNavigation && selectedButton === 1) { return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1) - else if (confirmButton.containsMouse) + } else if (confirmButton.containsMouse) { return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) - else + } else { return baseColor + } } border.color: (keyboardNavigation && selectedButton === 1) ? "white" : "transparent" border.width: (keyboardNavigation && selectedButton === 1) ? 1 : 0 @@ -215,15 +218,9 @@ DankModal { selectButton() } } - } - } - } - } - } - } diff --git a/Modals/DankModal.qml b/Modals/DankModal.qml index d2c87e26..a7c3c791 100644 --- a/Modals/DankModal.qml +++ b/Modals/DankModal.qml @@ -27,7 +27,6 @@ PanelWindow { property real borderWidth: 1 property real cornerRadius: Theme.cornerRadius property bool enableShadow: false - // Expose the focusScope for external access property alias modalFocusScope: focusScope property bool shouldBeVisible: false property bool shouldHaveFocus: shouldBeVisible @@ -38,15 +37,6 @@ PanelWindow { signal dialogClosed signal backgroundClicked - Connections { - target: ModalManager - function onCloseAllModalsExcept(excludedModal) { - if (excludedModal !== root && !allowStacking && shouldBeVisible) { - close() - } - } - } - function open() { ModalManager.openModal(root) closeTimer.stop() @@ -61,10 +51,11 @@ PanelWindow { } function toggle() { - if (shouldBeVisible) + if (shouldBeVisible) { close() - else + } else { open() + } } visible: shouldBeVisible @@ -84,6 +75,16 @@ PanelWindow { } } + Connections { + function onCloseAllModalsExcept(excludedModal) { + if (excludedModal !== root && !allowStacking && shouldBeVisible) { + close() + } + } + + target: ModalManager + } + Timer { id: closeTimer @@ -112,13 +113,10 @@ PanelWindow { anchors.fill: parent enabled: root.closeOnBackgroundClick onClicked: mouse => { - var localPos = mapToItem(contentContainer, - mouse.x, mouse.y) - if (localPos.x < 0 - || localPos.x > contentContainer.width - || localPos.y < 0 - || localPos.y > contentContainer.height) - root.backgroundClicked() + const localPos = mapToItem(contentContainer, mouse.x, mouse.y) + if (localPos.x < 0 || localPos.x > contentContainer.width || localPos.y < 0 || localPos.y > contentContainer.height) { + root.backgroundClicked() + } } } @@ -137,19 +135,20 @@ PanelWindow { height: root.height anchors.centerIn: positioning === "center" ? parent : undefined x: { - if (positioning === "top-right") - return Math.max(Theme.spacingL, - root.screenWidth - width - Theme.spacingL) - else if (positioning === "custom") + if (positioning === "top-right") { + return Math.max(Theme.spacingL, root.screenWidth - width - Theme.spacingL) + } else if (positioning === "custom") { return root.customPosition.x - return 0 // Will be overridden by anchors.centerIn when positioning === "center" + } + return 0 } y: { - if (positioning === "top-right") + if (positioning === "top-right") { return Theme.barHeight + Theme.spacingXS - else if (positioning === "custom") + } else if (positioning === "custom") { return root.customPosition.y - return 0 // Will be overridden by anchors.centerIn when positioning === "center" + } + return 0 } color: root.backgroundColor radius: root.cornerRadius @@ -157,12 +156,7 @@ PanelWindow { border.width: root.borderWidth layer.enabled: root.enableShadow opacity: root.shouldBeVisible ? 1 : 0 - scale: { - if (root.animationType === "scale") - return root.shouldBeVisible ? 1 : 0.9 - - return 1 - } + scale: root.animationType === "scale" ? (root.shouldBeVisible ? 1 : 0.9) : 1 transform: root.animationType === "slide" ? slideTransform : null Translate { @@ -214,28 +208,25 @@ PanelWindow { visible: root.visible // Only active when the modal is visible focus: root.visible Keys.onEscapePressed: event => { - if (root.closeOnEscapeKey - && shouldHaveFocus) { + if (root.closeOnEscapeKey && shouldHaveFocus) { root.close() event.accepted = true } } onVisibleChanged: { - if (visible && shouldHaveFocus) - Qt.callLater(function () { - focusScope.forceActiveFocus() - }) + if (visible && shouldHaveFocus) { + Qt.callLater(() => focusScope.forceActiveFocus()) + } } Connections { - target: root function onShouldHaveFocusChanged() { if (shouldHaveFocus && visible) { - Qt.callLater(function () { - focusScope.forceActiveFocus() - }) + Qt.callLater(() => focusScope.forceActiveFocus()) } } + + target: root } } } diff --git a/Modals/FileBrowserModal.qml b/Modals/FileBrowser/FileBrowserModal.qml similarity index 66% rename from Modals/FileBrowserModal.qml rename to Modals/FileBrowser/FileBrowserModal.qml index c85de582..8ed5c0ce 100644 --- a/Modals/FileBrowserModal.qml +++ b/Modals/FileBrowser/FileBrowserModal.qml @@ -1,20 +1,16 @@ +import Qt.labs.folderlistmodel +import QtCore import QtQuick import QtQuick.Controls -import QtCore -import Qt.labs.folderlistmodel import Quickshell.Io import qs.Common +import qs.Modals import qs.Widgets DankModal { id: fileBrowserModal - objectName: "fileBrowserModal" - allowStacking: true - signal fileSelected(string path) - - property string homeDir: StandardPaths.writableLocation( - StandardPaths.HomeLocation) + property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation) property string currentPath: "" property var fileExtensions: ["*.*"] property alias filterExtensions: fileBrowserModal.fileExtensions @@ -25,39 +21,29 @@ DankModal { property int selectedIndex: -1 property bool keyboardNavigationActive: false property bool backButtonFocused: false - property bool saveMode: false // Enable save functionality - property string defaultFileName: "" // Default filename for save mode + property bool saveMode: false // Enable save functionality + property string defaultFileName: "" // Default filename for save mode + 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 - FolderListModel { - id: folderModel - showDirsFirst: true - showDotAndDotDot: false - showHidden: fileBrowserModal.showHiddenFiles - nameFilters: fileExtensions - showFiles: true - showDirs: true - folder: currentPath ? "file://" + currentPath : "file://" + homeDir - } + signal fileSelected(string path) function isImageFile(fileName) { - if (!fileName) + if (!fileName) { return false - var ext = fileName.toLowerCase().split('.').pop() + } + const ext = fileName.toLowerCase().split('.').pop() return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext) } function getLastPath() { - var lastPath = "" - if (browserType === "wallpaper") { - lastPath = SessionData.wallpaperLastPath - } else if (browserType === "profile") { - lastPath = SessionData.profileLastPath - } - - if (lastPath && lastPath !== "") { - return lastPath - } - return homeDir + const lastPath = browserType === "wallpaper" ? SessionData.wallpaperLastPath : browserType === "profile" ? SessionData.profileLastPath : "" + return (lastPath && lastPath !== "") ? lastPath : homeDir } function saveLastPath(path) { @@ -68,86 +54,122 @@ DankModal { } } + function setSelectedFileData(path, name, isDir) { + selectedFilePath = path + selectedFileName = name + selectedFileIsDir = isDir + } + + function navigateUp() { + const path = currentPath + if (path === homeDir) + return + + const lastSlash = path.lastIndexOf('/') + if (lastSlash > 0) { + const newPath = path.substring(0, lastSlash) + if (newPath.length < homeDir.length) { + currentPath = homeDir + saveLastPath(homeDir) + } else { + currentPath = newPath + saveLastPath(newPath) + } + } + } + + function navigateTo(path) { + currentPath = path + saveLastPath(path) + selectedIndex = -1 + backButtonFocused = false + } + + function keyboardFileSelection(index) { + if (index >= 0) { + keyboardSelectionTimer.targetIndex = index + keyboardSelectionTimer.start() + } + } + + function executeKeyboardSelection(index) { + keyboardSelectionIndex = index + keyboardSelectionRequested = true + } + + objectName: "fileBrowserModal" + allowStacking: true Component.onCompleted: { currentPath = getLastPath() } - width: 800 height: 600 enableShadow: true visible: false - onBackgroundClicked: close() - onOpened: { modalFocusScope.forceActiveFocus() } - - modalFocusScope.Keys.onPressed: function(event) { + modalFocusScope.Keys.onPressed: function (event) { keyboardController.handleKey(event) } - onVisibleChanged: { if (visible) { - var startPath = getLastPath() - currentPath = startPath + currentPath = getLastPath() 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 + + FolderListModel { + id: folderModel + + showDirsFirst: true + showDotAndDotDot: false + showHidden: fileBrowserModal.showHiddenFiles + nameFilters: fileExtensions + showFiles: true + showDirs: true + folder: currentPath ? "file://" + currentPath : "file://" + homeDir } - + QtObject { id: keyboardController - + property int totalItems: folderModel.count - property int gridColumns: 5 // Fixed number of columns for the grid (matches actual display) - + property int gridColumns: 5 + 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) { @@ -156,7 +178,6 @@ DankModal { } return } - if (!keyboardNavigationActive) { if (event.key === Qt.Key_Tab || event.key === Qt.Key_Down || event.key === Qt.Key_Right) { keyboardNavigationActive = true @@ -171,207 +192,124 @@ DankModal { } 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 + 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-- + } else if (currentPath !== homeDir) { + backButtonFocused = true + selectedIndex = -1 + } + event.accepted = true + break + case Qt.Key_Right: + if (backButtonFocused) { + backButtonFocused = false + selectedIndex = 0 + } else if (selectedIndex < totalItems - 1) { + 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) + } else if (selectedIndex >= gridColumns) { + // Move up one row + selectedIndex -= gridColumns + } 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 + } else { + // Move down one row if possible + var newIndex = selectedIndex + gridColumns + if (newIndex < totalItems) { + selectedIndex = newIndex } 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 + // 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 - 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) - } + var targetIndex = lastRowStart + col + if (targetIndex < totalItems && targetIndex > selectedIndex) { + selectedIndex = targetIndex } } - 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() { - var path = currentPath - - if (path === homeDir) { - return - } - - var lastSlash = path.lastIndexOf('/') - if (lastSlash > 0) { - var newPath = path.substring(0, lastSlash) - if (newPath.length < homeDir.length) { - currentPath = homeDir - saveLastPath(homeDir) - } else { - currentPath = newPath - saveLastPath(newPath) + } + 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 } } } - 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 - + + interval: 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 { Item { anchors.fill: parent - + Column { anchors.fill: parent anchors.margins: Theme.spacingM @@ -405,7 +343,7 @@ DankModal { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: Theme.spacingS - + DankActionButton { circular: false iconName: "help" @@ -414,7 +352,7 @@ DankModal { hoverColor: Theme.surfacePressed onClicked: fileBrowserModal.showKeyboardHints = !fileBrowserModal.showKeyboardHints } - + DankActionButton { circular: false iconName: "close" @@ -434,9 +372,8 @@ DankModal { width: 32 height: 32 radius: Theme.cornerRadius - color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive)) - && currentPath !== homeDir ? Theme.surfaceVariant : "transparent" - opacity: currentPath !== homeDir ? 1.0 : 0.0 + color: (backButtonMouseArea.containsMouse || (backButtonFocused && keyboardNavigationActive)) && currentPath !== homeDir ? Theme.surfaceVariant : "transparent" + opacity: currentPath !== homeDir ? 1 : 0 DankIcon { anchors.centerIn: parent @@ -447,10 +384,10 @@ DankModal { MouseArea { id: backButtonMouseArea + anchors.fill: parent hoverEnabled: currentPath !== homeDir - cursorShape: currentPath - !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor + cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor enabled: currentPath !== homeDir onClicked: navigateUp() } @@ -478,19 +415,17 @@ DankModal { cellWidth: 150 cellHeight: 130 cacheBuffer: 260 - model: folderModel currentIndex: selectedIndex - onCurrentIndexChanged: { - if (keyboardNavigationActive && currentIndex >= 0) { + if (keyboardNavigationActive && currentIndex >= 0) positionViewAtIndex(currentIndex, GridView.Contain) - } } ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff } @@ -508,29 +443,27 @@ DankModal { height: 120 radius: Theme.cornerRadius color: { - if (keyboardNavigationActive && delegateRoot.index === selectedIndex) { - return Theme.surfacePressed - } - return mouseArea.containsMouse ? Theme.surfaceVariant : "transparent" - } + 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) { + 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) { + if (keyboardNavigationActive && selectedIndex === delegateRoot.index) setSelectedFileData(delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir) - } } + + target: fileBrowserModal } Column { @@ -555,8 +488,7 @@ DankModal { name: "description" size: Theme.iconSizeLarge color: Theme.primary - visible: !delegateRoot.fileIsDir - && !isImageFile(delegateRoot.fileName) + visible: !delegateRoot.fileIsDir && !isImageFile(delegateRoot.fileName) } DankIcon { @@ -583,35 +515,30 @@ DankModal { 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 + 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 { @@ -620,89 +547,91 @@ DankModal { } } } + + target: fileBrowserModal } - } } } - - // Save functionality - positioned at bottom in save mode + Row { id: saveRow + anchors.bottom: parent.bottom - anchors.left: parent.left + anchors.left: parent.left anchors.right: parent.right anchors.margins: Theme.spacingL height: saveMode ? 40 : 0 visible: saveMode spacing: Theme.spacingM - + DankTextField { id: fileNameInput + width: parent.width - saveButton.width - Theme.spacingM height: 36 text: defaultFileName placeholderText: "Enter filename..." - ignoreLeftRightKeys: false // Allow arrow key navigation - focus: saveMode // Auto-focus when in save mode - + ignoreLeftRightKeys: false + focus: saveMode Component.onCompleted: { - if (saveMode) { + if (saveMode) Qt.callLater(() => { - forceActiveFocus(); - }); - } + forceActiveFocus() + }) } - onAccepted: { if (text.trim() !== "") { - var fullPath = currentPath + "/" + text.trim(); - fileSelected(fullPath); - fileBrowserModal.close(); + var fullPath = currentPath + "/" + text.trim() + fileSelected(fullPath) + fileBrowserModal.close() } } } - + StyledRect { id: saveButton + width: 80 height: 36 color: fileNameInput.text.trim() !== "" ? Theme.primary : Theme.surfaceVariant radius: Theme.cornerRadius - + StyledText { anchors.centerIn: parent text: "Save" color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText font.pixelSize: Theme.fontSizeMedium } - + StateLayer { stateColor: Theme.primary cornerRadius: Theme.cornerRadius enabled: fileNameInput.text.trim() !== "" onClicked: { if (fileNameInput.text.trim() !== "") { - var fullPath = currentPath + "/" + fileNameInput.text.trim(); - fileSelected(fullPath); - fileBrowserModal.close(); + var fullPath = currentPath + "/" + fileNameInput.text.trim() + fileSelected(fullPath) + fileBrowserModal.close() } } } } } - - FileBrowserKeyboardHints { + + KeyboardHints { id: keyboardHints + anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.margins: Theme.spacingL showHints: fileBrowserModal.showKeyboardHints } - - FileBrowserFileInfo { + + FileInfo { id: fileInfo + anchors.top: parent.top anchors.right: parent.right anchors.margins: Theme.spacingL @@ -711,16 +640,15 @@ DankModal { 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 "" + if (fileBrowserModal.selectedFileIsDir || !fileBrowserModal.selectedFileName) + return "" + var lastDot = fileBrowserModal.selectedFileName.lastIndexOf('.') return lastDot > 0 ? fileBrowserModal.selectedFileName.substring(lastDot + 1).toLowerCase() : "" } - } } } diff --git a/Modals/FileBrowserFileInfo.qml b/Modals/FileBrowser/FileInfo.qml similarity index 65% rename from Modals/FileBrowserFileInfo.qml rename to Modals/FileBrowser/FileInfo.qml index 55389c48..bcf7a6b9 100644 --- a/Modals/FileBrowserFileInfo.qml +++ b/Modals/FileBrowser/FileInfo.qml @@ -1,7 +1,5 @@ import QtQuick -import QtQuick.Controls import QtCore -import Qt.labs.folderlistmodel import Quickshell.Io import qs.Common import qs.Widgets @@ -16,8 +14,7 @@ Rectangle { height: 200 radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, - Theme.surfaceContainer.b, 0.95) + 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 @@ -25,7 +22,7 @@ Rectangle { onShowFileInfoChanged: { if (showFileInfo && currentFileName && currentPath) { - var fullPath = currentPath + "/" + currentFileName + const fullPath = currentPath + "/" + currentFileName fileStatProcess.selectedFilePath = fullPath fileStatProcess.running = true } @@ -37,64 +34,24 @@ Rectangle { property string selectedFilePath: "" property var fileStats: null running: false - + stdout: StdioCollector { onStreamFinished: { if (text && text.trim()) { - var parts = text.trim().split('|') + const parts = text.trim().split('|') if (parts.length >= 4) { fileStatProcess.fileStats = { - modifiedTime: parts[0], - permissions: parts[1], - size: parseInt(parts[2]) || 0, - fullPath: parts[3] + "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" - } + onExited: function (exitCode) {} } property string currentFileName: "" @@ -103,7 +60,7 @@ Rectangle { onCurrentFileNameChanged: { if (showFileInfo && currentFileName && currentPath) { - var fullPath = currentPath + "/" + currentFileName + const fullPath = currentPath + "/" + currentFileName if (fullPath !== fileStatProcess.selectedFilePath) { fileStatProcess.selectedFilePath = fullPath fileStatProcess.running = true @@ -116,16 +73,16 @@ Rectangle { fileStatProcess.selectedFilePath = filePath currentFileName = fileName || "" currentFileIsDir = isDirectory || false - - var ext = "" + + let ext = "" if (!isDirectory && fileName) { - var lastDot = fileName.lastIndexOf('.') + const lastDot = fileName.lastIndexOf('.') if (lastDot > 0) { ext = fileName.substring(lastDot + 1).toLowerCase() } } currentFileExtension = ext - + if (showFileInfo) { fileStatProcess.running = true } @@ -135,27 +92,27 @@ Rectangle { readonly property var currentFileDisplayData: { if (selectedIndex < 0 || !sourceFolderModel) { return { - exists: false, - name: "No selection", - type: "", - size: "", - modified: "", - permissions: "", - extension: "", - position: "N/A" + "exists": false, + "name": "No selection", + "type": "", + "size": "", + "modified": "", + "permissions": "", + "extension": "", + "position": "N/A" } } - - var hasValidFile = currentFileName !== "" + + const 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" + "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" } } @@ -251,16 +208,20 @@ Rectangle { } 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)) + if (bytes === 0 || !bytes) { + return "0 B" + } + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const 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 (!dateTimeString) { + return "Unknown" + } + const parts = dateTimeString.split(' ') if (parts.length >= 2) { return parts[0] + " " + parts[1].split('.')[0] } @@ -273,4 +234,4 @@ Rectangle { easing.type: Theme.standardEasing } } -} \ No newline at end of file +} diff --git a/Modals/FileBrowserKeyboardHints.qml b/Modals/FileBrowser/KeyboardHints.qml similarity index 95% rename from Modals/FileBrowserKeyboardHints.qml rename to Modals/FileBrowser/KeyboardHints.qml index b8b9d68f..2a0a1027 100644 --- a/Modals/FileBrowserKeyboardHints.qml +++ b/Modals/FileBrowser/KeyboardHints.qml @@ -9,8 +9,7 @@ Rectangle { height: 80 radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, - Theme.surfaceContainer.b, 0.95) + 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 @@ -48,4 +47,4 @@ Rectangle { easing.type: Theme.standardEasing } } -} \ No newline at end of file +} diff --git a/Modals/NotepadModal.qml b/Modals/NotepadModal.qml index 9307da24..6514d0e1 100644 --- a/Modals/NotepadModal.qml +++ b/Modals/NotepadModal.qml @@ -6,6 +6,7 @@ import Quickshell.Io import qs.Common import qs.Services import qs.Widgets +import qs.Modals.FileBrowser pragma ComponentBehavior: Bound diff --git a/Modals/SettingsModal.qml b/Modals/SettingsModal.qml index bc1326c7..1dcc5d36 100644 --- a/Modals/SettingsModal.qml +++ b/Modals/SettingsModal.qml @@ -4,6 +4,7 @@ import QtQuick.Effects import Quickshell import Quickshell.Io import qs.Common +import qs.Modals.FileBrowser import qs.Modules.Settings import qs.Services import qs.Widgets diff --git a/Modules/Settings/PersonalizationTab.qml b/Modules/Settings/PersonalizationTab.qml index b2acd23f..4b615ae1 100644 --- a/Modules/Settings/PersonalizationTab.qml +++ b/Modules/Settings/PersonalizationTab.qml @@ -4,6 +4,7 @@ import QtQuick.Effects import Quickshell import qs.Common import qs.Modals +import qs.Modals.FileBrowser import qs.Services import qs.Widgets diff --git a/Modules/Settings/ThemeColorsTab.qml b/Modules/Settings/ThemeColorsTab.qml index 72707d19..f64175d0 100644 --- a/Modules/Settings/ThemeColorsTab.qml +++ b/Modules/Settings/ThemeColorsTab.qml @@ -4,6 +4,7 @@ import Quickshell import Quickshell.Io import qs.Common import qs.Modals +import qs.Modals.FileBrowser import qs.Services import qs.Widgets diff --git a/Widgets/DankIcon.qml b/Widgets/DankIcon.qml index 14d285d9..562027cb 100644 --- a/Widgets/DankIcon.qml +++ b/Widgets/DankIcon.qml @@ -13,7 +13,7 @@ StyledText { property int weight: filled ? 500 : 400 font.family: "Material Symbols Rounded" - font.pixelSize: Appearance.fontSize.normal + font.pixelSize: Theme.fontSizeMedium font.weight: weight color: Theme.surfaceText verticalAlignment: Text.AlignVCenter @@ -27,17 +27,15 @@ StyledText { Behavior on fill { NumberAnimation { - duration: Appearance.anim.durations.quick - easing.type: Easing.BezierSpline - easing.bezierCurve: Appearance.anim.curves.standard + duration: Theme.shortDuration + easing.type: Theme.standardEasing } } Behavior on weight { NumberAnimation { - duration: Appearance.anim.durations.quick - easing.type: Easing.BezierSpline - easing.bezierCurve: Appearance.anim.curves.standard + duration: Theme.shortDuration + easing.type: Theme.standardEasing } } } diff --git a/shell.qml b/shell.qml index fa135b8a..ca26872e 100644 --- a/shell.qml +++ b/shell.qml @@ -5,6 +5,7 @@ import Quickshell.Io import Quickshell.Widgets import qs.Common import qs.Modals +import qs.Modals.Clipboard import qs.Modules import qs.Modules.AppDrawer import qs.Modules.CentcomCenter