Rewrite SidePane using QQC Drawer

Cleaner approach, gets rid of the HPage swipe view hack, better
performances, a lot less complex
This commit is contained in:
miruka 2019-12-08 14:43:41 -04:00
parent 5326726c4f
commit 06a6a4c08d
13 changed files with 250 additions and 260 deletions

View File

@ -1,3 +1,7 @@
- make pageup/down not slippery again
- rename all setfocus() to takefocus()
- refactor roomsidepane too
- better pane minsize
- better cancel for all boxes - better cancel for all boxes
- Media - Media
- Confirmation box after picking file to upload - Confirmation box after picking file to upload

72
src/qml/Base/HDrawer.qml Normal file
View File

@ -0,0 +1,72 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import "../utils.js" as Utils
Drawer {
id: drawer
implicitWidth: calculatedWidth
implicitHeight: parent.height
// FIXME: https://bugreports.qt.io/browse/QTBUG-59141
// dragMargin: parent.width / 2
interactive: collapse
position: 1
visible: ! collapse
modal: false
closePolicy: Popup.CloseOnEscape
background: Rectangle { id: bg; color: theme.colors.strongBackground }
signal userResized(int newWidth)
property int normalWidth: 300
property int minNormalWidth: resizeAreaWidth
property int maxNormalWidth: parent.width
property bool collapse: window.width < 400
property int collapseExpandedWidth: parent.width
property alias color: bg.color
property alias resizeAreaWidth: resizeArea.width
readonly property int calculatedWidth:
collapse ?
collapseExpandedWidth :
Math.max(minNormalWidth, Math.min(normalWidth, maxNormalWidth))
Behavior on width {
enabled: ! resizeMouseHandler.drag.active
NumberAnimation { duration: 100 }
}
Item {
id: resizeArea
anchors.right: parent.right
width: theme.spacing / 2
height: parent.height
z: 9999
MouseArea {
id: resizeMouseHandler
anchors.fill: parent
enabled: ! drawer.collapse
acceptedButtons: Qt.LeftButton
hoverEnabled: true
cursorShape:
containsMouse || drag.active ?
Qt.SizeHorCursor : Qt.ArrowCursor
onPressed: canResize = true
onReleased: { canResize = false; userResized(drawer.normalWidth) }
onMouseXChanged:
if (canResize)
drawer.normalWidth = drawer.calculatedWidth + mouseX
property bool canResize: false
}
}
}

View File

@ -3,14 +3,13 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import "../SidePane" import "../SidePane"
SwipeView { Page {
id: innerPage
default property alias columnChildren: contentColumn.children default property alias columnChildren: contentColumn.children
property alias page: innerPage
property alias header: innerPage.header
property alias footer: innerPage.header
property alias flickable: innerFlickable property alias flickable: innerFlickable
property alias headerLabel: innerHeaderLabel property alias headerLabel: innerHeaderLabel
property var hideHeaderUnderHeight: null property var hideHeaderUnderHeight: null
@ -19,72 +18,56 @@ SwipeView {
property bool becomeKeyboardFlickableTarget: true property bool becomeKeyboardFlickableTarget: true
id: swipeView
clip: true
interactive: sidePane.reduce
currentIndex: 1
SidePane { background: null
implicitWidth: swipeView.width
animateWidth: false // Without this, the SidePane gets auto-focused header: Rectangle {
collapse: false implicitWidth: parent ? parent.width : 0
reduce: false color: theme.controls.header.background
visible: swipeView.interactive
onVisibleChanged: if (currentIndex != 1) swipeView.setCurrentIndex(1) height: innerHeaderLabel.text && (
! hideHeaderUnderHeight ||
window.height >=
hideHeaderUnderHeight +
theme.baseElementsHeight +
currentSpacing * 2
) ? theme.baseElementsHeight : 0
Behavior on height { HNumberAnimation {} }
visible: height > 0
HLabel {
id: innerHeaderLabel
anchors.fill: parent
textFormat: Text.StyledText
font.pixelSize: theme.fontSize.big
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
leftPadding: currentSpacing
rightPadding: leftPadding
}
} }
Page { leftPadding: currentSpacing < theme.spacing ? 0 : currentSpacing
id: innerPage rightPadding: leftPadding
background: null Behavior on leftPadding { HNumberAnimation {} }
header: Rectangle { HFlickable {
implicitWidth: parent ? parent.width : 0 id: innerFlickable
color: theme.controls.header.background anchors.fill: parent
clip: true
contentWidth: parent.width
contentHeight: contentColumn.childrenRect.height
height: innerHeaderLabel.text && ( Component.onCompleted:
! hideHeaderUnderHeight || if (becomeKeyboardFlickableTarget) shortcuts.flickTarget = this
window.height >=
hideHeaderUnderHeight +
theme.baseElementsHeight +
currentSpacing * 2
) ? theme.baseElementsHeight : 0
Behavior on height { HNumberAnimation {} } HColumnLayout {
visible: height > 0 id: contentColumn
width: innerFlickable.width
HLabel { height: innerFlickable.height
id: innerHeaderLabel
anchors.fill: parent
textFormat: Text.StyledText
font.pixelSize: theme.fontSize.big
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
leftPadding: currentSpacing
rightPadding: leftPadding
}
}
leftPadding: currentSpacing < theme.spacing ? 0 : currentSpacing
rightPadding: leftPadding
Behavior on leftPadding { HNumberAnimation {} }
HFlickable {
id: innerFlickable
anchors.fill: parent
clip: true
contentWidth: parent.width
contentHeight: contentColumn.childrenRect.height
Component.onCompleted:
if (becomeKeyboardFlickableTarget) shortcuts.flickTarget = this
HColumnLayout {
id: contentColumn
width: innerFlickable.width
height: innerFlickable.height
}
} }
} }
} }

View File

@ -5,6 +5,9 @@ import "../utils.js" as Utils
HPage { HPage {
id: chatPage id: chatPage
leftPadding: 0
rightPadding: 0
// The target will be our EventList, not the page itself // The target will be our EventList, not the page itself
becomeKeyboardFlickableTarget: false becomeKeyboardFlickableTarget: false
@ -49,9 +52,6 @@ HPage {
Behavior on height { HNumberAnimation {} } Behavior on height { HNumberAnimation {} }
} }
page.leftPadding: 0
page.rightPadding: 0
HLoader { HLoader {
source: ready ? "ChatSplitView.qml" : "../Base/HBusyIndicator.qml" source: ready ? "ChatSplitView.qml" : "../Base/HBusyIndicator.qml"

View File

@ -8,7 +8,7 @@ import "FileTransfer"
HSplitView { HSplitView {
id: chatSplitView id: chatSplitView
Component.onCompleted: composer.setFocus() Component.onCompleted: composer.takeFocus()
HColumnLayout { HColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true

View File

@ -5,8 +5,6 @@ import "../Dialogs"
import "../utils.js" as Utils import "../utils.js" as Utils
Rectangle { Rectangle {
function setFocus() { areaScrollView.forceActiveFocus() }
property string indent: " " property string indent: " "
property var aliases: window.settings.writeAliases property var aliases: window.settings.writeAliases
@ -40,6 +38,8 @@ Rectangle {
lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length : lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length :
1 1
function takeFocus() { areaScrollView.forceActiveFocus() }
// property var pr: lineTextUntilCursor // property var pr: lineTextUntilCursor
// onPrChanged: print( // onPrChanged: print(
// "y", cursorY, "x", cursorX, // "y", cursorY, "x", cursorX,

View File

@ -6,7 +6,7 @@ import "../utils.js" as Utils
HTileDelegate { HTileDelegate {
id: accountDelegate id: accountDelegate
spacing: 0 spacing: 0
topPadding: model.index > 0 ? sidePane.currentSpacing / 2 : 0 topPadding: model.index > 0 ? theme.spacing / 2 : 0
bottomPadding: topPadding bottomPadding: topPadding
backgroundColor: theme.sidePane.account.background backgroundColor: theme.sidePane.account.background
opacity: collapsed && ! forceExpand ? opacity: collapsed && ! forceExpand ?
@ -54,7 +54,7 @@ HTileDelegate {
title.color: theme.sidePane.account.name title.color: theme.sidePane.account.name
title.text: model.data.display_name || model.data.user_id title.text: model.data.display_name || model.data.user_id
title.font.pixelSize: theme.fontSize.big title.font.pixelSize: theme.fontSize.big
title.leftPadding: sidePane.currentSpacing title.leftPadding: theme.spacing
HButton { HButton {
id: addChat id: addChat
@ -112,7 +112,7 @@ HTileDelegate {
text: qsTr("Sign out") text: qsTr("Sign out")
onTriggered: Utils.makePopup( onTriggered: Utils.makePopup(
"Popups/SignOutPopup.qml", "Popups/SignOutPopup.qml",
mainUI, window,
{ "userId": model.data.user_id }, { "userId": model.data.user_id },
popup => { popup.ok.connect(() => { disconnecting = true }) }, popup => { popup.ok.connect(() => { disconnecting = true }) },
) )

View File

@ -5,7 +5,7 @@ import "../utils.js" as Utils
HTileDelegate { HTileDelegate {
id: roomDelegate id: roomDelegate
spacing: sidePane.currentSpacing spacing: theme.spacing
backgroundColor: theme.sidePane.room.background backgroundColor: theme.sidePane.room.background
opacity: model.data.left ? theme.sidePane.room.leftRoomOpacity : 1 opacity: model.data.left ? theme.sidePane.room.leftRoomOpacity : 1
@ -110,7 +110,7 @@ HTileDelegate {
onTriggered: Utils.makePopup( onTriggered: Utils.makePopup(
"Popups/LeaveRoomPopup.qml", "Popups/LeaveRoomPopup.qml",
sidePane, window,
{ {
userId: model.user_id, userId: model.user_id,
roomId: model.data.room_id, roomId: model.data.room_id,
@ -126,7 +126,7 @@ HTileDelegate {
onTriggered: Utils.makePopup( onTriggered: Utils.makePopup(
"Popups/ForgetRoomPopup.qml", "Popups/ForgetRoomPopup.qml",
sidePane, window,
{ {
userId: model.user_id, userId: model.user_id,
roomId: model.data.room_id, roomId: model.data.room_id,

View File

@ -3,108 +3,37 @@ import QtQuick.Layouts 1.12
import "../Base" import "../Base"
import "../utils.js" as Utils import "../utils.js" as Utils
Rectangle { HDrawer {
id: sidePane id: sidePane
clip: true clip: true
opacity: mainUI.accountsPresent && ! reduce ? 1 : 0 opacity: mainUI.accountsPresent ? 1 : 0
visible: opacity > 0
color: theme.sidePane.background color: theme.sidePane.background
normalWidth: window.uiState.sidePaneManualWidth
onUserResized: {
window.uiState.sidePaneManualWidth = newWidth
window.uiStateChanged()
}
property bool hasFocus: toolBar.filterField.activeFocus property bool hasFocus: toolBar.filterField.activeFocus
property alias sidePaneList: sidePaneList property alias sidePaneList: sidePaneList
property alias toolBar: toolBar property alias toolBar: toolBar
property real autoWidthRatio: theme.sidePane.autoWidthRatio
property bool manuallyResizing: false
property bool manuallyResized: false
property int manualWidth: 0
property bool animateWidth: true
Component.onCompleted: { function toggleFocus() {
if (window.uiState.sidePaneManualWidth) { if (toolBar.filterField.activeFocus) {
manualWidth = window.uiState.sidePaneManualWidth pageLoader.takeFocus()
manuallyResized = true return
}
}
onFocusChanged: if (focus) toolBar.filterField.forceActiveFocus()
onManualWidthChanged: {
window.uiState.sidePaneManualWidth = manualWidth
window.uiStateChanged()
}
property int maximumCalculatedWidth: Math.min(
manuallyResized ? manualWidth : theme.sidePane.maximumAutoWidth,
window.width - theme.minimumSupportedWidthPlusSpacing
)
property int parentWidth: parent.width
// Needed for SplitView since it breaks the binding when user manual sizes
onParentWidthChanged: width = Qt.binding(() => implicitWidth)
property int calculatedWidth: Math.min(
manuallyResized ? manualWidth * theme.uiScale :
parentWidth * autoWidthRatio,
maximumCalculatedWidth
)
property bool collapse:
(manuallyResizing ? width : calculatedWidth) <
(manuallyResized ?
(theme.sidePane.collapsedWidth + theme.spacing * 2) :
theme.sidePane.autoCollapseBelowWidth)
property bool reduce:
window.width < theme.sidePane.autoReduceBelowWindowWidth
property int implicitWidth:
reduce ? 0 :
collapse ? theme.sidePane.collapsedWidth :
calculatedWidth
property int currentSpacing:
width <= theme.sidePane.collapsedWidth + theme.spacing * 2 ?
0 : theme.spacing
Behavior on currentSpacing { HNumberAnimation {} }
Behavior on implicitWidth {
HNumberAnimation { factor: animateWidth ? 1 : 0 }
}
function setFocus() {
forceActiveFocus()
if (reduce) {
pageLoader.item.currentIndex = 0
}
}
Keys.enabled: sidePane.hasFocus
Keys.onUpPressed: sidePaneList.previous(false) // do not activate
Keys.onDownPressed: sidePaneList.next(false)
Keys.onEnterPressed: Keys.onReturnPressed(event)
Keys.onReturnPressed: if (event.modifiers & Qt.ShiftModifier) {
sidePaneList.toggleCollapseAccount()
} else {
if (window.settings.clearRoomFilterOnEnter) {
mainUI.sidePane.toolBar.roomFilter = ""
} }
sidePaneList.activate() sidePane.open()
} toolBar.filterField.forceActiveFocus()
Keys.onEscapePressed: {
if (window.settings.clearRoomFilterOnEscape) {
mainUI.sidePane.toolBar.roomFilter = ""
}
mainUI.pageLoader.forceActiveFocus()
} }
Behavior on opacity { HOpacityAnimator {} }
HColumnLayout { HColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -118,6 +47,7 @@ Rectangle {
SidePaneToolBar { SidePaneToolBar {
id: toolBar id: toolBar
sidePaneList: sidePaneList
} }
} }
} }

View File

@ -108,7 +108,6 @@ HListView {
{ {
currentIndex = i currentIndex = i
currentItem.item.toggleCollapse() currentItem.item.toggleCollapse()
activate()
} }
} }
} }

View File

@ -5,6 +5,7 @@ import "../Base"
HRowLayout { HRowLayout {
id: toolBar id: toolBar
property SidePaneList sidePaneList
readonly property alias addAccountButton: addAccountButton readonly property alias addAccountButton: addAccountButton
readonly property alias filterField: filterField readonly property alias filterField: filterField
property alias roomFilter: filterField.text property alias roomFilter: filterField.text
@ -29,8 +30,7 @@ HRowLayout {
backgroundColor: theme.sidePane.filterRooms.background backgroundColor: theme.sidePane.filterRooms.background
bordered: false bordered: false
Layout.fillWidth: true Component.onCompleted: filterField.text = uiState.sidePaneFilter
Layout.fillHeight: true
onTextChanged: { onTextChanged: {
if (window.uiState.sidePaneFilter == text) return if (window.uiState.sidePaneFilter == text) return
@ -38,12 +38,26 @@ HRowLayout {
window.uiStateChanged() window.uiStateChanged()
} }
Connections { Layout.fillWidth: true
target: window Layout.fillHeight: true
// Keep multiple instances of SidePaneToolBar in sync.
// This also sets the text on startup. Keys.onUpPressed: sidePaneList.previous(false) // do not activate
onUiStateChanged: filterField.text = uiState.sidePaneFilter Keys.onDownPressed: sidePaneList.next(false)
Keys.onEnterPressed: Keys.onReturnPressed(event)
Keys.onReturnPressed: {
if (event.modifiers & Qt.ShiftModifier) {
sidePaneList.toggleCollapseAccount()
return
}
if (window.settings.clearRoomFilterOnEnter) text = ""
sidePaneList.activate()
} }
Keys.onEscapePressed: {
if (window.settings.clearRoomFilterOnEscape) text = ""
mainUI.pageLoader.forceActiveFocus()
}
} }
} }

View File

@ -11,7 +11,6 @@ Item {
id: mainUI id: mainUI
focus: true focus: true
Component.onCompleted: window.mainUI = mainUI Component.onCompleted: window.mainUI = mainUI
Keys.forwardTo: [shortcuts]
readonly property alias shortcuts: shortcuts readonly property alias shortcuts: shortcuts
readonly property alias sidePane: sidePane readonly property alias sidePane: sidePane
@ -55,107 +54,99 @@ Item {
} }
HSplitView { SidePane {
id: uiSplitView id: sidePane
maxNormalWidth: parent.width - theme.minimumSupportedWidth
}
HLoader {
id: pageLoader
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: sidePane.width * sidePane.position
visible: ! sidePane.hidden || anchors.leftMargin < width
onAnyResizingChanged: if (anyResizing) { // onSourceChanged: if (sidePane.collapse) sidePane.close()
sidePane.manuallyResizing = true
} else {
sidePane.manuallyResizing = false property bool isWide: width > theme.contentIsWideAbove
sidePane.manuallyResized = true
sidePane.manualWidth = sidePane.width // List of previously loaded [componentUrl, {properties}]
property var history: []
property int historyLength: 20
Component.onCompleted: {
if (! py.startupAnyAccountsSaved) {
pageLoader.showPage("AddAccount/AddAccount")
return
}
let page = window.uiState.page
let props = window.uiState.pageProperties
if (page == "Chat/Chat.qml") {
pageLoader.showRoom(props.userId, props.roomId)
} else {
pageLoader._show(page, props)
}
} }
SidePane { function _show(componentUrl, properties={}) {
id: sidePane history.unshift([componentUrl, properties])
if (history.length > historyLength) history.pop()
// Initial width until user manually resizes pageLoader.setSource(componentUrl, properties)
width: implicitWidth
Layout.minimumWidth: reduce ? 0 : theme.sidePane.collapsedWidth
Layout.maximumWidth:
window.width - theme.minimumSupportedWidthPlusSpacing
Behavior on Layout.minimumWidth { HNumberAnimation {} }
} }
HLoader { function showPage(name, properties={}) {
id: pageLoader let path = `Pages/${name}.qml`
_show(path, properties)
property bool isWide: width > theme.contentIsWideAbove window.uiState.page = path
window.uiState.pageProperties = properties
window.uiStateChanged()
}
// List of previously loaded [componentUrl, {properties}] function showRoom(userId, roomId) {
property var history: [] _show("Chat/Chat.qml", {userId, roomId})
property int historyLength: 20
Component.onCompleted: { window.uiState.page = "Chat/Chat.qml"
if (! py.startupAnyAccountsSaved) { window.uiState.pageProperties = {userId, roomId}
pageLoader.showPage("AddAccount/AddAccount") window.uiStateChanged()
return }
}
let page = window.uiState.page function showPrevious(timesBack=1) {
let props = window.uiState.pageProperties timesBack = Math.min(timesBack, history.length - 1)
if (timesBack < 1) return false
if (page == "Chat/Chat.qml") { let [componentUrl, properties] = history[timesBack]
pageLoader.showRoom(props.userId, props.roomId)
} else {
pageLoader._show(page, props)
}
}
function _show(componentUrl, properties={}) { _show(componentUrl, properties)
history.unshift([componentUrl, properties])
if (history.length > historyLength) history.pop()
pageLoader.setSource(componentUrl, properties) window.uiState.page = componentUrl
} window.uiState.pageProperties = properties
window.uiStateChanged()
return true
}
function showPage(name, properties={}) { function takeFocus() {
let path = `Pages/${name}.qml` pageLoader.item.forceActiveFocus()
_show(path, properties) if (sidePane.collapse) sidePane.close()
}
window.uiState.page = path
window.uiState.pageProperties = properties
window.uiStateChanged()
}
function showRoom(userId, roomId) { onStatusChanged: if (status == Loader.Ready) {
_show("Chat/Chat.qml", {userId, roomId}) pageLoader.takeFocus()
appearAnimation.start()
}
window.uiState.page = "Chat/Chat.qml" clip: appearAnimation.running
window.uiState.pageProperties = {userId, roomId} XAnimator {
window.uiStateChanged() id: appearAnimation
} target: pageLoader.item
from: -300
function showPrevious(timesBack=1) { to: 0
timesBack = Math.min(timesBack, history.length - 1) easing.type: Easing.OutBack
if (timesBack < 1) return false duration: theme.animationDuration * 2
let [componentUrl, properties] = history[timesBack]
_show(componentUrl, properties)
window.uiState.page = componentUrl
window.uiState.pageProperties = properties
window.uiStateChanged()
return true
}
onStatusChanged: if (status == Loader.Ready) {
item.forceActiveFocus()
appearAnimation.start()
}
clip: appearAnimation.running
XAnimator {
id: appearAnimation
target: pageLoader.item
from: -300
to: 0
easing.type: Easing.OutBack
duration: theme.animationDuration * 2
}
} }
} }

View File

@ -261,9 +261,6 @@ ui:
sidePane: sidePane:
real autoWidthRatio: 0.33 * uiScale
int maximumAutoWidth: 320 * uiScale
int autoCollapseBelowWidth: 128 * uiScale int autoCollapseBelowWidth: 128 * uiScale
int collapsedWidth: controls.avatar.size int collapsedWidth: controls.avatar.size