Switch to manual polishing of event delegates

This commit is contained in:
Nicolas Werner 2023-10-08 20:14:13 +02:00
parent 205a42dade
commit 6c6370c83f
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
7 changed files with 169 additions and 60 deletions

View file

@ -52,8 +52,8 @@ Item {
//onModelChanged: if (room) room.sendReset() //onModelChanged: if (room) room.sendReset()
//reuseItems: true //reuseItems: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
displayMarginBeginning: height / 2 displayMarginBeginning: height / 4
displayMarginEnd: height / 2 displayMarginEnd: height / 4
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
//pixelAligned: true //pixelAligned: true
spacing: 2 spacing: 2

View file

@ -18,7 +18,7 @@ TimelineEvent {
id: wrapper id: wrapper
ListView.delayRemove: true ListView.delayRemove: true
width: chat.delegateMaxWidth width: chat.delegateMaxWidth
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 20)
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
//room: chatRoot.roommodel //room: chatRoot.roommodel
@ -51,6 +51,8 @@ TimelineEvent {
property alias hovered: messageHover.hovered property alias hovered: messageHover.hovered
property bool scrolledToThis: false property bool scrolledToThis: false
maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width
data: [ data: [
Loader { Loader {
id: section id: section
@ -131,7 +133,7 @@ TimelineEvent {
anchors.top: gridContainer.top anchors.top: gridContainer.top
anchors.left: gridContainer.left anchors.left: gridContainer.left
anchors.topMargin: -2 anchors.topMargin: -2
anchors.leftMargin: wrapper.avatarMargin + 2 anchors.leftMargin: -2
color: "transparent" color: "transparent"
border.color: Nheko.theme.red border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
@ -139,11 +141,13 @@ TimelineEvent {
height: contentColumn.implicitHeight + 4 height: contentColumn.implicitHeight + 4
width: contentColumn.implicitWidth + 4 width: contentColumn.implicitWidth + 4
}, },
RowLayout { Row {
id: gridContainer id: gridContainer
width: wrapper.width width: wrapper.width - wrapper.avatarMargin
x: wrapper.avatarMargin
y: section.visible && section.active ? section.y + section.height : 0 y: section.visible && section.active ? section.y + section.height : 0
spacing: Nheko.paddingSmall
HoverHandler { HoverHandler {
id: messageHover id: messageHover
@ -154,23 +158,20 @@ TimelineEvent {
messageActions.model = wrapper; messageActions.model = wrapper;
messageActions.attached = wrapper; messageActions.attached = wrapper;
messageActions.anchors.bottomMargin = -gridContainer.y messageActions.anchors.bottomMargin = -gridContainer.y
messageActions.anchors.rightMargin = metadata.width
} }
} }
} }
} }
Item {
Layout.preferredWidth: wrapper.avatarMargin
}
AbstractButton { AbstractButton {
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Part of a thread") ToolTip.text: qsTr("Part of a thread")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.fillHeight: true height: contentColumn.height
visible: wrapper.threadId visible: wrapper.threadId
Layout.preferredWidth: 4 width: 4
onClicked: wrapper.room.thread = wrapper.threadId onClicked: wrapper.room.thread = wrapper.threadId
@ -181,17 +182,16 @@ TimelineEvent {
color: TimelineManager.userColor(wrapper.threadId, palette.base) color: TimelineManager.userColor(wrapper.threadId, palette.base)
} }
} }
ColumnLayout { Column {
id: contentColumn id: contentColumn
Layout.fillWidth: true
AbstractButton { AbstractButton {
id: replyRow id: replyRow
visible: wrapper.reply visible: wrapper.reply
Layout.fillWidth: true //Layout.fillWidth: true
Layout.maximumHeight: timelineView.height / 8 //Layout.maximumHeight: timelineView.height / 8
Layout.preferredWidth: replyRowLay.implicitWidth //Layout.preferredWidth: replyRowLay.implicitWidth
Layout.preferredHeight: replyRowLay.implicitHeight //Layout.preferredHeight: replyRowLay.implicitHeight
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
@ -202,32 +202,33 @@ TimelineEvent {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
contentItem: RowLayout { contentItem: Row {
id: replyRowLay id: replyRowLay
anchors.fill: parent spacing: Nheko.paddingSmall
Rectangle { Rectangle {
id: replyLine id: replyLine
Layout.fillHeight: true height: replyCol.height
color: replyRow.userColor color: replyRow.userColor
Layout.preferredWidth: 4 width: 4
} }
ColumnLayout { Column {
spacing: 0 spacing: 0
id: replyCol
AbstractButton { AbstractButton {
id: replyUserButton id: replyUserButton
Layout.fillWidth: true
contentItem: ElidedLabel { contentItem: Label {
id: userName_ id: userName_
fullText: wrapper.reply?.userName ?? '' text: wrapper.reply?.userName ?? ''
color: replyRow.userColor color: replyRow.userColor
textFormat: Text.RichText textFormat: Text.RichText
width: parent.width width: wrapper.maxWidth
elideWidth: width //elideWidth: wrapper.maxWidth
} }
onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
} }
@ -239,7 +240,7 @@ TimelineEvent {
} }
background: Rectangle { background: Rectangle {
width: replyRow.implicitContentWidth //width: replyRow.implicitContentWidth
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
} }
@ -259,19 +260,16 @@ TimelineEvent {
] ]
} }
Item { },
// spacer to fill width if needed
Layout.fillWidth: true
}
RowLayout { RowLayout {
id: metadata id: metadata
property int iconSize: Math.floor(fontMetrics.ascent * scaling) property int iconSize: Math.floor(fontMetrics.ascent * scaling)
property double scaling: Settings.bubbles ? 0.75 : 1 property double scaling: Settings.bubbles ? 0.75 : 1
Layout.alignment: Qt.AlignTop | Qt.AlignRight anchors.right: parent.right
Layout.preferredWidth: implicitWidth y: section.visible && section.active ? section.y + section.height : 0
spacing: 2 spacing: 2
visible: !isStateEvent visible: !isStateEvent
@ -339,8 +337,7 @@ TimelineEvent {
} }
} }
} },
},
Reactions { Reactions {
id: reactionRow id: reactionRow

View file

@ -620,7 +620,6 @@ Item {
roleValue: MtxEvent.Member roleValue: MtxEvent.Member
ColumnLayout { ColumnLayout {
width: parent?.width ?? 100
NoticeMessage { NoticeMessage {
body: formatted body: formatted

View file

@ -4,7 +4,7 @@
import ".." import ".."
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts //import QtQuick.Layouts
import im.nheko import im.nheko
MatrixText { MatrixText {
@ -41,7 +41,10 @@ MatrixText {
}" : "") + // TODO(Nico): Figure out how to support mobile }" : "") + // TODO(Nico): Figure out how to support mobile
"</style> "</style>
" + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>") " + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
Layout.maximumHeight: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight //Layout.maximumHeight: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
//EventDelegateChooser.fillWidth: true
clip: !keepFullText clip: !keepFullText
selectByMouse: !Settings.mobileMode && !isReply selectByMouse: !Settings.mobileMode && !isReply
enabled: !Settings.mobileMode && !isReply enabled: !Settings.mobileMode && !isReply

View file

@ -131,7 +131,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
roleToPropIdx.insert(*role, i); roleToPropIdx.insert(*role, i);
roles.emplace_back(*role); roles.emplace_back(*role);
nhlog::ui()->critical("Found prop {}, idx {}, role {}", prop.name(), i, *role); // nhlog::ui()->critical("Found prop {}, idx {}, role {}", prop.name(), i, *role);
} else { } else {
nhlog::ui()->critical("Required property {} not found in model!", prop.name()); nhlog::ui()->critical("Required property {} not found in model!", prop.name());
} }
@ -140,14 +140,15 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
nhlog::ui()->debug("Querying data for id {}", currentId.toStdString()); nhlog::ui()->debug("Querying data for id {}", currentId.toStdString());
chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles); chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles);
Qt::beginPropertyUpdateGroup();
for (const auto &role : roles) { for (const auto &role : roles) {
const auto &roleName = roleNames[role.role()]; const auto &roleName = roleNames[role.role()];
nhlog::ui()->critical("Setting role {}, {} to {}", // nhlog::ui()->critical("Setting role {}, {} to {}",
role.role(), // role.role(),
roleName.toStdString(), // roleName.toStdString(),
role.data().toString().toStdString()); // role.data().toString().toStdString());
nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[role.role()]).name()); // nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[role.role()]).name());
mo->property(roleToPropIdx[role.role()]).write(obj, role.data()); mo->property(roleToPropIdx[role.role()]).write(obj, role.data());
if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end())
@ -156,14 +157,15 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
if (isReplyNeeded) { if (isReplyNeeded) {
const auto roleName = QByteArray("isReply"); const auto roleName = QByteArray("isReply");
nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply); // nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply);
nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name()); // nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name());
mo->property(roleToPropIdx[-1]).write(obj, forReply); mo->property(roleToPropIdx[-1]).write(obj, forReply);
if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end())
QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req);
} }
Qt::endPropertyUpdateGroup();
// setInitialProperties(rolesToSet); // setInitialProperties(rolesToSet);
@ -188,9 +190,12 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
auto mo = obj->metaObject(); auto mo = obj->metaObject();
chooser.room_->multiData( chooser.room_->multiData(
currentId, forReply ? chooser.eventId_ : QString(), rolesToRequest); currentId, forReply ? chooser.eventId_ : QString(), rolesToRequest);
Qt::beginPropertyUpdateGroup();
for (const auto &role : rolesToRequest) { for (const auto &role : rolesToRequest) {
mo->property(roleToPropIdx[role.role()]).write(obj, role.data()); mo->property(roleToPropIdx[role.role()]).write(obj, role.data());
} }
Qt::endPropertyUpdateGroup();
}; };
if (!forReply) { if (!forReply) {
@ -257,11 +262,22 @@ EventDelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status sta
child->setParentItem(&chooser); child->setParentItem(&chooser);
QQmlEngine::setObjectOwnership(child, QQmlEngine::ObjectOwnership::JavaScriptOwnership); QQmlEngine::setObjectOwnership(child, QQmlEngine::ObjectOwnership::JavaScriptOwnership);
// connect(child, &QQuickItem::parentChanged, child, [child](QQuickItem *) {
// // QTBUG-115687
// if (child->flags().testFlag(QQuickItem::ItemObservesViewport)) {
// nhlog::ui()->critical("SETTING OBSERVES VIEWPORT");
// // Re-trigger the parent traversal to get subtreeTransformChangedEnabled turned
// on child->setFlag(QQuickItem::ItemObservesViewport);
// }
// });
if (forReply) if (forReply)
emit chooser.replyChanged(); emit chooser.replyChanged();
else else
emit chooser.mainChanged(); emit chooser.mainChanged();
chooser.polish();
} else if (status == QQmlIncubator::Error) { } else if (status == QQmlIncubator::Error) {
auto errors_ = errors(); auto errors_ = errors();
for (const auto &e : qAsConst(errors_)) for (const auto &e : qAsConst(errors_))
@ -269,3 +285,49 @@ EventDelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status sta
} }
} }
void
EventDelegateChooser::updatePolish()
{
auto mainChild = qobject_cast<QQuickItem *>(eventIncubator.object());
auto replyChild = qobject_cast<QQuickItem *>(replyIncubator.object());
nhlog::ui()->critical("POLISHING {}", (void *)this);
if (mainChild) {
auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
qmlAttachedPropertiesObject<EventDelegateChooser>(mainChild));
Q_ASSERT(attached != nullptr);
// in theory we could also reset the width, but that doesn't seem to work nicely for text
// areas because of how they cache it.
mainChild->setWidth(maxWidth_);
mainChild->ensurePolished();
auto width = mainChild->implicitWidth();
if (width > maxWidth_ || attached->fillWidth())
width = maxWidth_;
nhlog::ui()->debug(
"Made event delegate width: {}, {}", width, mainChild->metaObject()->className());
mainChild->setWidth(width);
mainChild->ensurePolished();
}
if (replyChild) {
auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
qmlAttachedPropertiesObject<EventDelegateChooser>(replyChild));
Q_ASSERT(attached != nullptr);
// in theory we could also reset the width, but that doesn't seem to work nicely for text
// areas because of how they cache it.
replyChild->setWidth(maxWidth_);
replyChild->ensurePolished();
auto width = replyChild->implicitWidth();
if (width > maxWidth_ || attached->fillWidth())
width = maxWidth_;
replyChild->setWidth(width);
replyChild->ensurePolished();
}
}

View file

@ -2,9 +2,6 @@
// //
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// A DelegateChooser like the one, that was added to Qt5.12 (in labs), but compatible with older Qt
// versions see KDE/kquickitemviews see qtdeclarative/qqmldelagatecomponent
#pragma once #pragma once
#include <QAbstractItemModel> #include <QAbstractItemModel>
@ -17,6 +14,32 @@
#include "TimelineModel.h" #include "TimelineModel.h"
class EventDelegateChooserAttachedType : public QObject
{
Q_OBJECT
Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged)
QML_ANONYMOUS
public:
EventDelegateChooserAttachedType(QObject *parent)
: QObject(parent)
{
}
bool fillWidth() const { return fillWidth_; }
void setFillWidth(bool fill)
{
fillWidth_ = fill;
emit fillWidthChanged();
}
signals:
void fillWidthChanged();
private:
bool fillWidth_ = false, keepAspectRatio = false;
double aspectRatio = 1.;
int maxWidth = -1;
int maxHeight = -1;
};
class EventDelegateChoice : public QObject class EventDelegateChoice : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -51,14 +74,18 @@ class EventDelegateChooser : public QQuickItem
QML_ELEMENT QML_ELEMENT
Q_CLASSINFO("DefaultProperty", "choices") Q_CLASSINFO("DefaultProperty", "choices")
public: QML_ATTACHED(EventDelegateChooserAttachedType)
Q_PROPERTY(QQmlListProperty<EventDelegateChoice> choices READ choices CONSTANT FINAL) Q_PROPERTY(QQmlListProperty<EventDelegateChoice> choices READ choices CONSTANT FINAL)
Q_PROPERTY(QQuickItem *main READ main NOTIFY mainChanged FINAL) Q_PROPERTY(QQuickItem *main READ main NOTIFY mainChanged FINAL)
Q_PROPERTY(QQuickItem *reply READ reply NOTIFY replyChanged FINAL) Q_PROPERTY(QQuickItem *reply READ reply NOTIFY replyChanged FINAL)
Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged REQUIRED FINAL) Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged REQUIRED FINAL)
Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL) Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL)
Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL) Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL)
Q_PROPERTY(bool sameWidth READ sameWidth WRITE setSameWidth NOTIFY sameWidthChanged)
Q_PROPERTY(int maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged)
public:
QQmlListProperty<EventDelegateChoice> choices(); QQmlListProperty<EventDelegateChoice> choices();
[[nodiscard]] QQuickItem *main() const [[nodiscard]] QQuickItem *main() const
@ -70,6 +97,20 @@ public:
return qobject_cast<QQuickItem *>(replyIncubator.object()); return qobject_cast<QQuickItem *>(replyIncubator.object());
} }
bool sameWidth() const { return sameWidth_; }
void setSameWidth(bool width)
{
sameWidth_ = width;
emit sameWidthChanged();
}
bool maxWidth() const { return maxWidth_; }
void setMaxWidth(int width)
{
maxWidth_ = width;
emit maxWidthChanged();
polish();
}
void setRoom(TimelineModel *m) void setRoom(TimelineModel *m)
{ {
if (m != room_) { if (m != room_) {
@ -105,12 +146,21 @@ public:
void componentComplete() override; void componentComplete() override;
static EventDelegateChooserAttachedType *qmlAttachedProperties(QObject *object)
{
return new EventDelegateChooserAttachedType(object);
}
void updatePolish() override;
signals: signals:
void mainChanged(); void mainChanged();
void replyChanged(); void replyChanged();
void roomChanged(); void roomChanged();
void eventIdChanged(); void eventIdChanged();
void replyToChanged(); void replyToChanged();
void sameWidthChanged();
void maxWidthChanged();
private: private:
struct DelegateIncubator final : public QQmlIncubator struct DelegateIncubator final : public QQmlIncubator
@ -142,6 +192,8 @@ private:
TimelineModel *room_{nullptr}; TimelineModel *room_{nullptr};
QString eventId_; QString eventId_;
QString replyId; QString replyId;
bool sameWidth_ = false;
int maxWidth_ = 400;
static void appendChoice(QQmlListProperty<EventDelegateChoice> *, EventDelegateChoice *); static void appendChoice(QQmlListProperty<EventDelegateChoice> *, EventDelegateChoice *);
static qsizetype choiceCount(QQmlListProperty<EventDelegateChoice> *); static qsizetype choiceCount(QQmlListProperty<EventDelegateChoice> *);

View file

@ -601,12 +601,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
case UserName: case UserName:
return QVariant(displayName(QString::fromStdString(acc::sender(event)))); return QVariant(displayName(QString::fromStdString(acc::sender(event))));
case UserPowerlevel: { case UserPowerlevel: {
return static_cast<qlonglong>(mtx::events::state::PowerLevels{ return static_cast<qlonglong>(
cache::client() permissions_.powerlevelEvent().user_level(acc::sender(event)));
->getStateEvent<mtx::events::state::PowerLevels>(room_id_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content}
.user_level(acc::sender(event)));
} }
case Day: { case Day: {