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
- Media
- 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 "../SidePane"
SwipeView {
Page {
id: innerPage
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 headerLabel: innerHeaderLabel
property var hideHeaderUnderHeight: null
@ -19,72 +18,56 @@ SwipeView {
property bool becomeKeyboardFlickableTarget: true
id: swipeView
clip: true
interactive: sidePane.reduce
currentIndex: 1
SidePane {
implicitWidth: swipeView.width
animateWidth: false // Without this, the SidePane gets auto-focused
collapse: false
reduce: false
visible: swipeView.interactive
onVisibleChanged: if (currentIndex != 1) swipeView.setCurrentIndex(1)
background: null
header: Rectangle {
implicitWidth: parent ? parent.width : 0
color: theme.controls.header.background
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 {
id: innerPage
background: null
leftPadding: currentSpacing < theme.spacing ? 0 : currentSpacing
rightPadding: leftPadding
Behavior on leftPadding { HNumberAnimation {} }
header: Rectangle {
implicitWidth: parent ? parent.width : 0
color: theme.controls.header.background
HFlickable {
id: innerFlickable
anchors.fill: parent
clip: true
contentWidth: parent.width
contentHeight: contentColumn.childrenRect.height
height: innerHeaderLabel.text && (
! hideHeaderUnderHeight ||
window.height >=
hideHeaderUnderHeight +
theme.baseElementsHeight +
currentSpacing * 2
) ? theme.baseElementsHeight : 0
Component.onCompleted:
if (becomeKeyboardFlickableTarget) shortcuts.flickTarget = this
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
}
}
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
}
HColumnLayout {
id: contentColumn
width: innerFlickable.width
height: innerFlickable.height
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,108 +3,37 @@ import QtQuick.Layouts 1.12
import "../Base"
import "../utils.js" as Utils
Rectangle {
HDrawer {
id: sidePane
clip: true
opacity: mainUI.accountsPresent && ! reduce ? 1 : 0
visible: opacity > 0
opacity: mainUI.accountsPresent ? 1 : 0
color: theme.sidePane.background
normalWidth: window.uiState.sidePaneManualWidth
onUserResized: {
window.uiState.sidePaneManualWidth = newWidth
window.uiStateChanged()
}
property bool hasFocus: toolBar.filterField.activeFocus
property alias sidePaneList: sidePaneList
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: {
if (window.uiState.sidePaneManualWidth) {
manualWidth = window.uiState.sidePaneManualWidth
manuallyResized = true
}
}
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 = ""
function toggleFocus() {
if (toolBar.filterField.activeFocus) {
pageLoader.takeFocus()
return
}
sidePaneList.activate()
}
Keys.onEscapePressed: {
if (window.settings.clearRoomFilterOnEscape) {
mainUI.sidePane.toolBar.roomFilter = ""
}
mainUI.pageLoader.forceActiveFocus()
sidePane.open()
toolBar.filterField.forceActiveFocus()
}
Behavior on opacity { HOpacityAnimator {} }
HColumnLayout {
anchors.fill: parent
@ -118,6 +47,7 @@ Rectangle {
SidePaneToolBar {
id: toolBar
sidePaneList: sidePaneList
}
}
}

View File

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

View File

@ -5,6 +5,7 @@ import "../Base"
HRowLayout {
id: toolBar
property SidePaneList sidePaneList
readonly property alias addAccountButton: addAccountButton
readonly property alias filterField: filterField
property alias roomFilter: filterField.text
@ -29,8 +30,7 @@ HRowLayout {
backgroundColor: theme.sidePane.filterRooms.background
bordered: false
Layout.fillWidth: true
Layout.fillHeight: true
Component.onCompleted: filterField.text = uiState.sidePaneFilter
onTextChanged: {
if (window.uiState.sidePaneFilter == text) return
@ -38,12 +38,26 @@ HRowLayout {
window.uiStateChanged()
}
Connections {
target: window
// Keep multiple instances of SidePaneToolBar in sync.
// This also sets the text on startup.
onUiStateChanged: filterField.text = uiState.sidePaneFilter
Layout.fillWidth: true
Layout.fillHeight: true
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()
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
focus: true
Component.onCompleted: window.mainUI = mainUI
Keys.forwardTo: [shortcuts]
readonly property alias shortcuts: shortcuts
readonly property alias sidePane: sidePane
@ -55,107 +54,99 @@ Item {
}
HSplitView {
id: uiSplitView
SidePane {
id: sidePane
maxNormalWidth: parent.width - theme.minimumSupportedWidth
}
HLoader {
id: pageLoader
anchors.fill: parent
anchors.leftMargin: sidePane.width * sidePane.position
visible: ! sidePane.hidden || anchors.leftMargin < width
onAnyResizingChanged: if (anyResizing) {
sidePane.manuallyResizing = true
} else {
sidePane.manuallyResizing = false
sidePane.manuallyResized = true
sidePane.manualWidth = sidePane.width
// onSourceChanged: if (sidePane.collapse) sidePane.close()
property bool isWide: width > theme.contentIsWideAbove
// 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 {
id: sidePane
function _show(componentUrl, properties={}) {
history.unshift([componentUrl, properties])
if (history.length > historyLength) history.pop()
// Initial width until user manually resizes
width: implicitWidth
Layout.minimumWidth: reduce ? 0 : theme.sidePane.collapsedWidth
Layout.maximumWidth:
window.width - theme.minimumSupportedWidthPlusSpacing
Behavior on Layout.minimumWidth { HNumberAnimation {} }
pageLoader.setSource(componentUrl, properties)
}
HLoader {
id: pageLoader
function showPage(name, properties={}) {
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}]
property var history: []
property int historyLength: 20
function showRoom(userId, roomId) {
_show("Chat/Chat.qml", {userId, roomId})
Component.onCompleted: {
if (! py.startupAnyAccountsSaved) {
pageLoader.showPage("AddAccount/AddAccount")
return
}
window.uiState.page = "Chat/Chat.qml"
window.uiState.pageProperties = {userId, roomId}
window.uiStateChanged()
}
let page = window.uiState.page
let props = window.uiState.pageProperties
function showPrevious(timesBack=1) {
timesBack = Math.min(timesBack, history.length - 1)
if (timesBack < 1) return false
if (page == "Chat/Chat.qml") {
pageLoader.showRoom(props.userId, props.roomId)
} else {
pageLoader._show(page, props)
}
}
let [componentUrl, properties] = history[timesBack]
function _show(componentUrl, properties={}) {
history.unshift([componentUrl, properties])
if (history.length > historyLength) history.pop()
_show(componentUrl, properties)
pageLoader.setSource(componentUrl, properties)
}
window.uiState.page = componentUrl
window.uiState.pageProperties = properties
window.uiStateChanged()
return true
}
function showPage(name, properties={}) {
let path = `Pages/${name}.qml`
_show(path, properties)
function takeFocus() {
pageLoader.item.forceActiveFocus()
if (sidePane.collapse) sidePane.close()
}
window.uiState.page = path
window.uiState.pageProperties = properties
window.uiStateChanged()
}
function showRoom(userId, roomId) {
_show("Chat/Chat.qml", {userId, roomId})
onStatusChanged: if (status == Loader.Ready) {
pageLoader.takeFocus()
appearAnimation.start()
}
window.uiState.page = "Chat/Chat.qml"
window.uiState.pageProperties = {userId, roomId}
window.uiStateChanged()
}
function showPrevious(timesBack=1) {
timesBack = Math.min(timesBack, history.length - 1)
if (timesBack < 1) return false
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
}
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:
real autoWidthRatio: 0.33 * uiScale
int maximumAutoWidth: 320 * uiScale
int autoCollapseBelowWidth: 128 * uiScale
int collapsedWidth: controls.avatar.size