Remove message text selection hack
This commit is contained in:
parent
f148837fae
commit
710dba09ec
6
TODO.md
6
TODO.md
|
@ -1,5 +1,9 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
- side pane back/forward buttons hard to use on touch
|
||||||
|
- is it still slow on sway with wayland-egl?
|
||||||
|
- room pane drag-scroll a tiny bit activates the delegates
|
||||||
|
|
||||||
## Goals before 0.5.0
|
## Goals before 0.5.0
|
||||||
|
|
||||||
- Redacting messages
|
- Redacting messages
|
||||||
|
@ -13,8 +17,6 @@
|
||||||
|
|
||||||
## Refactoring
|
## Refactoring
|
||||||
|
|
||||||
- Rewrite the message text selection buggy mess
|
|
||||||
|
|
||||||
- Put keybindings in the components they belong to instead of shoving them
|
- Put keybindings in the components they belong to instead of shoving them
|
||||||
all in one central file
|
all in one central file
|
||||||
|
|
||||||
|
|
|
@ -19,120 +19,26 @@ TextEdit {
|
||||||
|
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
|
||||||
Component.onCompleted: updateSelection()
|
|
||||||
|
|
||||||
|
|
||||||
// If index is a whole number, the label will get two \n before itself
|
|
||||||
// in container.joinedSelection. If it's a decimal number, if gets one \n.
|
|
||||||
property real index
|
|
||||||
property HSelectableLabelContainer container
|
|
||||||
property bool selectable: true
|
|
||||||
|
|
||||||
|
|
||||||
function updateSelection() {
|
|
||||||
if (! selectable && label.selectedText) {
|
|
||||||
label.deselect()
|
|
||||||
updateContainerSelectedTexts()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! selectable) return
|
|
||||||
|
|
||||||
if (! container.reversed &&
|
|
||||||
container.selectionStart <= container.selectionEnd ||
|
|
||||||
|
|
||||||
container.reversed &&
|
|
||||||
container.selectionStart > container.selectionEnd)
|
|
||||||
{
|
|
||||||
var first = container.selectionStart
|
|
||||||
var firstPos = container.selectionStartPosition
|
|
||||||
var last = container.selectionEnd
|
|
||||||
var lastPos = container.selectionEndPosition
|
|
||||||
} else {
|
|
||||||
var first = container.selectionEnd
|
|
||||||
var firstPos = container.selectionEndPosition
|
|
||||||
var last = container.selectionStart
|
|
||||||
var lastPos = container.selectionStartPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first === index && last === index) {
|
|
||||||
select(
|
|
||||||
label.positionAt(firstPos.x, firstPos.y),
|
|
||||||
label.positionAt(lastPos.x, lastPos.y),
|
|
||||||
)
|
|
||||||
|
|
||||||
} else if ((! container.reversed && first < index && index < last) ||
|
|
||||||
(container.reversed && first > index && index > last))
|
|
||||||
{
|
|
||||||
label.selectAll()
|
|
||||||
|
|
||||||
} else if (first === index) {
|
|
||||||
label.select(positionAt(firstPos.x, firstPos.y), length)
|
|
||||||
|
|
||||||
} else if (last === index) {
|
|
||||||
label.select(0, positionAt(lastPos.x, lastPos.y))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
label.deselect()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateContainerSelectedTexts()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateContainerSelectedTexts() {
|
|
||||||
container.selectedTexts[index] = selectedText
|
|
||||||
container.selectedTextsChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectWordAt(position) {
|
function selectWordAt(position) {
|
||||||
container.clearSelection()
|
|
||||||
label.cursorPosition = positionAt(position.x, position.y)
|
label.cursorPosition = positionAt(position.x, position.y)
|
||||||
label.selectWord()
|
label.selectWord()
|
||||||
updateContainerSelectedTexts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAllText() {
|
function selectAllText() {
|
||||||
container.clearSelection()
|
|
||||||
label.selectAll()
|
label.selectAll()
|
||||||
updateContainerSelectedTexts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
// XXX
|
||||||
target: container
|
// TapHandler {
|
||||||
onSelectionInfoChanged: updateSelection()
|
// acceptedButtons: Qt.LeftButton
|
||||||
onDeselectAll: deselect()
|
// onTapped: {
|
||||||
}
|
// tapCount === 2 ? selectWordAt(eventPoint.position) :
|
||||||
|
// tapCount === 3 ? selectAllText() :
|
||||||
DropArea {
|
// null
|
||||||
anchors.fill: parent
|
// }
|
||||||
onPositionChanged: {
|
// }
|
||||||
if (! container.selecting) {
|
|
||||||
container.clearSelection()
|
|
||||||
container.selectionStart = index
|
|
||||||
container.selectionStartPosition = Qt.point(drag.x, drag.y)
|
|
||||||
container.selecting = true
|
|
||||||
} else {
|
|
||||||
container.selectionEnd = index
|
|
||||||
container.selectionEndPosition = Qt.point(drag.x, drag.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onTapped: {
|
|
||||||
tapCount === 2 ? selectWordAt(eventPoint.position) :
|
|
||||||
tapCount === 3 ? selectAllText() :
|
|
||||||
container.clearSelection()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PointHandler {
|
|
||||||
onActiveChanged:
|
|
||||||
active ? container.dragStarted() : container.dragStopped()
|
|
||||||
onPointChanged: container.dragPointChanged(point)
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: label
|
anchors.fill: label
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
import Clipboard 0.1
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
signal deselectAll()
|
|
||||||
signal dragStarted()
|
|
||||||
signal dragStopped()
|
|
||||||
signal dragPointChanged(var eventPoint)
|
|
||||||
|
|
||||||
|
|
||||||
property bool reversed: false
|
|
||||||
|
|
||||||
property bool selecting: false
|
|
||||||
property real selectionStart: -1
|
|
||||||
property real selectionEnd: -1
|
|
||||||
property point selectionStartPosition: Qt.point(-1, -1)
|
|
||||||
property point selectionEndPosition: Qt.point(-1, -1)
|
|
||||||
property var selectedTexts: ({})
|
|
||||||
|
|
||||||
readonly property var selectionInfo: [
|
|
||||||
selectionStart, selectionStartPosition,
|
|
||||||
selectionEnd, selectionEndPosition,
|
|
||||||
]
|
|
||||||
|
|
||||||
readonly property string joinedSelection: {
|
|
||||||
const toCopy = []
|
|
||||||
|
|
||||||
for (const key of Object.keys(selectedTexts).sort()) {
|
|
||||||
if (! selectedTexts[key]) continue
|
|
||||||
|
|
||||||
// For some dumb reason, Object.keys convert the floats to strings
|
|
||||||
toCopy.push(Number.isInteger(parseFloat(key)) ? "\n\n" : "\n")
|
|
||||||
toCopy.push(selectedTexts[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reversed) toCopy.reverse()
|
|
||||||
|
|
||||||
return toCopy.join("").trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onJoinedSelectionChanged:
|
|
||||||
if (joinedSelection) Clipboard.selection = joinedSelection
|
|
||||||
|
|
||||||
onDragStarted: {
|
|
||||||
draggedItem.Drag.active = true
|
|
||||||
}
|
|
||||||
onDragStopped: {
|
|
||||||
draggedItem.Drag.drop()
|
|
||||||
draggedItem.Drag.active = false
|
|
||||||
selecting = false
|
|
||||||
}
|
|
||||||
onDragPointChanged: {
|
|
||||||
const pos = mapFromItem(
|
|
||||||
mainUI, eventPoint.scenePosition.x, eventPoint.scenePosition.y,
|
|
||||||
)
|
|
||||||
draggedItem.x = pos.x
|
|
||||||
draggedItem.y = pos.y
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function clearSelection() {
|
|
||||||
selecting = false
|
|
||||||
selectionStart = -1
|
|
||||||
selectionEnd = -1
|
|
||||||
selectionStartPosition = Qt.point(-1, -1)
|
|
||||||
selectionEndPosition = Qt.point(-1, -1)
|
|
||||||
deselectAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// PointHandler and TapHandler won't activate if the press occurs inside
|
|
||||||
// a label child, so we need a Point/TapHandler inside them too.
|
|
||||||
|
|
||||||
PointHandler {
|
|
||||||
// We don't use a DragHandler because they have an unchangable minimum
|
|
||||||
// drag distance before they activate.
|
|
||||||
id: pointHandler
|
|
||||||
onActiveChanged: active ? dragStarted() : dragStopped()
|
|
||||||
onPointChanged: dragPointChanged(point)
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onTapped: clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This item will trigger the children labels's DropAreas
|
|
||||||
Item { id: draggedItem }
|
|
||||||
}
|
|
|
@ -165,9 +165,10 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
area.onSelectedTextChanged: if (area.selectedText && eventList) {
|
// XXX
|
||||||
eventList.selectableLabelContainer.clearSelection()
|
// area.onSelectedTextChanged: if (area.selectedText && eventList) {
|
||||||
}
|
// eventList.selectableLabelContainer.clearSelection()
|
||||||
|
// }
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
area.Keys.onReturnPressed.connect(ev => {
|
area.Keys.onReturnPressed.connect(ev => {
|
||||||
|
@ -206,15 +207,16 @@ Rectangle {
|
||||||
})
|
})
|
||||||
|
|
||||||
area.Keys.onPressed.connect(ev => {
|
area.Keys.onPressed.connect(ev => {
|
||||||
if (ev.matches(StandardKey.Copy) &&
|
// XXX
|
||||||
eventList &&
|
// if (ev.matches(StandardKey.Copy) &&
|
||||||
eventList.selectableLabelContainer.joinedSelection
|
// eventList &&
|
||||||
) {
|
// eventList.selectableLabelContainer.joinedSelection
|
||||||
ev.accepted = true
|
// ) {
|
||||||
Clipboard.text =
|
// ev.accepted = true
|
||||||
eventList.selectableLabelContainer.joinedSelection
|
// Clipboard.text =
|
||||||
return
|
// eventList.selectableLabelContainer.joinedSelection
|
||||||
}
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// FIXME: buggy
|
// FIXME: buggy
|
||||||
// if (ev.modifiers === Qt.NoModifier &&
|
// if (ev.modifiers === Qt.NoModifier &&
|
||||||
|
|
|
@ -109,8 +109,6 @@ HRowLayout {
|
||||||
|
|
||||||
HSelectableLabel {
|
HSelectableLabel {
|
||||||
id: contentLabel
|
id: contentLabel
|
||||||
container: selectableLabelContainer
|
|
||||||
index: model.index
|
|
||||||
visible: ! pureMedia
|
visible: ! pureMedia
|
||||||
|
|
||||||
topPadding: theme.chat.message.verticalSpacing
|
topPadding: theme.chat.message.verticalSpacing
|
||||||
|
@ -158,13 +156,11 @@ HRowLayout {
|
||||||
|
|
||||||
function selectAllText() {
|
function selectAllText() {
|
||||||
// Select the message body without the date or name
|
// Select the message body without the date or name
|
||||||
container.clearSelection()
|
|
||||||
contentLabel.select(
|
contentLabel.select(
|
||||||
0,
|
0,
|
||||||
contentLabel.length -
|
contentLabel.length -
|
||||||
timeText.length - 1 // - 1: separating space
|
timeText.length - 1 // - 1: separating space
|
||||||
)
|
)
|
||||||
contentLabel.updateContainerSelectedTexts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler { id: contentHover }
|
HoverHandler { id: contentHover }
|
||||||
|
|
|
@ -7,183 +7,132 @@ import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property alias selectableLabelContainer: selectableLabelContainer
|
|
||||||
property alias eventList: eventList
|
|
||||||
|
|
||||||
color: theme.chat.eventList.background
|
color: theme.chat.eventList.background
|
||||||
|
|
||||||
HSelectableLabelContainer {
|
|
||||||
id: selectableLabelContainer
|
property Item selectableLabelContainer: Item {}
|
||||||
|
property alias eventList: eventList
|
||||||
|
|
||||||
|
|
||||||
|
HListView {
|
||||||
|
id: eventList
|
||||||
|
clip: true
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
reversed: eventList.verticalLayoutDirection === ListView.BottomToTop
|
anchors.leftMargin: theme.spacing
|
||||||
|
anchors.rightMargin: theme.spacing
|
||||||
|
|
||||||
DragHandler {
|
topMargin: theme.spacing
|
||||||
target: null
|
bottomMargin: theme.spacing
|
||||||
onActiveChanged: if (! active) dragFlicker.speed = 0
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
onCentroidChanged: {
|
|
||||||
const left = centroid.pressedButtons & Qt.LeftButton
|
|
||||||
const vel = centroid.velocity.y
|
|
||||||
const pos = centroid.position.y
|
|
||||||
const dist = Math.min(selectableLabelContainer.height / 4, 50)
|
|
||||||
const boost = 20 * (pos < dist ? -pos : -(height - pos))
|
|
||||||
|
|
||||||
dragFlicker.speed =
|
// Keep x scroll pages cached, to limit images having to be
|
||||||
left && vel && pos < dist ? 1000 + boost :
|
// reloaded from network.
|
||||||
left && vel && pos > height - dist ? -1000 + -boost :
|
cacheBuffer: Screen.desktopAvailableHeight * 2
|
||||||
0
|
|
||||||
|
model: ModelStore.get(chat.userId, chat.roomId, "events")
|
||||||
|
delegate: EventDelegate {}
|
||||||
|
|
||||||
|
// Since the list is BottomToTop, this is actually a header
|
||||||
|
footer: Item {
|
||||||
|
width: eventList.width
|
||||||
|
height: (button.height + theme.spacing * 2) * opacity
|
||||||
|
opacity: eventList.loading ? 1 : 0
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
|
||||||
|
HButton {
|
||||||
|
id: button
|
||||||
|
width: Math.min(parent.width, implicitWidth)
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
loading: true
|
||||||
|
text: qsTr("Loading previous messages...")
|
||||||
|
enableRadius: true
|
||||||
|
iconItem.small: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
onYPosChanged:
|
||||||
id: dragFlicker
|
if (canLoad && yPos < 0.1) Qt.callLater(loadPastEvents)
|
||||||
interval: 100
|
|
||||||
running: speed !== 0
|
|
||||||
repeat: true
|
|
||||||
|
|
||||||
onTriggered: {
|
// When an invited room becomes joined, we should now be able to
|
||||||
if (eventList.verticalOvershoot !== 0) return
|
// fetch past events.
|
||||||
if (speed < 0 && eventList.atYEnd) return
|
onInviterChanged: canLoad = true
|
||||||
if (eventList.atYBeggining) {
|
|
||||||
if (bouncedStart) { return } else { bouncedStart = true }
|
|
||||||
}
|
|
||||||
|
|
||||||
eventList.flick(0, speed * acceleration)
|
Component.onCompleted: shortcuts.flickTarget = eventList
|
||||||
acceleration = Math.min(8, acceleration * 1.05)
|
|
||||||
}
|
|
||||||
onRunningChanged: if (! running) {
|
|
||||||
acceleration = 1.0
|
|
||||||
bouncedStart = false
|
|
||||||
eventList.cancelFlick()
|
|
||||||
eventList.returnToBounds()
|
|
||||||
}
|
|
||||||
|
|
||||||
property real speed: 0.0
|
|
||||||
property real acceleration: 1.0
|
property string inviter: chat.roomInfo.inviter || ""
|
||||||
property bool bouncedStart: false
|
property real yPos: visibleArea.yPosition
|
||||||
|
property bool canLoad: true
|
||||||
|
property bool loading: false
|
||||||
|
|
||||||
|
property bool ownEventsOnRight:
|
||||||
|
width < theme.chat.eventList.ownEventsOnRightUnderWidth
|
||||||
|
|
||||||
|
|
||||||
|
function canCombine(item, itemAfter) {
|
||||||
|
if (! item || ! itemAfter) return false
|
||||||
|
|
||||||
|
return Boolean(
|
||||||
|
! canTalkBreak(item, itemAfter) &&
|
||||||
|
! canDayBreak(item, itemAfter) &&
|
||||||
|
item.sender_id === itemAfter.sender_id &&
|
||||||
|
utils.minutesBetween(item.date, itemAfter.date) <= 5
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HListView {
|
function canTalkBreak(item, itemAfter) {
|
||||||
id: eventList
|
if (! item || ! itemAfter) return false
|
||||||
clip: true
|
|
||||||
allowDragging: false
|
|
||||||
|
|
||||||
anchors.fill: parent
|
return Boolean(
|
||||||
anchors.leftMargin: theme.spacing
|
! canDayBreak(item, itemAfter) &&
|
||||||
anchors.rightMargin: theme.spacing
|
utils.minutesBetween(item.date, itemAfter.date) >= 20
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
topMargin: theme.spacing
|
function canDayBreak(item, itemAfter) {
|
||||||
bottomMargin: theme.spacing
|
if (itemAfter && itemAfter.event_type === "RoomCreateEvent")
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
return true
|
||||||
|
|
||||||
// Keep x scroll pages cached, to limit images having to be
|
if (! item || ! itemAfter || ! item.date || ! itemAfter.date)
|
||||||
// reloaded from network.
|
return false
|
||||||
cacheBuffer: Screen.desktopAvailableHeight * 2
|
|
||||||
|
|
||||||
onYPosChanged:
|
return item.date.getDate() !== itemAfter.date.getDate()
|
||||||
if (canLoad && yPos < 0.1) Qt.callLater(loadPastEvents)
|
}
|
||||||
|
|
||||||
// When an invited room becomes joined, we should now be able to
|
function loadPastEvents() {
|
||||||
// fetch past events.
|
// try/catch blocks to hide pyotherside error when the
|
||||||
onInviterChanged: canLoad = true
|
// component is destroyed but func is still running
|
||||||
|
|
||||||
// Since the list is BottomToTop, this is actually a header
|
try {
|
||||||
footer: Item {
|
eventList.canLoad = false
|
||||||
width: eventList.width
|
eventList.loading = true
|
||||||
height: (button.height + theme.spacing * 2) * opacity
|
|
||||||
opacity: eventList.loading ? 1 : 0
|
|
||||||
visible: opacity > 0
|
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
py.callClientCoro(
|
||||||
|
chat.userId,
|
||||||
|
"load_past_events",
|
||||||
|
[chat.roomId],
|
||||||
|
moreToLoad => {
|
||||||
|
try {
|
||||||
|
eventList.canLoad = moreToLoad
|
||||||
|
|
||||||
HButton {
|
// Call yPosChanged() to run this func again
|
||||||
id: button
|
// if the loaded messages aren't enough to fill
|
||||||
width: Math.min(parent.width, implicitWidth)
|
// the screen.
|
||||||
anchors.centerIn: parent
|
if (moreToLoad) yPosChanged()
|
||||||
|
|
||||||
loading: true
|
eventList.loading = false
|
||||||
text: qsTr("Loading previous messages...")
|
} catch (err) {
|
||||||
enableRadius: true
|
return
|
||||||
iconItem.small: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: shortcuts.flickTarget = eventList
|
|
||||||
|
|
||||||
|
|
||||||
property string inviter: chat.roomInfo.inviter || ""
|
|
||||||
property real yPos: visibleArea.yPosition
|
|
||||||
property bool canLoad: true
|
|
||||||
property bool loading: false
|
|
||||||
|
|
||||||
property bool ownEventsOnRight:
|
|
||||||
width < theme.chat.eventList.ownEventsOnRightUnderWidth
|
|
||||||
|
|
||||||
|
|
||||||
function canCombine(item, itemAfter) {
|
|
||||||
if (! item || ! itemAfter) return false
|
|
||||||
|
|
||||||
return Boolean(
|
|
||||||
! canTalkBreak(item, itemAfter) &&
|
|
||||||
! canDayBreak(item, itemAfter) &&
|
|
||||||
item.sender_id === itemAfter.sender_id &&
|
|
||||||
utils.minutesBetween(item.date, itemAfter.date) <= 5
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function canTalkBreak(item, itemAfter) {
|
|
||||||
if (! item || ! itemAfter) return false
|
|
||||||
|
|
||||||
return Boolean(
|
|
||||||
! canDayBreak(item, itemAfter) &&
|
|
||||||
utils.minutesBetween(item.date, itemAfter.date) >= 20
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function canDayBreak(item, itemAfter) {
|
|
||||||
if (itemAfter && itemAfter.event_type === "RoomCreateEvent")
|
|
||||||
return true
|
|
||||||
|
|
||||||
if (! item || ! itemAfter || ! item.date || ! itemAfter.date)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return item.date.getDate() !== itemAfter.date.getDate()
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadPastEvents() {
|
|
||||||
// try/catch blocks to hide pyotherside error when the
|
|
||||||
// component is destroyed but func is still running
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventList.canLoad = false
|
|
||||||
eventList.loading = true
|
|
||||||
|
|
||||||
py.callClientCoro(
|
|
||||||
chat.userId,
|
|
||||||
"load_past_events",
|
|
||||||
[chat.roomId],
|
|
||||||
moreToLoad => {
|
|
||||||
try {
|
|
||||||
eventList.canLoad = moreToLoad
|
|
||||||
|
|
||||||
// Call yPosChanged() to run this func again
|
|
||||||
// if the loaded messages aren't enough to fill
|
|
||||||
// the screen.
|
|
||||||
if (moreToLoad) yPosChanged()
|
|
||||||
|
|
||||||
eventList.loading = false
|
|
||||||
} catch (err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
} catch (err) {
|
)
|
||||||
return
|
} catch (err) {
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model: ModelStore.get(chat.userId, chat.roomId, "events")
|
|
||||||
delegate: EventDelegate {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,4 +142,49 @@ Rectangle {
|
||||||
visible: eventList.model.count < 1
|
visible: eventList.model.count < 1
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DragHandler {
|
||||||
|
target: null
|
||||||
|
onActiveChanged: if (! active) dragFlicker.speed = 0
|
||||||
|
onCentroidChanged: {
|
||||||
|
const left = centroid.pressedButtons & Qt.LeftButton
|
||||||
|
const vel = centroid.velocity.y
|
||||||
|
const pos = centroid.position.y
|
||||||
|
const dist = Math.min(selectableLabelContainer.height / 4, 50)
|
||||||
|
const boost = 20 * (pos < dist ? -pos : -(height - pos))
|
||||||
|
|
||||||
|
dragFlicker.speed =
|
||||||
|
left && vel && pos < dist ? 1000 + boost :
|
||||||
|
left && vel && pos > height - dist ? -1000 + -boost :
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: dragFlicker
|
||||||
|
interval: 100
|
||||||
|
running: speed !== 0
|
||||||
|
repeat: true
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
if (eventList.verticalOvershoot !== 0) return
|
||||||
|
if (speed < 0 && eventList.atYEnd) return
|
||||||
|
if (eventList.atYBeggining) {
|
||||||
|
if (bouncedStart) { return } else { bouncedStart = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
eventList.flick(0, speed * acceleration)
|
||||||
|
acceleration = Math.min(8, acceleration * 1.05)
|
||||||
|
}
|
||||||
|
onRunningChanged: if (! running) {
|
||||||
|
acceleration = 1.0
|
||||||
|
bouncedStart = false
|
||||||
|
eventList.cancelFlick()
|
||||||
|
eventList.returnToBounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
property real speed: 0.0
|
||||||
|
property real acceleration: 1.0
|
||||||
|
property bool bouncedStart: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user