optionally use QQuickWidget and replace ColorOverlay -> colorImageProvider

This commit is contained in:
Nicolas Werner 2019-11-08 14:39:45 +01:00
parent 1268e9f11c
commit 2bfb885b47
12 changed files with 197 additions and 152 deletions

View file

@ -69,7 +69,7 @@ include(LMDB)
# #
# Discover Qt dependencies. # Discover Qt dependencies.
# #
find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 REQUIRED) find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 QuickWidgets REQUIRED)
find_package(Qt5QuickCompiler) find_package(Qt5QuickCompiler)
find_package(Qt5DBus) find_package(Qt5DBus)
@ -234,6 +234,7 @@ set(SRC_FILES
src/MainWindow.cpp src/MainWindow.cpp
src/MatrixClient.cpp src/MatrixClient.cpp
src/MxcImageProvider.cpp src/MxcImageProvider.cpp
src/ColorImageProvider.cpp
src/QuickSwitcher.cpp src/QuickSwitcher.cpp
src/Olm.cpp src/Olm.cpp
src/RegisterPage.cpp src/RegisterPage.cpp
@ -414,6 +415,7 @@ set(COMMON_LIBS
Qt5::Multimedia Qt5::Multimedia
Qt5::Qml Qt5::Qml
Qt5::QuickControls2 Qt5::QuickControls2
Qt5::QuickWidgets
nlohmann_json::nlohmann_json) nlohmann_json::nlohmann_json)
if(APPVEYOR_BUILD) if(APPVEYOR_BUILD)

View file

@ -1,6 +1,5 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtGraphicalEffects 1.0
import com.github.nheko 1.0 import com.github.nheko 1.0
Rectangle { Rectangle {
@ -19,12 +18,7 @@ Rectangle {
Image { Image {
id: stateImg id: stateImg
anchors.fill: parent anchors.fill: parent
source: "qrc:/icons/icons/ui/lock.png" source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
}
ColorOverlay {
anchors.fill: stateImg
source: stateImg
color: colors.buttonText
} }
} }

View file

@ -1,9 +1,8 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtGraphicalEffects 1.0
Button { Button {
property alias image: buttonImg.source property string image: undefined
id: button id: button
@ -17,11 +16,7 @@ Button {
id: buttonImg id: buttonImg
// Workaround, can't get icon.source working for now... // Workaround, can't get icon.source working for now...
anchors.fill: parent anchors.fill: parent
} source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
ColorOverlay {
anchors.fill: buttonImg
source: buttonImg
color: button.hovered ? colors.highlight : colors.buttonText
} }
MouseArea MouseArea

View file

@ -1,6 +1,5 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtGraphicalEffects 1.0
import com.github.nheko 1.0 import com.github.nheko 1.0
Rectangle { Rectangle {
@ -28,18 +27,12 @@ Rectangle {
// Workaround, can't get icon.source working for now... // Workaround, can't get icon.source working for now...
anchors.fill: parent anchors.fill: parent
source: switch (indicator.state) { source: switch (indicator.state) {
case MtxEvent.Failed: return "qrc:/icons/icons/ui/remove-symbol.png" case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText
case MtxEvent.Sent: return "qrc:/icons/icons/ui/clock.png" case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText
case MtxEvent.Received: return "qrc:/icons/icons/ui/checkmark.png" case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText
case MtxEvent.Read: return "qrc:/icons/icons/ui/double-tick-indicator.png" case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText
default: return "" default: return ""
} }
} }
ColorOverlay {
anchors.fill: stateImg
source: stateImg
color: colors.buttonText
visible: stateImg.source != ""
}
} }

View file

@ -50,7 +50,7 @@ RowLayout {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
id: replyButton id: replyButton
image: "qrc:/icons/icons/ui/mail-reply.png" image: ":/icons/icons/ui/mail-reply.png"
ToolTip { ToolTip {
visible: replyButton.hovered visible: replyButton.hovered
text: qsTr("Reply") text: qsTr("Reply")
@ -64,7 +64,7 @@ RowLayout {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
id: optionsButton id: optionsButton
image: "qrc:/icons/icons/ui/vertical-ellipsis.png" image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip { ToolTip {
visible: optionsButton.hovered visible: optionsButton.hovered
text: qsTr("Options") text: qsTr("Options")

View file

@ -8,151 +8,151 @@ import com.github.nheko 1.0
import "./delegates" import "./delegates"
Rectangle { Item {
anchors.fill: parent
property var colors: currentActivePalette property var colors: currentActivePalette
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled } property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40 property int avatarSize: 40
color: colors.window Rectangle {
Text {
visible: !timelineManager.timeline
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: colors.windowText
}
ListView {
id: chat
cacheBuffer: 2000
visible: timelineManager.timeline != null
anchors.fill: parent anchors.fill: parent
color: colors.window
anchors.leftMargin: 4 Text {
anchors.rightMargin: scrollbar.width visible: !timelineManager.timeline
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: colors.windowText
}
model: timelineManager.timeline ListView {
id: chat
onModelChanged: { cacheBuffer: 2000
if (model) {
currentIndex = model.currentIndex visible: timelineManager.timeline != null
if (model.currentIndex == count - 1) { anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: scrollbar.width
model: timelineManager.timeline
onModelChanged: {
if (model) {
currentIndex = model.currentIndex
if (model.currentIndex == count - 1) {
positionViewAtEnd()
} else {
positionViewAtIndex(model.currentIndex, ListView.End)
}
//if (contentHeight < height) {
// model.fetchHistory();
//}
}
}
ScrollBar.vertical: ScrollBar {
id: scrollbar
anchors.top: parent.top
anchors.left: parent.right
anchors.bottom: parent.bottom
onPressedChanged: if (!pressed) chat.updatePosition()
}
property bool atBottom: false
onCountChanged: {
if (atBottom) {
var newIndex = count - 1 // last index
positionViewAtEnd() positionViewAtEnd()
} else { currentIndex = newIndex
positionViewAtIndex(model.currentIndex, ListView.End) model.currentIndex = newIndex
} }
if (contentHeight < height) { if (contentHeight < height && model) {
model.fetchHistory(); model.fetchHistory();
} }
} }
}
ScrollBar.vertical: ScrollBar { onAtYBeginningChanged: if (atYBeginning) model.fetchHistory()
id: scrollbar
anchors.top: parent.top
anchors.left: parent.right
anchors.bottom: parent.bottom
onPressedChanged: if (!pressed) chat.updatePosition()
}
property bool atBottom: false function updatePosition() {
onCountChanged: { for (var y = chat.contentY + chat.height; y > chat.height; y -= 9) {
if (atBottom && Window.active) { var i = chat.itemAt(100, y);
var newIndex = count - 1 // last index if (!i) continue;
positionViewAtEnd() if (!i.isFullyVisible()) continue;
currentIndex = newIndex chat.model.currentIndex = i.getIndex();
model.currentIndex = newIndex chat.currentIndex = i.getIndex()
} atBottom = i.getIndex() == count - 1;
break;
if (contentHeight < height && model) {
model.fetchHistory();
}
}
onAtYBeginningChanged: if (atYBeginning) model.fetchHistory()
function updatePosition() {
for (var y = chat.contentY + chat.height; y > chat.height; y -= 5) {
var i = chat.itemAt(100, y);
if (!i) continue;
if (!i.isFullyVisible()) continue;
chat.model.currentIndex = i.getIndex();
chat.currentIndex = i.getIndex()
atBottom = i.getIndex() == count - 1;
console.log("bottom:" + atBottom)
break;
}
}
onMovementEnded: updatePosition()
spacing: 4
delegate: TimelineRow {
function isFullyVisible() {
return height > 1 && (y - chat.contentY - 1) + height < chat.height
}
function getIndex() {
return index;
}
}
section {
property: "section"
delegate: Column {
topPadding: 4
bottomPadding: 4
spacing: 8
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
anchors.horizontalCenter: parent.horizontalCenter
visible: section.includes(" ")
text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1])))
color: colors.windowText
height: contentHeight * 1.2
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
radius: parent.height / 2
color: colors.dark
}
} }
Row { }
height: userName.height onMovementEnded: updatePosition()
spacing: 4
Avatar {
width: avatarSize
height: avatarSize
url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
displayName: chat.model.displayName(section.split(" ")[0])
MouseArea { spacing: 4
anchors.fill: parent delegate: TimelineRow {
onClicked: chat.model.openUserProfile(section.split(" ")[0]) function isFullyVisible() {
cursorShape: Qt.PointingHandCursor return height > 1 && (y - chat.contentY - 1) + height < chat.height
}
function getIndex() {
return index;
}
}
section {
property: "section"
delegate: Column {
topPadding: 4
bottomPadding: 4
spacing: 8
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
anchors.horizontalCenter: parent.horizontalCenter
visible: section.includes(" ")
text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1])))
color: colors.windowText
height: contentHeight * 1.2
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
radius: parent.height / 2
color: colors.dark
} }
} }
Row {
height: userName.height
spacing: 4
Avatar {
width: avatarSize
height: avatarSize
url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
displayName: chat.model.displayName(section.split(" ")[0])
Text { MouseArea {
id: userName anchors.fill: parent
text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0])) onClicked: chat.model.openUserProfile(section.split(" ")[0])
color: chat.model.userColor(section.split(" ")[0], colors.window) cursorShape: Qt.PointingHandCursor
textFormat: Text.RichText }
}
MouseArea { Text {
anchors.fill: parent id: userName
onClicked: chat.model.openUserProfile(section.split(" ")[0]) text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0]))
cursorShape: Qt.PointingHandCursor color: chat.model.userColor(section.split(" ")[0], colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(section.split(" ")[0])
cursorShape: Qt.PointingHandCursor
}
} }
} }
} }

View file

@ -0,0 +1,30 @@
#include "ColorImageProvider.h"
#include "Logging.h"
#include <QPainter>
QPixmap
ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &)
{
auto args = id.split('?');
nhlog::ui()->info("Loading {}, source is {}", id.toStdString(), args[0].toStdString());
QPixmap source(args[0]);
if (size)
*size = QSize(source.width(), source.height());
if (args.size() < 2)
return source;
QColor color(args[1]);
QPixmap colorized = source;
QPainter painter(&colorized);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.fillRect(colorized.rect(), color);
painter.end();
return colorized;
}

11
src/ColorImageProvider.h Normal file
View file

@ -0,0 +1,11 @@
#include <QQuickImageProvider>
class ColorImageProvider : public QQuickImageProvider
{
public:
ColorImageProvider()
: QQuickImageProvider(QQuickImageProvider::Pixmap)
{}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
};

View file

@ -119,7 +119,6 @@ DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
chooser.child = dynamic_cast<QQuickItem *>(object()); chooser.child = dynamic_cast<QQuickItem *>(object());
if (chooser.child == nullptr) { if (chooser.child == nullptr) {
nhlog::ui()->error("Delegate has to be derived of Item!"); nhlog::ui()->error("Delegate has to be derived of Item!");
delete chooser.child;
return; return;
} }

View file

@ -844,7 +844,7 @@ TimelineModel::replyAction(QString id)
return related_; return related_;
}, },
event); event);
related.type = mtx::events::getMessageType(boost::apply_visitor( related.type = mtx::events::getMessageType(boost::apply_visitor(
[](const auto &e) -> std::string { return eventMsgType(e); }, event)); [](const auto &e) -> std::string { return eventMsgType(e); }, event));
related.quoted_body = boost::apply_visitor( related.quoted_body = boost::apply_visitor(
[](const auto &e) -> QString { return eventFormattedBody(e); }, event); [](const auto &e) -> QString { return eventFormattedBody(e); }, event);

View file

@ -8,6 +8,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include "ChatPage.h" #include "ChatPage.h"
#include "ColorImageProvider.h"
#include "DelegateChooser.h" #include "DelegateChooser.h"
#include "Logging.h" #include "Logging.h"
#include "MxcImageProvider.h" #include "MxcImageProvider.h"
@ -51,6 +52,7 @@ TimelineViewManager::updateColorPalette()
TimelineViewManager::TimelineViewManager(QWidget *parent) TimelineViewManager::TimelineViewManager(QWidget *parent)
: imgProvider(new MxcImageProvider()) : imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
{ {
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
"com.github.nheko", "com.github.nheko",
@ -61,12 +63,24 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
qmlRegisterType<DelegateChoice>("com.github.nheko", 1, 0, "DelegateChoice"); qmlRegisterType<DelegateChoice>("com.github.nheko", 1, 0, "DelegateChoice");
qmlRegisterType<DelegateChooser>("com.github.nheko", 1, 0, "DelegateChooser"); qmlRegisterType<DelegateChooser>("com.github.nheko", 1, 0, "DelegateChooser");
#ifdef USE_QUICK_VIEW
view = new QQuickView(); view = new QQuickView();
container = QWidget::createWindowContainer(view, parent); container = QWidget::createWindowContainer(view, parent);
#else
view = new QQuickWidget(parent);
container = view;
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
nhlog::ui()->debug("Status changed to {}", status);
});
#endif
container->setMinimumSize(200, 200); container->setMinimumSize(200, 200);
view->rootContext()->setContextProperty("timelineManager", this); view->rootContext()->setContextProperty("timelineManager", this);
updateColorPalette(); updateColorPalette();
view->engine()->addImageProvider("MxcImage", imgProvider); view->engine()->addImageProvider("MxcImage", imgProvider);
view->engine()->addImageProvider("colorimage", colorImgProvider);
view->setSource(QUrl("qrc:///qml/TimelineView.qml")); view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
connect(dynamic_cast<ChatPage *>(parent), connect(dynamic_cast<ChatPage *>(parent),

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <QQuickView> #include <QQuickView>
#include <QQuickWidget>
#include <QSharedPointer> #include <QSharedPointer>
#include <QWidget> #include <QWidget>
@ -16,6 +17,7 @@
#pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-parameter"
class MxcImageProvider; class MxcImageProvider;
class ColorImageProvider;
class TimelineViewManager : public QObject class TimelineViewManager : public QObject
{ {
@ -99,10 +101,15 @@ public slots:
uint64_t dsize); uint64_t dsize);
private: private:
#ifdef USE_QUICK_VIEW
QQuickView *view; QQuickView *view;
#else
QQuickWidget *view;
#endif
QWidget *container; QWidget *container;
TimelineModel *timeline_ = nullptr; TimelineModel *timeline_ = nullptr;
MxcImageProvider *imgProvider; MxcImageProvider *imgProvider;
ColorImageProvider *colorImgProvider;
QHash<QString, QSharedPointer<TimelineModel>> models; QHash<QString, QSharedPointer<TimelineModel>> models;
}; };