Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
edd94f9
Draw the selected name in the bright text color
thisisgm Nov 11, 2026
2364c92
Scroll the cursor into view when keys move it
thisisgm Nov 11, 2026
cc41cb1
Frame the cursor's item when nothing is selected
thisisgm Nov 11, 2026
c95f185
Draw the other pane's selection without the accent
thisisgm Nov 11, 2026
b3c8818
Wrap the details pane between words
thisisgm Nov 11, 2026
7084409
Keep the folder in view in the notice line
thisisgm Nov 11, 2026
a824473
Say how many are selected in the details pane
thisisgm Nov 11, 2026
2472b0a
Name a search result by its file, with its folder beside it
thisisgm Nov 11, 2026
760853f
Dim the whole window behind a dialog
thisisgm Nov 11, 2026
47d053a
Show the shortcut sheet's scroll bar at rest
thisisgm Nov 11, 2026
aca122f
Copy instead of move when Ctrl is held on a drop
thisisgm Nov 11, 2026
54e0bba
Draw the shortcut sheet's resting scroll bar so it reads
thisisgm Nov 11, 2026
5c3a7a6
Keep menus off the window edge and their open parent grey
thisisgm Nov 11, 2026
c53e1ca
Put a plain space before AM and PM in dates
thisisgm Nov 11, 2026
b845199
Show no one file's picture when several are selected
thisisgm Nov 11, 2026
4af40ec
Leave the date off a search result tile led by its folder
thisisgm Nov 11, 2026
3761edf
Say whether a drop will move or copy while it hovers
thisisgm Nov 11, 2026
0ce1d6c
Make the pane a right click lands in the active one
thisisgm Nov 11, 2026
4089e5b
Line up "Nothing selected" with the empty folder's caption
thisisgm Nov 11, 2026
8ad8fc7
Drop the selection's accent while the keys are elsewhere
thisisgm Nov 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/FileDrop.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import QtQuick

// A folder files can be dropped on. While they hover it knows whether letting go moves or copies them.
DropArea {
id: area
property string folder
property bool copies: false
// Whether the hovering files all come from other drives. It asks the drives, so it is read once a drag, never on the timer.
property bool otherDrive: false
// The drag loop takes the key events, so Ctrl pressed or let go with the pointer still is seen only by asking again.
readonly property int ctrlCheckMs: 100

keys: ["text/uri-list"]
function recheck() { copies = otherDrive || fileOps.ctrlHeld() }
onEntered: (drag) => { otherDrive = fileOps.dropCopies(drag.urls, folder, false); recheck() }
onPositionChanged: recheck()

Timer { interval: area.ctrlCheckMs; repeat: true; running: area.containsDrag; onTriggered: area.recheck() }
}
48 changes: 35 additions & 13 deletions src/FileRow.qml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ Item {
required property int itemCount
required property string icon
required property bool selected
required property string fileName
required property string folder

property bool current: false
// In a pane the keys don't go to: the selection is drawn without the accent.
property bool inactive: false
// In a column to the left: the folder the next column is showing.
property bool trail: false
property bool detailed: false
Expand All @@ -37,6 +41,8 @@ Item {
// for it.)
readonly property bool showDate: detailed && width >= 480
readonly property bool showSize: detailed && width >= 380
// With nothing selected, keys act on the cursor's item; only the folder being shown has one.
readonly property bool cursor: ListView.isCurrentItem && ListView.view.model === backend.folder && backend.folder.selectedCount === 0

readonly property string glyph: row.isDir ? "folder"
: row.kind === "item" ? row.icon
Expand All @@ -47,24 +53,24 @@ Item {
: "file"

// A folder row takes files dropped on it, lighting up while they hover.
DropArea {
FileDrop {
id: folderDrop
anchors.fill: parent
enabled: row.isDir
keys: ["text/uri-list"]
folder: row.path
onEntered: (drag) => { drag.accepted = fileOps.canDrop(drag.urls, row.path) }
onDropped: (drop) => { fileOps.dropInto(drop.urls, row.path); drop.accept(Qt.MoveAction) }
onDropped: (drop) => drop.accept(fileOps.dropInto(drop.urls, row.path, fileOps.ctrlHeld()))
}

Rectangle {
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
radius: Math.min(4, theme.radius)
border.width: folderDrop.containsDrag ? 2 : 0
border.color: theme.accent
color: row.current || folderDrop.containsDrag ? Qt.alpha(theme.accent, 0.18)
: row.trail ? Qt.alpha(theme.foreground, 0.08)
border.width: folderDrop.containsDrag ? 2 : row.cursor ? 1 : 0
border.color: row.inactive && !folderDrop.containsDrag ? Qt.alpha(theme.foreground, 0.4) : theme.accent
color: row.current && !row.inactive || folderDrop.containsDrag ? Qt.alpha(theme.accent, 0.18)
: row.trail || row.current ? Qt.alpha(theme.foreground, 0.08)
: hover.hovered ? Qt.alpha(theme.foreground, 0.05)
: "transparent"
}
Expand Down Expand Up @@ -116,31 +122,47 @@ Item {
Layout.fillWidth: true
Layout.minimumWidth: 120
horizontalAlignment: Text.AlignLeft
text: row.name
color: row.current ? theme.accent : theme.foreground
text: row.fileName
color: row.current ? theme.brightForeground : theme.foreground
elide: Text.ElideMiddle
}
Text {
visible: row.showSize
visible: row.folder.length > 0
Layout.maximumWidth: Math.round(row.width / 3)
horizontalAlignment: Text.AlignLeft
text: row.folder
color: theme.secondaryForeground
font.pixelSize: 12
elide: Text.ElideMiddle
}
Text {
visible: row.showSize && !folderDrop.containsDrag
Layout.preferredWidth: 96
horizontalAlignment: Text.AlignRight
text: row.kind === "item" ? row.extension : row.isDir ? Format.count(row.itemCount) : Format.size(row.size)
color: theme.secondaryForeground
color: row.current ? theme.foreground : theme.secondaryForeground
font.pixelSize: 12
}
Text {
visible: row.showDate
visible: row.showDate && !folderDrop.containsDrag
Layout.preferredWidth: 96
Layout.leftMargin: 12
horizontalAlignment: Text.AlignLeft
text: {
translator.language // Re-read when the language (and so the default locale) changes.
return Format.when(row.modified)
}
color: theme.secondaryForeground
color: row.current ? theme.foreground : theme.secondaryForeground
font.pixelSize: 12
elide: Text.ElideRight
}
// While files hover a folder, what the drop will do takes the size and date's place.
Text {
visible: folderDrop.containsDrag
text: folderDrop.copies ? qsTr("Copy here") : qsTr("Move here")
color: theme.secondaryForeground
font.pixelSize: 12
}
// Points to where the next column opens.
Glyph {
visible: row.chevron && row.isDir
Expand Down
44 changes: 28 additions & 16 deletions src/FileTile.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ Item {
required property var size
required property var modified
required property bool selected
required property string fileName
required property string folder

property bool current: false
// In a pane the keys don't go to: the selection is drawn without the accent.
property bool inactive: false

signal clicked(int modifiers)
signal activated()

// Pictures and drawings round their corners with the theme: square on
// Omarchy's default, softly rounded on themes that round windows.
readonly property real iconRadius: Math.min(10, theme.radius)
// With nothing selected, keys act on the cursor's item.
readonly property bool cursor: GridView.isCurrentItem && backend.folder.selectedCount === 0

// The badge and stand-in color for each kind, from the theme's palette.
readonly property color kindColor: kind === "pdf" ? theme.red
Expand Down Expand Up @@ -73,8 +79,8 @@ Item {
anchors.topMargin: 12
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 28
text: tile.name
color: tile.current ? theme.accent : theme.foreground
text: tile.fileName
color: tile.current ? theme.brightForeground : theme.foreground
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
Expand All @@ -95,13 +101,17 @@ Item {
translator.language // Re-read when the language (and so the default locale) changes.
if (tile.kind === "item")
return tile.extension
if (folderDrop.containsDrag)
return folderDrop.copies ? qsTr("Copy here") : qsTr("Move here")
const size = tile.isDir ? Format.count(tile.itemCount) : Format.size(tile.size)
// A folder's own date says little (it changes when anything in
// it is added or removed), so only files show one.
const date = tile.isDir ? "" : Format.when(tile.modified)
return size && date ? qsTr("%1 · %2").arg(size).arg(date) : size || date
const date = tile.isDir || tile.folder ? "" : Format.when(tile.modified) // A search result's folder takes the date's room.
const rest = size && date ? qsTr("%1 · %2").arg(size).arg(date) : size || date
// Both in one arg() call, so a "%2" in a folder's name stays as written.
return tile.folder && rest ? qsTr("%1 · %2").arg(tile.folder, rest) : tile.folder || rest
}
color: theme.secondaryForeground
color: tile.current ? theme.foreground : theme.secondaryForeground
font.pixelSize: 10
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
Expand All @@ -110,13 +120,13 @@ Item {
// A folder takes files dropped anywhere on its tile, picture or name,
// lighting up while they hover: the view's own drop area refuses the
// whole cell, so the tile has to take all of it.
DropArea {
FileDrop {
id: folderDrop
anchors.fill: parent
enabled: tile.isDir
keys: ["text/uri-list"]
folder: tile.path
onEntered: (drag) => { drag.accepted = fileOps.canDrop(drag.urls, tile.path) }
onDropped: (drop) => { fileOps.dropInto(drop.urls, tile.path); drop.accept(Qt.MoveAction) }
onDropped: (drop) => drop.accept(fileOps.dropInto(drop.urls, tile.path, fileOps.ctrlHeld()))
}

HoverHandler {
Expand Down Expand Up @@ -175,11 +185,13 @@ Item {
height: 92
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: tile.current || folderDrop.containsDrag ? Qt.tint(theme.lighterBackground, Qt.alpha(theme.accent, 0.3))
fillColor: tile.current && !tile.inactive || folderDrop.containsDrag ? Qt.tint(theme.lighterBackground, Qt.alpha(theme.accent, 0.3))
: tile.current ? Qt.tint(theme.lighterBackground, Qt.alpha(theme.foreground, 0.08))
: hover.hovered ? Qt.tint(theme.lighterBackground, Qt.alpha(theme.foreground, 0.06))
: theme.lighterBackground
strokeColor: tile.current || folderDrop.containsDrag ? theme.accent : "transparent"
strokeWidth: folderDrop.containsDrag ? 2.5 : 1.5
strokeColor: (tile.current || tile.cursor) && !tile.inactive || folderDrop.containsDrag ? theme.accent
: tile.current || tile.cursor ? Qt.alpha(theme.foreground, 0.4) : "transparent"
strokeWidth: folderDrop.containsDrag ? 2.5 : tile.cursor ? 1 : 1.5
joinStyle: tile.iconRadius > 0 ? ShapePath.RoundJoin : ShapePath.MiterJoin
PathSvg { path: tile.folderPath(tile.iconRadius) }
}
Expand Down Expand Up @@ -257,14 +269,14 @@ Item {
return Qt.rect((w - 66) / 2, 0, 66, h)
}
Rectangle {
visible: tile.current || hover.hovered
visible: tile.current || tile.cursor || hover.hovered
x: parent.shown.x - 3
y: parent.shown.y - 3
width: parent.shown.width + 6
height: parent.shown.height + 6
color: "transparent"
border.width: tile.current ? 2 : 1
border.color: tile.current ? theme.accent : Qt.alpha(theme.foreground, 0.2)
border.color: (tile.current || tile.cursor) && !tile.inactive ? theme.accent : Qt.alpha(theme.foreground, tile.current || tile.cursor ? 0.4 : 0.2)
radius: tile.iconRadius > 0 ? tile.iconRadius + 3 : 0
}
}
Expand All @@ -280,15 +292,15 @@ Item {
name: tile.icon
size: 76
stroke: 1.2
color: tile.current ? theme.accent : theme.foreground
color: tile.current && !tile.inactive ? theme.accent : theme.foreground
}
Rectangle {
visible: tile.current || hover.hovered
visible: tile.current || tile.cursor || hover.hovered
anchors.fill: computer
anchors.margins: -3
color: "transparent"
border.width: tile.current ? 2 : 1
border.color: tile.current ? theme.accent : Qt.alpha(theme.foreground, 0.2)
border.color: (tile.current || tile.cursor) && !tile.inactive ? theme.accent : Qt.alpha(theme.foreground, tile.current || tile.cursor ? 0.4 : 0.2)
radius: tile.iconRadius > 0 ? tile.iconRadius + 3 : 0
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/FolderColumns.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Item {
id: view

property alias currentIndex: here.currentIndex
// In a pane the keys don't go to: the selection is drawn without the accent.
property bool inactive: false
readonly property int columnWidth: 260

function select(row, keep) { selecting.select(row, keep) }
Expand Down Expand Up @@ -173,6 +175,7 @@ Item {
width: here.width
chevron: true
current: selected
inactive: view.inactive
onClicked: (modifiers) => { selecting.click(index, modifiers); here.forceActiveFocus() }
onActivated: backend.open(path)
}
Expand Down
15 changes: 10 additions & 5 deletions src/FolderDrop.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import QtQuick
// where it refuses one (it's the folder being dropped, or the one the drop
// is already in), this refuses too, rather than catching the drop as it
// falls through and landing it in this folder instead.
DropArea {
FileDrop {
id: area

property string folder
// The list or grid whose items lie over this area.
property Item items

Expand All @@ -18,14 +17,12 @@ DropArea {
return !(item && item.isDir) && fileOps.canDrop(drag.urls, folder)
}

keys: ["text/uri-list"]
onEntered: (drag) => { drag.accepted = takes(drag) }
onPositionChanged: (drag) => { drag.accepted = takes(drag) }
onDropped: (drop) => {
if (!takes(drop))
return
fileOps.dropInto(drop.urls, folder)
drop.accept(Qt.MoveAction)
drop.accept(fileOps.dropInto(drop.urls, folder, fileOps.ctrlHeld()))
}

Rectangle {
Expand All @@ -36,5 +33,13 @@ DropArea {
border.width: 2
border.color: theme.accent
radius: Math.min(6, theme.radius)
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
text: area.copies ? qsTr("Copy here") : qsTr("Move here")
color: theme.secondaryForeground
font.pixelSize: 12
}
}
}
4 changes: 4 additions & 0 deletions src/FolderGrid.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import QtQuick.Controls
GridView {
id: grid

// In a pane the keys don't go to: the selection is drawn without the accent.
property bool inactive: false

function select(row, keep) { selecting.select(row, keep) }
// A click on empty space lets go of the cursor; the pane then keeps it
// let go of through a re-read or a switch of view.
Expand Down Expand Up @@ -95,6 +98,7 @@ GridView {
height: grid.cellHeight
zoom: grid.tileZoom
current: selected
inactive: grid.inactive
onClicked: (modifiers) => { selecting.click(index, modifiers); grid.forceActiveFocus() }
onActivated: backend.open(path)
}
Expand Down
4 changes: 4 additions & 0 deletions src/FolderList.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import QtQuick.Controls
ListView {
id: list

// In a pane the keys don't go to: the selection is drawn without the accent.
property bool inactive: false

function select(row, keep) { selecting.select(row, keep) }
// A click on empty space lets go of the cursor; the pane then keeps it
// let go of through a re-read or a switch of view.
Expand Down Expand Up @@ -57,6 +60,7 @@ ListView {
width: list.width
detailed: true
current: selected
inactive: list.inactive
onClicked: (modifiers) => { selecting.click(index, modifiers); list.forceActiveFocus() }
onActivated: backend.open(path)
}
Expand Down
Loading