feat(filemanager): refine sidebar and navigation layout

main
reionwong 3 weeks ago
parent 4020ae4d18
commit 30012a0582

@ -56,6 +56,16 @@ bool PathHistory::isEmpty()
return m_prevHistory.isEmpty(); return m_prevHistory.isEmpty();
} }
bool PathHistory::canGoBack() const
{
return m_prevHistory.length() >= 2;
}
bool PathHistory::canGoForward() const
{
return !m_postHistory.isEmpty();
}
QUrl PathHistory::posteriorPath() QUrl PathHistory::posteriorPath()
{ {
if (m_postHistory.isEmpty()) if (m_postHistory.isEmpty())

@ -40,6 +40,9 @@ public:
bool isEmpty(); bool isEmpty();
bool canGoBack() const;
bool canGoForward() const;
QUrl posteriorPath(); QUrl posteriorPath();
QUrl previousPath(); QUrl previousPath();

@ -458,6 +458,8 @@ void FolderModel::setUrl(const QString &url)
// Refresh this directory. // Refresh this directory.
if (url == m_url) { if (url == m_url) {
refresh(); refresh();
emit canGoBackChanged();
emit canGoForwardChanged();
return; return;
} }
@ -479,6 +481,8 @@ void FolderModel::setUrl(const QString &url)
emit urlChanged(); emit urlChanged();
emit resolvedUrlChanged(); emit resolvedUrlChanged();
emit canGoBackChanged();
emit canGoForwardChanged();
} }
QUrl FolderModel::resolvedUrl() const QUrl FolderModel::resolvedUrl() const
@ -637,6 +641,16 @@ bool FolderModel::dragging() const
return m_dragInProgress; return m_dragInProgress;
} }
bool FolderModel::canGoBack() const
{
return m_pathHistory.canGoBack();
}
bool FolderModel::canGoForward() const
{
return m_pathHistory.canGoForward();
}
bool FolderModel::isDir(const QModelIndex &index, const KDirModel *dirModel) const bool FolderModel::isDir(const QModelIndex &index, const KDirModel *dirModel) const
{ {
KFileItem item = dirModel->itemForIndex(index); KFileItem item = dirModel->itemForIndex(index);

@ -71,6 +71,8 @@ class FolderModel : public QSortFilterProxyModel, public QQmlParserStatus
Q_PROPERTY(int archiveProgress READ archiveProgress NOTIFY archiveProgressChanged) Q_PROPERTY(int archiveProgress READ archiveProgress NOTIFY archiveProgressChanged)
Q_PROPERTY(QString archiveOperation READ archiveOperation NOTIFY archiveOperationChanged) Q_PROPERTY(QString archiveOperation READ archiveOperation NOTIFY archiveOperationChanged)
Q_PROPERTY(QString archiveCurrentFile READ archiveCurrentFile NOTIFY archiveCurrentFileChanged) Q_PROPERTY(QString archiveCurrentFile READ archiveCurrentFile NOTIFY archiveCurrentFileChanged)
Q_PROPERTY(bool canGoBack READ canGoBack NOTIFY canGoBackChanged)
Q_PROPERTY(bool canGoForward READ canGoForward NOTIFY canGoForwardChanged)
public: public:
enum DataRole { enum DataRole {
@ -160,6 +162,9 @@ public:
bool dragging() const; bool dragging() const;
bool canGoBack() const;
bool canGoForward() const;
bool isDir(const QModelIndex &index, const KDirModel *dirModel) const; bool isDir(const QModelIndex &index, const KDirModel *dirModel) const;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
@ -263,6 +268,8 @@ signals:
void filterModeChanged(); void filterModeChanged();
void requestRename(); void requestRename();
void draggingChanged(); void draggingChanged();
void canGoBackChanged();
void canGoForwardChanged();
void viewAdapterChanged(); void viewAdapterChanged();
void isDesktopChanged(); void isDesktopChanged();
void selectionCountChanged(); void selectionCountChanged();

@ -38,8 +38,13 @@ Item {
Rectangle { Rectangle {
id: _background id: _background
anchors.fill: parent anchors.fill: parent
radius: FishUI.Theme.smallRadius // Same inset as FishUI's window buttons, so both draw the same circle.
color: _mouseArea.pressed ? pressedColor : _mouseArea.containsMouse ? control.hoveredColor : control.backgroundColor anchors.margins: Math.round(control.width * 0.1)
radius: width / 2
color: !control.enabled ? "transparent"
: _mouseArea.pressed ? pressedColor
: _mouseArea.containsMouse ? control.hoveredColor
: control.backgroundColor
} }
Image { Image {
@ -50,11 +55,13 @@ Item {
sourceSize: Qt.size(width, height) sourceSize: Qt.size(width, height)
smooth: false smooth: false
antialiasing: true antialiasing: true
opacity: control.enabled ? 1 : 0.35
} }
MouseArea { MouseArea {
id: _mouseArea id: _mouseArea
anchors.fill: parent anchors.fill: parent
enabled: control.enabled
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onClicked: control.clicked() onClicked: control.clicked()

@ -33,8 +33,11 @@ Item {
property alias currentUrl: dirModel.url property alias currentUrl: dirModel.url
property alias model: dirModel property alias model: dirModel
property alias canGoBack: dirModel.canGoBack
property alias canGoForward: dirModel.canGoForward
property Item currentView: _viewLoader.item property Item currentView: _viewLoader.item
property int statusBarHeight: 22 property int headerHeight: 0
property int bottomNavigationHeight: 0
signal requestPathEditor() signal requestPathEditor()
@ -200,7 +203,9 @@ Item {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.bottomMargin: 2 // Same breathing room the sidebar leaves between its title and its list.
anchors.topMargin: folderPage.headerHeight
anchors.bottomMargin: 2 + folderPage.bottomNavigationHeight
spacing: 0 spacing: 0
Loader { Loader {
@ -221,82 +226,6 @@ Item {
shortCut.install(_viewLoader.item) shortCut.install(_viewLoader.item)
} }
} }
Item {
visible: true
height: statusBarHeight
}
}
Item {
id: _statusBar
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: statusBarHeight
z: 999
// Rectangle {
// anchors.fill: parent
// color: FishUI.Theme.backgroundColor
// opacity: 0.7
// }
MouseArea {
anchors.fill: parent
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: FishUI.Units.smallSpacing
anchors.rightMargin: FishUI.Units.smallSpacing
// anchors.bottomMargin: 1
spacing: FishUI.Units.largeSpacing
Label {
Layout.alignment: Qt.AlignLeft
font.pointSize: 10
text: dirModel.count === 1 ? qsTr("%1 item").arg(dirModel.count)
: qsTr("%1 items").arg(dirModel.count)
}
Label {
Layout.alignment: Qt.AlignLeft
font.pointSize: 10
text: qsTr("%1 selected").arg(dirModel.selectionCount)
visible: dirModel.selectionCount >= 1
}
FishUI.BusyIndicator {
id: _busyIndicator
Layout.alignment: Qt.AlignLeft
height: statusBarHeight
width: height
running: visible
visible: dirModel.status === FM.FolderModel.Listing
}
Label {
text: dirModel.selectedItemSize
visible: dirModel.url !== "trash:///"
}
Item {
Layout.fillWidth: true
}
Button {
id: _emptyTrashBtn
implicitHeight: statusBarHeight
text: qsTr("Empty Trash")
font.pointSize: 10
onClicked: dirModel.emptyTrash()
visible: dirModel.url === "trash:///"
&& _viewLoader.item
&& _viewLoader.item.count > 0
focusPolicy: Qt.NoFocus
}
}
} }
function rename() { function rename() {

@ -29,11 +29,30 @@ Item {
id: control id: control
property string url: "" property string url: ""
property string currentName: nameForUrl(url)
signal itemClicked(string path) signal itemClicked(string path)
signal editorAccepted(string path) signal editorAccepted(string path)
// The path bar is part of the title bar. Start the Wayland move only function nameForUrl(value) {
if (!value)
return ""
if (value.indexOf("trash:/") === 0)
return qsTr("Trash")
var path = value.toString()
if (path.indexOf("file://") === 0)
path = path.substring(7)
while (path.length > 1 && path.endsWith("/"))
path = path.substring(0, path.length - 1)
var separator = path.lastIndexOf("/")
return separator >= 0 ? (path.substring(separator + 1) || "/") : path
}
// The path bar is part of the navigation bar. Start the Wayland move only
// after a drag begins, so a single click can open the path editor. // after a drag begins, so a single click can open the path editor.
property bool _dragged: false property bool _dragged: false
property real _pressX: 0 property real _pressX: 0

@ -25,23 +25,20 @@ import QtQuick.Window 2.12
import FishUI 1.0 as FishUI import FishUI 1.0 as FishUI
import Cutefish.FileManager 1.0 import Cutefish.FileManager 1.0
ListView { Item {
id: sideBar id: sideBar
signal clicked(string path) implicitWidth: 190
signal openInNewWindow(string path)
// Rounding to whole *logical* pixels is not enough at a fractional scale: property string title
// at 150% a 37px row pitch is 55.5 device px, so every other row lands on // The header aligns its own contents to this label's text line.
// a half pixel and its icon is resampled. property alias titleItem: titleLabel
readonly property real dpr: FishUI.Dpi.ratio property alias currentIndex: listView.currentIndex
function snap(v) { property alias count: listView.count
return Math.round(v * sideBar.dpr) / sideBar.dpr property alias model: listView.model
}
FishUI.WheelHandler { signal clicked(string path)
target: sideBar signal openInNewWindow(string path)
}
Fm { Fm {
id: _fm id: _fm
@ -52,16 +49,63 @@ ListView {
onDeviceSetupDone: sideBar.clicked(filePath) // onDeviceSetupDone: sideBar.clicked(filePath) //
} }
model: placesModel Rectangle {
anchors.fill: parent
color: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 1.5)
: Qt.darker(FishUI.Theme.backgroundColor, 1.05)
opacity: 0.7
Behavior on color {
ColorAnimation {
duration: 250
easing.type: Easing.Linear
}
}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Label {
id: titleLabel
text: sideBar.title
color: root.active ? FishUI.Theme.textColor : FishUI.Theme.disabledTextColor
Layout.preferredHeight: root.header.height
leftPadding: FishUI.Units.largeSpacing + FishUI.Units.smallSpacing
rightPadding: FishUI.Units.largeSpacing + FishUI.Units.smallSpacing
topPadding: FishUI.Units.smallSpacing
bottomPadding: 0
font.pointSize: 12
}
ListView {
id: listView
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: FishUI.Units.smallSpacing
clip: true clip: true
model: placesModel
// Rounding to whole *logical* pixels is not enough at a fractional scale:
// at 150% a 37px row pitch is 55.5 device px, so every other row lands on
// a half pixel and its icon is resampled.
readonly property real dpr: FishUI.Dpi.ratio
function snap(v) {
return Math.round(v * listView.dpr) / listView.dpr
}
FishUI.WheelHandler {
target: listView
}
// Not leftMargin/rightMargin: Qt rounds the content item's position to a // Not leftMargin/rightMargin: Qt rounds the content item's position to a
// whole logical pixel, so a snapped fractional margin never takes effect. // whole logical pixel, so a snapped fractional margin never takes effect.
// The inset is applied inside the rows instead. // The inset is applied inside the rows instead.
readonly property real sideInset: sideBar.snap(FishUI.Units.smallSpacing * 1.5) readonly property real sideInset: listView.snap(FishUI.Units.smallSpacing * 1.5)
bottomMargin: sideBar.snap(FishUI.Units.smallSpacing) bottomMargin: listView.snap(FishUI.Units.smallSpacing)
spacing: sideBar.snap(FishUI.Units.smallSpacing / 2) spacing: 2
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
bottomPadding: FishUI.Units.smallSpacing bottomPadding: FishUI.Units.smallSpacing
@ -70,13 +114,13 @@ ListView {
section.property: "category" section.property: "category"
section.delegate: Item { section.delegate: Item {
width: ListView.view.width width: ListView.view.width
height: sideBar.snap(FishUI.Units.fontMetrics.height + FishUI.Units.largeSpacing + FishUI.Units.smallSpacing) height: listView.snap(FishUI.Units.fontMetrics.height + FishUI.Units.largeSpacing + FishUI.Units.smallSpacing)
Text { Text {
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.leftMargin: Qt.application.layoutDirection === Qt.RightToLeft anchors.leftMargin: Qt.application.layoutDirection === Qt.RightToLeft
? 0 : sideBar.snap(sideBar.sideInset + FishUI.Units.smallSpacing) ? 0 : listView.snap(listView.sideInset + FishUI.Units.smallSpacing)
anchors.rightMargin: FishUI.Units.smallSpacing anchors.rightMargin: FishUI.Units.smallSpacing
anchors.topMargin: FishUI.Units.largeSpacing anchors.topMargin: FishUI.Units.largeSpacing
anchors.bottomMargin: FishUI.Units.smallSpacing anchors.bottomMargin: FishUI.Units.smallSpacing
@ -90,7 +134,7 @@ ListView {
delegate: Item { delegate: Item {
id: _item id: _item
width: ListView.view.width width: ListView.view.width
height: sideBar.snap(FishUI.Units.fontMetrics.height + FishUI.Units.largeSpacing * 1.5) height: listView.snap(FishUI.Units.fontMetrics.height + FishUI.Units.largeSpacing * 1.5)
property bool checked: sideBar.currentIndex === index property bool checked: sideBar.currentIndex === index
property color hoveredColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 1.1) property color hoveredColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 1.1)
@ -167,8 +211,8 @@ ListView {
} }
Rectangle { Rectangle {
x: sideBar.sideInset x: listView.sideInset
width: _item.width - sideBar.sideInset * 2 width: _item.width - listView.sideInset * 2
height: parent.height height: parent.height
radius: FishUI.Theme.mediumRadius radius: FishUI.Theme.mediumRadius
color: _mouseArea.pressed ? Qt.rgba(FishUI.Theme.textColor.r, color: _mouseArea.pressed ? Qt.rgba(FishUI.Theme.textColor.r,
@ -189,9 +233,9 @@ ListView {
// row height itself is snapped. // row height itself is snapped.
FishUI.IconItem { FishUI.IconItem {
id: _icon id: _icon
x: sideBar.snap(sideBar.sideInset + FishUI.Units.smallSpacing) x: listView.snap(listView.sideInset + FishUI.Units.smallSpacing)
y: sideBar.snap((_item.height - height) / 2) y: listView.snap((_item.height - height) / 2)
width: sideBar.snap(22) width: listView.snap(22)
height: width height: width
source: "qrc:/images/" + model.iconPath source: "qrc:/images/" + model.iconPath
@ -200,22 +244,24 @@ ListView {
Label { Label {
id: _label id: _label
x: sideBar.snap(_icon.x + _icon.width + FishUI.Units.smallSpacing) x: listView.snap(_icon.x + _icon.width + FishUI.Units.smallSpacing)
y: sideBar.snap((_item.height - height) / 2) y: listView.snap((_item.height - height) / 2)
width: _item.width - x - sideBar.snap(FishUI.Units.smallSpacing) width: _item.width - x - listView.snap(FishUI.Units.smallSpacing)
text: model.name text: model.name
color: checked ? FishUI.Theme.highlightColor : FishUI.Theme.textColor color: checked ? FishUI.Theme.highlightColor : FishUI.Theme.textColor
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
}
}
function updateSelection(path) { function updateSelection(path) {
sideBar.currentIndex = -1 listView.currentIndex = -1
for (var i = 0; i < sideBar.count; ++i) { for (var i = 0; i < listView.count; ++i) {
if (path === sideBar.model.get(i).path || if (path === listView.model.get(i).path ||
path === sideBar.model.get(i).url) { path === listView.model.get(i).url) {
sideBar.currentIndex = i listView.currentIndex = i
break break
} }
} }

@ -35,7 +35,8 @@ FishUI.Window {
title: qsTr("File Manager") title: qsTr("File Manager")
background.opacity: 1 background.opacity: 1
header.height: 36 + FishUI.Units.largeSpacing header.height: 40
contentTopMargin: 0
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true LayoutMirroring.childrenInherit: true
@ -62,41 +63,57 @@ FishUI.Window {
headerItem: Item { headerItem: Item {
RowLayout { RowLayout {
id: _headerRow
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: FishUI.Units.smallSpacing * 1.5 anchors.leftMargin: _sideBar.width + FishUI.Units.smallSpacing * 1.5
anchors.rightMargin: FishUI.Units.smallSpacing * 1.5 anchors.rightMargin: FishUI.Units.smallSpacing
anchors.topMargin: FishUI.Units.smallSpacing * 1.5
anchors.bottomMargin: FishUI.Units.smallSpacing * 1.5
spacing: FishUI.Units.smallSpacing spacing: FishUI.Units.smallSpacing
readonly property int buttonSize: 31
FontMetrics {
id: _nameMetrics
font: _folderName.font
}
IconButton { IconButton {
Layout.fillHeight: true Layout.alignment: root.windowButtonsAlignment
implicitWidth: height Layout.topMargin: root.windowButtonsTopMargin
Layout.preferredWidth: _headerRow.buttonSize
Layout.preferredHeight: _headerRow.buttonSize
enabled: _folderPage.canGoBack
source: FishUI.Theme.darkMode ? "qrc:/images/dark/go-previous.svg" source: FishUI.Theme.darkMode ? "qrc:/images/dark/go-previous.svg"
: "qrc:/images/light/go-previous.svg" : "qrc:/images/light/go-previous.svg"
onClicked: _folderPage.goBack() onClicked: _folderPage.goBack()
} }
IconButton { IconButton {
Layout.fillHeight: true Layout.alignment: root.windowButtonsAlignment
implicitWidth: height Layout.topMargin: root.windowButtonsTopMargin
Layout.preferredWidth: _headerRow.buttonSize
Layout.preferredHeight: _headerRow.buttonSize
enabled: _folderPage.canGoForward
source: FishUI.Theme.darkMode ? "qrc:/images/dark/go-next.svg" source: FishUI.Theme.darkMode ? "qrc:/images/dark/go-next.svg"
: "qrc:/images/light/go-next.svg" : "qrc:/images/light/go-next.svg"
onClicked: _folderPage.goForward() onClicked: _folderPage.goForward()
} }
PathBar { Label {
id: _pathBar id: _folderName
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
onItemClicked: _folderPage.openUrl(path) leftPadding: root.windowButtonsTopMargin
onEditorAccepted: _folderPage.openUrl(path) topPadding: root.windowButtonsTopMargin / 2
color: root.active ? FishUI.Theme.textColor : FishUI.Theme.disabledTextColor
font.pointSize: 11
text: _pathBar.currentName
} }
IconButton { IconButton {
Layout.fillHeight: true Layout.alignment: root.windowButtonsAlignment
implicitWidth: height Layout.topMargin: root.windowButtonsTopMargin
Layout.preferredWidth: _headerRow.buttonSize
Layout.preferredHeight: _headerRow.buttonSize
property var gridSource: FishUI.Theme.darkMode ? "qrc:/images/dark/grid.svg" : "qrc:/images/light/grid.svg" property var gridSource: FishUI.Theme.darkMode ? "qrc:/images/dark/grid.svg" : "qrc:/images/light/grid.svg"
property var listSource: FishUI.Theme.darkMode ? "qrc:/images/dark/list.svg" : "qrc:/images/light/list.svg" property var listSource: FishUI.Theme.darkMode ? "qrc:/images/dark/list.svg" : "qrc:/images/light/list.svg"
@ -117,7 +134,7 @@ FishUI.Window {
SideBar { SideBar {
id: _sideBar id: _sideBar
Layout.fillHeight: true Layout.fillHeight: true
width: 180 + FishUI.Units.largeSpacing title: root.title
onClicked: _folderPage.openUrl(path) onClicked: _folderPage.openUrl(path)
onOpenInNewWindow: _folderPage.model.openInNewWindow(path) onOpenInNewWindow: _folderPage.model.openInNewWindow(path)
} }
@ -126,6 +143,8 @@ FishUI.Window {
id: _folderPage id: _folderPage
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
headerHeight: root.header.height
bottomNavigationHeight: root.header.height
onCurrentUrlChanged: { onCurrentUrlChanged: {
_sideBar.updateSelection(currentUrl) _sideBar.updateSelection(currentUrl)
_pathBar.updateUrl(currentUrl) _pathBar.updateUrl(currentUrl)
@ -135,4 +154,40 @@ FishUI.Window {
} }
} }
} }
Item {
id: _navigationBar
x: _folderPage.x
width: _folderPage.width
height: root.header.height
anchors.bottom: parent.bottom
z: 3
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: 1
color: Qt.rgba(FishUI.Theme.textColor.r,
FishUI.Theme.textColor.g,
FishUI.Theme.textColor.b, FishUI.Theme.darkMode ? 0.16 : 0.12)
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: FishUI.Units.smallSpacing * 1.5
anchors.rightMargin: FishUI.Units.smallSpacing * 1.5
anchors.topMargin: FishUI.Units.smallSpacing
anchors.bottomMargin: FishUI.Units.smallSpacing
spacing: 0
PathBar {
id: _pathBar
Layout.fillWidth: true
Layout.fillHeight: true
onItemClicked: _folderPage.openUrl(path)
onEditorAccepted: _folderPage.openUrl(path)
}
}
}
} }

Loading…
Cancel
Save