Better message bubbles.

This commit is contained in:
Black Hat 2019-05-09 21:18:04 +08:00
parent 927a0aa017
commit 50445bccf1
6 changed files with 134 additions and 44 deletions

View File

@ -15,7 +15,7 @@ import Spectral.Font 0.1
import Spectral.Effect 2.0 import Spectral.Effect 2.0
ColumnLayout { ColumnLayout {
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other") readonly property bool avatarVisible: !sentByMe && showAuthor
readonly property bool sentByMe: author === currentRoom.localUser readonly property bool sentByMe: author === currentRoom.localUser
property bool openOnFinished: false property bool openOnFinished: false
@ -39,8 +39,6 @@ ColumnLayout {
} }
RowLayout { RowLayout {
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
z: -5 z: -5
id: messageRow id: messageRow
@ -111,7 +109,7 @@ ColumnLayout {
} }
Label { Label {
text: progressInfo.active ? (progressInfo.progress + "/" + progressInfo.total) : content.info ? content.info.size : "Unknown" text: progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0)
color: MPalette.lighter color: MPalette.lighter
} }
} }
@ -195,4 +193,20 @@ ColumnLayout {
if (Qt.openUrlExternally(progressInfo.localPath)) return; if (Qt.openUrlExternally(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) return; if (Qt.openUrlExternally(progressInfo.localDir)) return;
} }
function humanSize(bytes)
{
if (!bytes)
return qsTr("Unknown", "Unknown attachment size")
if (bytes < 4000)
return qsTr("%1 bytes").arg(bytes)
bytes = Math.round(bytes / 100) / 10
if (bytes < 2000)
return qsTr("%1 KB").arg(bytes)
bytes = Math.round(bytes / 100) / 10
if (bytes < 2000)
return qsTr("%1 MB").arg(bytes)
return qsTr("%1 GB").arg(Math.round(bytes / 100) / 10)
}
} }

View File

@ -15,7 +15,7 @@ import Spectral.Effect 2.0
import Spectral.Font 0.1 import Spectral.Font 0.1
ColumnLayout { ColumnLayout {
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other") readonly property bool avatarVisible: !sentByMe && showAuthor
readonly property bool sentByMe: author === currentRoom.localUser readonly property bool sentByMe: author === currentRoom.localUser
property bool openOnFinished: false property bool openOnFinished: false
@ -44,8 +44,6 @@ ColumnLayout {
} }
RowLayout { RowLayout {
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
z: -5 z: -5
id: messageRow id: messageRow
@ -121,17 +119,6 @@ ColumnLayout {
} }
} }
Rectangle {
anchors.fill: parent
color: "transparent"
radius: 24
antialiasing: true
border.width: 4
border.color: MPalette.background
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent

View File

@ -12,7 +12,7 @@ import Spectral.Menu.Timeline 2.0
import Spectral.Effect 2.0 import Spectral.Effect 2.0
RowLayout { RowLayout {
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other") readonly property bool avatarVisible: !sentByMe && showAuthor
readonly property bool sentByMe: author === currentRoom.localUser readonly property bool sentByMe: author === currentRoom.localUser
readonly property bool darkBackground: !sentByMe readonly property bool darkBackground: !sentByMe
readonly property bool replyVisible: replyEventId || false readonly property bool replyVisible: replyEventId || false
@ -68,6 +68,58 @@ RowLayout {
radius: 18 radius: 18
antialiasing: true antialiasing: true
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
width: parent.width / 2
height: parent.height / 2
visible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2)
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 2
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
width: parent.width / 2
height: parent.height / 2
visible: sentByMe && (bubbleShape == 3 || bubbleShape == 2)
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 2
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width / 2
height: parent.height / 2
visible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2)
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 2
}
Rectangle {
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width / 2
height: parent.height / 2
visible: sentByMe && (bubbleShape == 1 || bubbleShape == 2)
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 2
}
AutoMouseArea { AutoMouseArea {
anchors.fill: parent anchors.fill: parent

View File

@ -105,7 +105,7 @@ Item {
id: messageListView id: messageListView
spacing: 4 spacing: 2
displayMarginBeginning: 100 displayMarginBeginning: 100
displayMarginEnd: 100 displayMarginEnd: 100
@ -186,6 +186,8 @@ Item {
DelegateChoice { DelegateChoice {
roleValue: "image" roleValue: "image"
delegate: ImageDelegate { delegate: ImageDelegate {
anchors.right: sentByMe ? parent.right : undefined
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
} }
} }
@ -193,6 +195,8 @@ Item {
DelegateChoice { DelegateChoice {
roleValue: "file" roleValue: "file"
delegate: FileDelegate { delegate: FileDelegate {
anchors.right: sentByMe ? parent.right : undefined
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
} }
} }

View File

@ -18,14 +18,10 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[EventTypeRole] = "eventType"; roles[EventTypeRole] = "eventType";
roles[MessageRole] = "message"; roles[MessageRole] = "message";
roles[AboveEventTypeRole] = "aboveEventType";
roles[EventIdRole] = "eventId"; roles[EventIdRole] = "eventId";
roles[TimeRole] = "time"; roles[TimeRole] = "time";
roles[AboveTimeRole] = "aboveTime";
roles[SectionRole] = "section"; roles[SectionRole] = "section";
roles[AboveSectionRole] = "aboveSection";
roles[AuthorRole] = "author"; roles[AuthorRole] = "author";
roles[AboveAuthorRole] = "aboveAuthor";
roles[ContentRole] = "content"; roles[ContentRole] = "content";
roles[ContentTypeRole] = "contentType"; roles[ContentTypeRole] = "contentType";
roles[HighlightRole] = "highlight"; roles[HighlightRole] = "highlight";
@ -38,6 +34,9 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
roles[ReplyAuthorRole] = "replyAuthor"; roles[ReplyAuthorRole] = "replyAuthor";
roles[ReplyDisplayRole] = "replyDisplay"; roles[ReplyDisplayRole] = "replyDisplay";
roles[UserMarkerRole] = "userMarker"; roles[UserMarkerRole] = "userMarker";
roles[ShowTimestampRole] = "showTimestamp";
roles[ShowAuthorRole] = "showAuthor";
roles[BubbleShapeRole] = "bubbleShape";
return roles; return roles;
} }
@ -84,9 +83,9 @@ void MessageEventModel::setRoom(SpectralRoom* room) {
if (biggest < m_currentRoom->maxTimelineIndex()) { if (biggest < m_currentRoom->maxTimelineIndex()) {
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - auto rowBelowInserted = m_currentRoom->maxTimelineIndex() -
biggest + timelineBaseIndex() - 1; biggest + timelineBaseIndex() - 1;
refreshEventRoles(rowBelowInserted, refreshEventRoles(
{AboveEventTypeRole, AboveAuthorRole, rowBelowInserted,
AboveSectionRole, AboveTimeRole}); {ShowTimestampRole, ShowAuthorRole, BubbleShapeRole});
} }
for (auto i = m_currentRoom->maxTimelineIndex() - biggest; for (auto i = m_currentRoom->maxTimelineIndex() - biggest;
i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
@ -117,8 +116,7 @@ void MessageEventModel::setRoom(SpectralRoom* room) {
refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole});
if (timelineBaseIndex() > 0) // Refresh below, see #312 if (timelineBaseIndex() > 0) // Refresh below, see #312
refreshEventRoles(timelineBaseIndex() - 1, refreshEventRoles(timelineBaseIndex() - 1,
{AboveEventTypeRole, AboveAuthorRole, {ShowTimestampRole, ShowAuthorRole, BubbleShapeRole});
AboveSectionRole, AboveTimeRole});
}); });
connect(m_currentRoom, &Room::pendingEventChanged, this, connect(m_currentRoom, &Room::pendingEventChanged, this,
&MessageEventModel::refreshRow); &MessageEventModel::refreshRow);
@ -417,22 +415,49 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
return {}; return {};
} }
if (role == AboveEventTypeRole || role == AboveSectionRole || if (role == ShowTimestampRole || role == ShowAuthorRole)
role == AboveAuthorRole || role == AboveTimeRole)
for (auto r = row + 1; r < rowCount(); ++r) { for (auto r = row + 1; r < rowCount(); ++r) {
auto i = index(r); auto i = index(r);
if (data(i, SpecialMarksRole) != EventStatus::Hidden) if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
switch (role) { switch (role) {
case AboveEventTypeRole: case ShowTimestampRole:
return data(i, EventTypeRole); return data(i, TimeRole)
case AboveSectionRole: .toDateTime()
return data(i, SectionRole); .msecsTo(data(idx, TimeRole).toDateTime()) > 600000;
case AboveAuthorRole: case ShowAuthorRole:
return data(i, AuthorRole); return data(i, AuthorRole) != data(idx, AuthorRole);
case AboveTimeRole:
return data(i, TimeRole);
} }
} }
}
if (role == BubbleShapeRole) { // TODO: Convoluted logic.
int belowRow = -1; // Invalid
for (auto r = row - 1; r >= 0; --r) {
auto i = index(r);
if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
belowRow = r;
break;
}
}
bool aboveShow, belowShow;
aboveShow = data(idx, ShowAuthorRole).toBool() ||
data(idx, ShowTimestampRole).toBool();
if (belowRow == -1)
belowShow = true;
else
belowShow = data(index(belowRow), ShowAuthorRole).toBool() ||
data(index(belowRow), ShowTimestampRole).toBool();
if (aboveShow && belowShow)
return BubbleShapes::NoShape;
if (aboveShow && !belowShow)
return BubbleShapes::BeginShape;
if (belowShow)
return BubbleShapes::EndShape;
return BubbleShapes::MiddleShape;
}
return {}; return {};
} }

View File

@ -14,14 +14,10 @@ class MessageEventModel : public QAbstractListModel {
enum EventRoles { enum EventRoles {
EventTypeRole = Qt::UserRole + 1, EventTypeRole = Qt::UserRole + 1,
MessageRole, MessageRole,
AboveEventTypeRole,
EventIdRole, EventIdRole,
TimeRole, TimeRole,
AboveTimeRole,
SectionRole, SectionRole,
AboveSectionRole,
AuthorRole, AuthorRole,
AboveAuthorRole,
ContentRole, ContentRole,
ContentTypeRole, ContentTypeRole,
HighlightRole, HighlightRole,
@ -30,13 +26,25 @@ class MessageEventModel : public QAbstractListModel {
LongOperationRole, LongOperationRole,
AnnotationRole, AnnotationRole,
UserMarkerRole, UserMarkerRole,
// For reply
ReplyEventIdRole, ReplyEventIdRole,
ReplyAuthorRole, ReplyAuthorRole,
ReplyDisplayRole, ReplyDisplayRole,
ShowTimestampRole,
ShowAuthorRole,
BubbleShapeRole,
// For debugging // For debugging
EventResolvedTypeRole, EventResolvedTypeRole,
}; };
enum BubbleShapes {
NoShape = 0,
BeginShape,
MiddleShape,
EndShape,
};
explicit MessageEventModel(QObject* parent = nullptr); explicit MessageEventModel(QObject* parent = nullptr);
~MessageEventModel(); ~MessageEventModel();