parent
6b10c2ef2d
commit
01196e8b50
|
@ -19,6 +19,23 @@ Item {
|
||||||
MessageEventModel {
|
MessageEventModel {
|
||||||
id: messageEventModel
|
id: messageEventModel
|
||||||
room: currentRoom
|
room: currentRoom
|
||||||
|
|
||||||
|
onModelReset: {
|
||||||
|
if (currentRoom)
|
||||||
|
{
|
||||||
|
var lastScrollPosition = currentRoom.savedTopVisibleIndex()
|
||||||
|
if (lastScrollPosition === 0)
|
||||||
|
messageListView.positionViewAtBeginning()
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log("Scrolling to position", lastScrollPosition)
|
||||||
|
messageListView.positionViewAtIndex(lastScrollPosition, ListView.Contain)
|
||||||
|
}
|
||||||
|
if (messageListView.contentY < messageListView.originY + 10)
|
||||||
|
currentRoom.getPreviousContent(100)
|
||||||
|
}
|
||||||
|
console.log("Model timeline reset")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomDrawer {
|
RoomDrawer {
|
||||||
|
@ -46,6 +63,8 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 64
|
Layout.preferredHeight: 64
|
||||||
|
|
||||||
|
z: 10
|
||||||
|
|
||||||
color: Material.accent
|
color: Material.accent
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
|
@ -98,221 +117,215 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
ListView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.leftMargin: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
z: -10
|
id: messageListView
|
||||||
|
|
||||||
spacing: 0
|
displayMarginBeginning: 40
|
||||||
|
displayMarginEnd: 40
|
||||||
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
ListView {
|
flickDeceleration: 4096
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: messageListView
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
property int largestVisibleIndex: count > 0 ? indexAt(contentX, contentY + height - 1) : -1
|
||||||
|
|
||||||
|
onContentYChanged: {
|
||||||
|
// Check whether we're about to bump into the ceiling in 2 seconds
|
||||||
|
var curVelocity = verticalVelocity // Snapshot the current speed
|
||||||
|
if( curVelocity < 0 && contentY + curVelocity*2 < originY)
|
||||||
|
{
|
||||||
|
// Request the amount of messages enough to scroll at this
|
||||||
|
// rate for 3 more seconds
|
||||||
|
var avgHeight = contentHeight / count
|
||||||
|
currentRoom.getPreviousContent(-curVelocity*3 / avgHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMovementEnded: currentRoom.saveViewport(indexAt(contentX, contentY), largestVisibleIndex)
|
||||||
|
|
||||||
|
model: SortFilterProxyModel {
|
||||||
|
id: sortedRoomListModel
|
||||||
|
|
||||||
|
sourceModel: messageEventModel
|
||||||
|
|
||||||
|
filters: ExpressionFilter {
|
||||||
|
expression: marks !== 0x08 && marks !== 0x10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
id: delegateColumn
|
||||||
|
|
||||||
displayMarginBeginning: 40
|
|
||||||
displayMarginEnd: 40
|
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
Label {
|
||||||
// flickDeceleration: 4096
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
// cacheBuffer: 200
|
visible: section !== aboveSection
|
||||||
|
|
||||||
model: SortFilterProxyModel {
|
text: section
|
||||||
id: sortedRoomListModel
|
color: "white"
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
leftPadding: 8
|
||||||
|
rightPadding: 8
|
||||||
|
topPadding: 4
|
||||||
|
bottomPadding: 4
|
||||||
|
|
||||||
sourceModel: messageEventModel
|
background: Rectangle {
|
||||||
|
color: MSettings.darkTheme ? "#484848" : "grey"
|
||||||
filters: ExpressionFilter {
|
|
||||||
expression: marks !== 0x08 && marks !== 0x10
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
MessageDelegate {
|
||||||
width: parent.width
|
visible: eventType === "notice" || eventType === "message" || eventType === "image" || eventType === "video" || eventType === "audio" || eventType === "file"
|
||||||
|
|
||||||
id: delegateColumn
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: section !== aboveSection
|
|
||||||
|
|
||||||
text: section
|
|
||||||
color: "white"
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
leftPadding: 8
|
|
||||||
rightPadding: 8
|
|
||||||
topPadding: 4
|
|
||||||
bottomPadding: 4
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: MSettings.darkTheme ? "#484848" : "grey"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDelegate {
|
|
||||||
visible: eventType === "notice" || eventType === "message" || eventType === "image" || eventType === "video" || eventType === "audio" || eventType === "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
StateDelegate {
|
|
||||||
Layout.maximumWidth: messageListView.width * 0.8
|
|
||||||
|
|
||||||
visible: eventType === "emote" || eventType === "state"
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: eventType === "other"
|
|
||||||
|
|
||||||
text: display
|
|
||||||
color: "grey"
|
|
||||||
font.italic: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: readMarker === true && index !== 0
|
|
||||||
|
|
||||||
text: "And Now"
|
|
||||||
color: "white"
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
leftPadding: 8
|
|
||||||
rightPadding: 8
|
|
||||||
topPadding: 4
|
|
||||||
bottomPadding: 4
|
|
||||||
|
|
||||||
background: Rectangle { color: MSettings.darkTheme ? "#484848" : "grey" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: messageListViewScrollBar
|
StateDelegate {
|
||||||
|
Layout.maximumWidth: messageListView.width * 0.8
|
||||||
|
|
||||||
onAtYBeginningChanged: atYBeginning && currentRoom ? currentRoom.getPreviousContent(20) : {}
|
visible: eventType === "emote" || eventType === "state"
|
||||||
onAtYEndChanged: atYEnd && currentRoom ? currentRoom.markAllMessagesAsRead() : {}
|
|
||||||
|
|
||||||
RoundButton {
|
|
||||||
width: 64
|
|
||||||
height: 64
|
|
||||||
|
|
||||||
id: goTopFab
|
|
||||||
|
|
||||||
visible: !(parent.atYEnd || messageListView.moving)
|
|
||||||
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue313"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
Material.background: Material.accent
|
|
||||||
|
|
||||||
onClicked: parent.positionViewAtBeginning()
|
|
||||||
|
|
||||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageContextMenu { id: messageContextMenu }
|
Label {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
Popup {
|
visible: eventType === "other"
|
||||||
property string sourceText
|
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
text: display
|
||||||
y: (window.height - height) / 2
|
color: "grey"
|
||||||
width: 480
|
font.italic: true
|
||||||
|
|
||||||
id: sourceDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
modal: true
|
|
||||||
|
|
||||||
padding: 16
|
|
||||||
|
|
||||||
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
|
||||||
|
|
||||||
contentItem: ScrollView {
|
|
||||||
TextArea {
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: true
|
|
||||||
|
|
||||||
text: sourceDialog.sourceText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup {
|
Label {
|
||||||
property alias listModel: readMarkerListView.model
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
visible: readMarker === true && index !== 0
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 320
|
|
||||||
|
|
||||||
id: readMarkerDialog
|
text: "And Now"
|
||||||
|
color: "white"
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
leftPadding: 8
|
||||||
|
rightPadding: 8
|
||||||
|
topPadding: 4
|
||||||
|
bottomPadding: 4
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
background: Rectangle { color: MSettings.darkTheme ? "#484848" : "grey" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
modal: true
|
RoundButton {
|
||||||
padding: 16
|
width: 64
|
||||||
|
height: 64
|
||||||
|
|
||||||
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
id: goTopFab
|
||||||
|
|
||||||
contentItem: ListView {
|
visible: !(parent.atYEnd || messageListView.moving)
|
||||||
implicitHeight: Math.min(window.height - 64, readMarkerListView.contentHeight)
|
|
||||||
|
|
||||||
id: readMarkerListView
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
clip: true
|
contentItem: MaterialIcon {
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
anchors.fill: parent
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
icon: "\ue313"
|
||||||
width: parent.width
|
color: "white"
|
||||||
height: 48
|
}
|
||||||
|
|
||||||
RowLayout {
|
Material.background: Material.accent
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 8
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
ImageItem {
|
onClicked: parent.positionViewAtBeginning()
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
image: modelData.avatar
|
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||||
hint: modelData.displayName
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
MessageContextMenu { id: messageContextMenu }
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: modelData.displayName
|
Popup {
|
||||||
}
|
property string sourceText
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
x: (window.width - width) / 2
|
||||||
|
y: (window.height - height) / 2
|
||||||
|
width: 480
|
||||||
|
|
||||||
|
id: sourceDialog
|
||||||
|
|
||||||
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
padding: 16
|
||||||
|
|
||||||
|
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
||||||
|
|
||||||
|
contentItem: ScrollView {
|
||||||
|
TextArea {
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
|
||||||
|
text: sourceDialog.sourceText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar {
|
Popup {
|
||||||
Layout.preferredWidth: 16
|
property alias listModel: readMarkerListView.model
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: messageListViewScrollBar
|
x: (window.width - width) / 2
|
||||||
|
y: (window.height - height) / 2
|
||||||
|
width: 320
|
||||||
|
|
||||||
|
id: readMarkerDialog
|
||||||
|
|
||||||
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
padding: 16
|
||||||
|
|
||||||
|
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
||||||
|
|
||||||
|
contentItem: ListView {
|
||||||
|
implicitHeight: Math.min(window.height - 64, readMarkerListView.contentHeight)
|
||||||
|
|
||||||
|
id: readMarkerListView
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 8
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
ImageItem {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
image: modelData.avatar
|
||||||
|
hint: modelData.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: modelData.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,3 +147,31 @@ QDateTime SpectralRoom::lastActiveTime() {
|
||||||
}
|
}
|
||||||
|
|
||||||
float SpectralRoom::orderForTag(QString name) { return tag(name).order; }
|
float SpectralRoom::orderForTag(QString name) { return tag(name).order; }
|
||||||
|
|
||||||
|
int SpectralRoom::savedTopVisibleIndex() const {
|
||||||
|
return firstDisplayedMarker() == timelineEdge()
|
||||||
|
? 0
|
||||||
|
: firstDisplayedMarker() - messageEvents().rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpectralRoom::savedBottomVisibleIndex() const {
|
||||||
|
return lastDisplayedMarker() == timelineEdge()
|
||||||
|
? 0
|
||||||
|
: lastDisplayedMarker() - messageEvents().rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpectralRoom::saveViewport(int topIndex, int bottomIndex) {
|
||||||
|
if (topIndex == -1 || bottomIndex == -1 ||
|
||||||
|
(bottomIndex == savedBottomVisibleIndex() &&
|
||||||
|
(bottomIndex == 0 || topIndex == savedTopVisibleIndex())))
|
||||||
|
return;
|
||||||
|
if (bottomIndex == 0) {
|
||||||
|
qDebug() << "Saving viewport as the latest available";
|
||||||
|
setFirstDisplayedEventId({});
|
||||||
|
setLastDisplayedEventId({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug() << "Saving viewport:" << topIndex << "thru" << bottomIndex;
|
||||||
|
setFirstDisplayedEvent(maxTimelineIndex() - topIndex);
|
||||||
|
setLastDisplayedEvent(maxTimelineIndex() - bottomIndex);
|
||||||
|
}
|
||||||
|
|
|
@ -38,6 +38,9 @@ class SpectralRoom : public Room {
|
||||||
QDateTime lastActiveTime();
|
QDateTime lastActiveTime();
|
||||||
|
|
||||||
Q_INVOKABLE float orderForTag(QString name);
|
Q_INVOKABLE float orderForTag(QString name);
|
||||||
|
Q_INVOKABLE int savedTopVisibleIndex() const;
|
||||||
|
Q_INVOKABLE int savedBottomVisibleIndex() const;
|
||||||
|
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_cachedInput;
|
QString m_cachedInput;
|
||||||
|
|
Loading…
Reference in New Issue