Commit 00b32e06 authored by Nate Graham's avatar Nate Graham 🔩
Browse files

[RFC] Implement KDE-style header toolbars and statusbars for Playlist and Context view

Summary:
This patch is basically a design proposal in patch form.

Right now, each view's header is not visually separated at all from the content
view below it, and all header areas have different visual styles, some of then
being very tall and taking up a lot of space that could be used for more content.
The context view's flickable is adjusted to take advantage of this, resulting in
a greatly increased amount of vertical space when the lyrics portion is long.

This patch/design proposal implements a new style of header and statusbar
that looks more "KDE-style", for lack of a batter term. It mimics what toolbars
in Kirigami and other QML apps look like. Advantages include:
- Improved visual consistency with modern KDE apps
- Visually pleasing separation from content area
- Scrollable content doesn't appear to get "cut off" under an invisible item anymore
- More compact headers leave more room for content
- Code simplification from using a re-usable component rather than multiple custom views

Test Plan:
Playlist, before: {F6878549}
Playlist, after: {F6878548}

Playlist + context view, before: {F6878551}
Playlist + context view, after: {F6878550}

This patch implements the new header style for only the Playlist and Context View.
If the proposal is approved and the patch lands, I can do the main view too, which
is more complicated to port and will require changes to the autotests as well.

Reviewers: mgallien, #elisa, #vdg

Reviewed By: mgallien, #elisa

Differential Revision: https://phabricator.kde.org/D21676
parent af4937cc
......@@ -398,6 +398,7 @@ if (Qt5Quick_FOUND AND Qt5Widgets_FOUND)
qml/FileBrowserView.qml
qml/ScrollHelper.qml
qml/FlatButtonWithToolTip.qml
qml/HeaderFooterToolbar.qml
)
qt5_add_resources(elisa_SOURCES resources.qrc)
......
......@@ -106,6 +106,8 @@ Item {
property alias defaultFontPointSize: fontSize.font.pointSize
property int headerTitleFontSize: defaultFontPointSize * 2
property int viewSelectorSmallSizeThreshold: 800
Label {
......
......@@ -376,8 +376,6 @@ RowLayout {
Layout.minimumWidth: 0
Layout.maximumWidth: 0
Layout.preferredWidth: 0
Layout.leftMargin: !LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 1.5 : 0
Layout.rightMargin: LayoutMirroring.enabled ? elisaTheme.layoutHorizontalMargin * 1.5 : 0
visible: Layout.minimumWidth != 0
}
......
/*
* Copyright 2016 Matthieu Gallien <matthieu_gallien@yahoo.fr>
* Copyright 2019 Nate Graham <nate@kde.org>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
......@@ -46,136 +47,161 @@ FocusScope {
TextMetrics {
id: titleHeight
text: viewTitleHeight.text
font: viewTitleHeight.font
text: viewTitle.text
font: viewTitle.font
}
LabelWithToolTip {
id: viewTitleHeight
text: i18nc("Title of the context view related to the currently playing track", "Now Playing")
// Header with title
HeaderFooterToolbar {
type: "header"
contentItems: [
LabelWithToolTip {
id: viewTitle
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
font.pointSize: elisaTheme.defaultFontPointSize * 2
text: i18nc("Title of the context view related to the currently playing track", "Now Playing")
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: elisaTheme.layoutVerticalMargin * 3
Layout.bottomMargin: titleHeight.boundingRect.height - titleHeight.boundingRect.y
font.pointSize: elisaTheme.headerTitleFontSize
}
]
}
Image {
id: albumIcon
// Scrollview to hold all the content
Flickable {
id: flickable
clip: true
source: albumArtUrl.toString() === '' ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : albumArtUrl
contentWidth: content.width
contentHeight: content.height
Layout.fillWidth: true
Layout.maximumHeight: elisaTheme.contextCoverImageSize
Layout.preferredHeight: elisaTheme.contextCoverImageSize
Layout.maximumWidth: topItem.width
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
Layout.fillHeight: true
sourceSize.width: topItem.width
sourceSize.height: topItem.width
boundsBehavior: Flickable.StopAtBounds
asynchronous: true
ScrollBar.vertical: ScrollBar {
id: scrollBar
policy: ScrollBar.AlwaysOn
}
fillMode: Image.PreserveAspectCrop
}
// Album Art + title + metadata + lyrics
ColumnLayout {
id: content
LabelWithToolTip {
id: titleLabel
width: topItem.width
font.pointSize: elisaTheme.defaultFontPointSize * 2
font.weight: Font.Bold
// Album art slice
Image {
id: albumIcon
source: albumArtUrl.toString() === '' ? Qt.resolvedUrl(elisaTheme.defaultAlbumImage) : albumArtUrl
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: elisaTheme.layoutVerticalMargin
Layout.fillWidth: true
Layout.maximumWidth: topItem.width
}
Layout.maximumHeight: elisaTheme.contextCoverImageSize
Layout.preferredHeight: elisaTheme.contextCoverImageSize
Layout.fillWidth: true
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.topMargin: elisaTheme.layoutVerticalMargin
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
LabelWithToolTip {
id: albumArtistLabel
sourceSize.width: topItem.width
sourceSize.height: topItem.width
text: (artistName && albumName ? i18nc('display of artist and album in context view', '<i>by</i> <b>%1</b> <i>from</i> <b>%2</b>', artistName, albumName) : '')
asynchronous: true
font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4)
fillMode: Image.PreserveAspectCrop
}
visible: artistName !== '' && albumName !== ''
// Song title
LabelWithToolTip {
id: titleLabel
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
Layout.fillWidth: true
Layout.maximumWidth: topItem.width
}
font.pointSize: elisaTheme.headerTitleFontSize
font.weight: Font.Bold
LabelWithToolTip {
id: albumLabel
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.topMargin: elisaTheme.layoutVerticalMargin
}
text: (albumName ? i18nc('display of album in context view', '<i>from</i> <b>%1</b>', albumName) : '')
LabelWithToolTip {
id: albumArtistLabel
font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4)
text: (artistName && albumName ? i18nc('display of artist and album in context view', '<i>by</i> <b>%1</b> <i>from</i> <b>%2</b>', artistName, albumName) : '')
visible: artistName === '' && albumName !== ''
font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4)
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
Layout.fillWidth: true
Layout.maximumWidth: topItem.width
}
visible: artistName !== '' && albumName !== ''
LabelWithToolTip {
id: artistLabel
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
}
text: (artistName ? i18nc('display of artist in context view', '<i>by</i> <b>%1</b>', artistName) : '')
LabelWithToolTip {
id: albumLabel
font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4)
text: (albumName ? i18nc('display of album in context view', '<i>from</i> <b>%1</b>', albumName) : '')
visible: artistName !== '' && albumName === ''
font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4)
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
Layout.fillWidth: true
Layout.maximumWidth: topItem.width
}
visible: artistName === '' && albumName !== ''
Flickable {
id: flickable
clip: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
}
contentWidth: topItem.width
contentHeight: allMetaData.height
LabelWithToolTip {
id: artistLabel
Layout.fillWidth: true
Layout.fillHeight: true
text: (artistName ? i18nc('display of artist in context view', '<i>by</i> <b>%1</b>', artistName) : '')
boundsBehavior: Flickable.StopAtBounds
font.pointSize: Math.round(elisaTheme.defaultFontPointSize * 1.4)
ScrollBar.vertical: ScrollBar {
id: scrollBar
policy: ScrollBar.AlwaysOn
}
visible: artistName !== '' && albumName === ''
ColumnLayout {
id: allMetaData
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
}
spacing: 0
// Metadata
ColumnLayout {
id: allMetaData
width: topItem.width
spacing: 0
Layout.fillWidth: true
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Repeater {
id: trackData
Repeater {
id: trackData
model: metaDataModel
model: metaDataModel
delegate: MetaDataDelegate {
Layout.fillWidth: true
delegate: MetaDataDelegate {
Layout.fillWidth: true
}
}
}
// Lyrics
ContextViewLyrics {
id: lyricsContextView
Layout.fillWidth: true
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
visible: metaDataModel.lyrics !== ""
......@@ -184,30 +210,26 @@ FocusScope {
}
}
RowLayout {
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
Layout.topMargin: elisaTheme.layoutVerticalMargin
Layout.bottomMargin: elisaTheme.layoutVerticalMargin
Layout.maximumWidth: topItem.width
spacing: elisaTheme.layoutHorizontalMargin
// Footer with file path label
HeaderFooterToolbar {
type: "footer"
contentLayoutSpacing: elisaTheme.layoutHorizontalMargin
contentItems: [
Image {
sourceSize.width: fileNameLabel.height
sourceSize.height: fileNameLabel.height
Image {
sourceSize.width: fileNameLabel.height
sourceSize.height: fileNameLabel.height
source: elisaTheme.folderIcon
},
LabelWithToolTip {
id: fileNameLabel
source: elisaTheme.folderIcon
}
LabelWithToolTip {
id: fileNameLabel
Layout.fillWidth: true
text: fileUrl
Layout.fillWidth: true
elide: Text.ElideLeft
}
text: fileUrl
elide: Text.ElideLeft
}
]
}
}
......
......@@ -265,6 +265,12 @@ ApplicationWindow {
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: myPalette.mid
}
ContentView {
id: contentView
Layout.fillHeight: true
......
/*
* Copyright 2016 Matthieu Gallien <matthieu_gallien@yahoo.fr>
* Copyright 2019 Nate Graham <nate@kde.org>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.10
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
import org.kde.elisa 1.0
ColumnLayout {
spacing: 0
Layout.fillWidth: true
// Is this a header or a footer? Acceptable values are
// "header" (separator drawn on bottom)
// "footer" (separator drawn on top)
// "other" (no separator drawn)
property string type
// A list of items to be shown within the header or footer
property alias contentItems: contentLayout.children
// Spacing of content items. Defaults to 0
property alias contentLayoutSpacing: contentLayout.spacing
// Separator line above the header
Rectangle {
visible: type == "footer" && type != "other"
Layout.fillWidth: true
height: 1
color: myPalette.mid
}
// Background rectangle + content layout
Rectangle {
id: headerBackground
color: myPalette.window
Layout.fillWidth: true
height: contentLayout.height
// Content layout
RowLayout {
id: contentLayout
anchors {
left: parent.left
leftMargin: elisaTheme.layoutHorizontalMargin
right: parent.right
rightMargin: elisaTheme.layoutHorizontalMargin
verticalCenter: parent.verticalCenter
}
height: childrenRect.height + (elisaTheme.layoutVerticalMargin * 2)
spacing: 0
// Items provided by the contentItems property will go here
}
}
// Separator line under the header
Rectangle {
visible: type == "header" && type != "other"
Layout.fillWidth: true
height: 1
color: myPalette.mid
}
}
/*
* Copyright 2016-2017 Matthieu Gallien <matthieu_gallien@yahoo.fr>
* Copyright 2019 Nate Graham <nate@kde.org>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
......@@ -103,55 +104,39 @@ FocusScope {
anchors.fill: parent
spacing: 0
LabelWithToolTip {
id: viewTitleHeight
text: i18nc("Title of the view of the playlist", "Playlist")
color: myPalette.text
font.pointSize: elisaTheme.defaultFontPointSize * 2
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: elisaTheme.layoutVerticalMargin * 3
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
LabelWithToolTip {
id: playListInfo
text: i18np("1 track", "%1 tracks", (elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount : 0))
visible: elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount > 0 : false
color: myPalette.text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
}
Item { Layout.fillWidth: true }
Controls1.ToolButton {
action: showCurrentTrack
Keys.onReturnPressed: action.trigger()
}
Controls1.ToolButton {
action: savePlaylist
Keys.onReturnPressed: action.trigger()
}
Controls1.ToolButton {
action: loadPlaylist
Keys.onReturnPressed: action.trigger()
}
Controls1.ToolButton {
action: clearPlayList
Keys.onReturnPressed: action.trigger()
}
// Header with title and toolbar buttons
HeaderFooterToolbar {
type: "header"
contentItems: [
// Header title
LabelWithToolTip {
Layout.fillWidth: true
text: i18nc("Title of the view of the playlist", "Playlist")
font.pointSize: elisaTheme.headerTitleFontSize
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
},
// Toolbar buttons
Controls1.ToolButton {
action: showCurrentTrack
Keys.onReturnPressed: action.trigger()
},
Controls1.ToolButton {
action: savePlaylist
Keys.onReturnPressed: action.trigger()
},
Controls1.ToolButton {
action: loadPlaylist
Keys.onReturnPressed: action.trigger()
},
Controls1.ToolButton {
action: clearPlayList
Keys.onReturnPressed: action.trigger()
}
]
}
ColumnLayout {
......@@ -193,7 +178,7 @@ FocusScope {
Layout.rightMargin: elisaTheme.layoutHorizontalMargin
Layout.leftMargin: elisaTheme.layoutHorizontalMargin
font.pointSize: elisaTheme.defaultFontPointSize * 2
font.pointSize: elisaTheme.headerTitleFontSize
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
......@@ -288,6 +273,21 @@ FocusScope {
]
}
}
// Footer with number of tracks label
HeaderFooterToolbar {
type: "footer"
contentItems: [
LabelWithToolTip {
id: trackCountLabel
Layout.fillWidth: true
text: i18np("1 track", "%1 tracks", (elisa.mediaPlayList ? elisa.mediaPlayList.tracksCount : 0))
elide: Text.ElideLeft
}
]
}
}
}
......
......@@ -45,6 +45,7 @@
<file>qml/MetaDataDelegate.qml</file>
<file>qml/ContextViewLyrics.qml</file>
<file>qml/ViewSelectorDelegate.qml</file>
<file>qml/HeaderFooterToolbar.qml</file>
</qresource>
<qresource prefix="/qml/+windows">
<file alias="Theme.qml">windows/WindowsTheme.qml</file>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment