Fix hover handling in the timeline

This commit is contained in:
Nicolas Werner 2021-02-14 01:28:28 +01:00
parent 0d61f4bff1
commit d43607d01c
16 changed files with 127 additions and 67 deletions

View file

@ -281,6 +281,7 @@ set(SRC_FILES
src/ui/InfoMessage.cpp src/ui/InfoMessage.cpp
src/ui/Label.cpp src/ui/Label.cpp
src/ui/LoadingIndicator.cpp src/ui/LoadingIndicator.cpp
src/ui/NhekoCursorShape.cpp
src/ui/NhekoDropArea.cpp src/ui/NhekoDropArea.cpp
src/ui/OverlayModal.cpp src/ui/OverlayModal.cpp
src/ui/OverlayWidget.cpp src/ui/OverlayWidget.cpp
@ -495,6 +496,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/ui/Label.h src/ui/Label.h
src/ui/FloatingButton.h src/ui/FloatingButton.h
src/ui/Menu.h src/ui/Menu.h
src/ui/NhekoCursorShape.h
src/ui/NhekoDropArea.h src/ui/NhekoDropArea.h
src/ui/OverlayWidget.h src/ui/OverlayWidget.h
src/ui/SnackBar.h src/ui/SnackBar.h

View file

@ -116,9 +116,7 @@ brew install --cask nheko
### Build Requirements ### Build Requirements
- Qt5 (5.10 or greater). Qt 5.7 adds support for color font rendering with - Qt5 (5.12 or greater). Required for overlapping hover handlers in Qml.
Freetype, which is essential to properly support emoji, 5.8 adds some features
to make interopability with Qml easier, 5.10 makes sliders actually visible with different palettes.
- CMake 3.15 or greater. (Lower version may work, but may break boost linking) - CMake 3.15 or greater. (Lower version may work, but may break boost linking)
- [mtxclient](https://github.com/Nheko-Reborn/mtxclient) - [mtxclient](https://github.com/Nheko-Reborn/mtxclient)
- [LMDB](https://symas.com/lightning-memory-mapped-database/) - [LMDB](https://symas.com/lightning-memory-mapped-database/)

View file

@ -90,4 +90,9 @@ Rectangle {
} }
} }
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
} }

View file

@ -1,4 +1,4 @@
import QtQuick 2.5 import QtQuick 2.12
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import im.nheko 1.0 import im.nheko 1.0
@ -24,14 +24,11 @@ Rectangle {
color: "transparent" color: "transparent"
width: 16 width: 16
height: 16 height: 16
ToolTip.visible: ma.containsMouse && indicator.visible ToolTip.visible: ma.hovered && indicator.visible
ToolTip.text: getEncryptionTooltip() ToolTip.text: getEncryptionTooltip()
MouseArea { HoverHandler {
id: ma id: ma
anchors.fill: parent
hoverEnabled: true
} }
Image { Image {

View file

@ -1,6 +1,7 @@
import "./ui" import "./ui"
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko 1.0 // for cursor shape
AbstractButton { AbstractButton {
id: button id: button
@ -23,11 +24,10 @@ AbstractButton {
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : "" source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
} }
MouseArea { CursorShape {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }

View file

@ -8,6 +8,7 @@ TextEdit {
focus: false focus: false
wrapMode: Text.Wrap wrapMode: Text.Wrap
selectByMouse: !Settings.mobileMode selectByMouse: !Settings.mobileMode
enabled: selectByMouse
color: colors.text color: colors.text
onLinkActivated: { onLinkActivated: {
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) { if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
@ -25,12 +26,9 @@ TextEdit {
ToolTip.visible: hoveredLink ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink ToolTip.text: hoveredLink
MouseArea { CursorShape {
id: ma
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.NoButton cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }

View file

@ -1,6 +1,6 @@
import "./delegates" import "./delegates"
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.9 import QtQuick 2.12
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
@ -153,12 +153,15 @@ ScrollView {
color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window) color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window)
textFormat: Text.RichText textFormat: Text.RichText
MouseArea { TapHandler {
//cursorShape: Qt.PointingHandCursor
onSingleTapped: chat.model.openUserProfile(modelData.userId)
}
CursorShape {
anchors.fill: parent anchors.fill: parent
Layout.alignment: Qt.AlignHCenter
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
} }
} }

View file

@ -1,6 +1,6 @@
import "./delegates" import "./delegates"
import "./emoji" import "./emoji"
import QtQuick 2.6 import QtQuick 2.12
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
@ -12,27 +12,23 @@ Item {
height: row.height height: row.height
Rectangle { Rectangle {
color: (Settings.messageHoverHighlight && hoverHandler.containsMouse) ? colors.alternateBase : "transparent" color: (Settings.messageHoverHighlight && hoverHandler.hovered) ? colors.alternateBase : "transparent"
anchors.fill: row anchors.fill: row
} }
MouseArea { HoverHandler {
id: hoverHandler id: hoverHandler
anchors.fill: parent acceptedDevices: PointerDevice.GenericPointer
propagateComposedEvents: true
preventStealing: false
hoverEnabled: true
acceptedButtons: Qt.AllButtons
onClicked: {
if (mouse.button === Qt.RightButton)
messageContextMenu.show(model.id, model.type, model.isEncrypted, model.isEditable, row);
else
mouse.accepted = false;
} }
onPressAndHold: {
messageContextMenu.show(model.id, model.type, model.isEncrypted, model.isEditable, row, mapToItem(timelineRoot, mouse.x, mouse.y)); TapHandler {
acceptedButtons: Qt.RightButton
onSingleTapped: messageContextMenu.show(model.id, model.type, model.isEncrypted, model.isEditable, row, mapToItem(timelineRoot, eventPoint.position.x, eventPoint.position.y))
} }
TapHandler {
onLongPressed: messageContextMenu.show(model.id, model.type, model.isEncrypted, model.isEditable, row, mapToItem(timelineRoot, point.position.x, point.position.y))
} }
RowLayout { RowLayout {
@ -151,15 +147,11 @@ Item {
text: model.timestamp.toLocaleTimeString("HH:mm") text: model.timestamp.toLocaleTimeString("HH:mm")
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth) width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: inactiveColors.text color: inactiveColors.text
ToolTip.visible: ma.containsMouse ToolTip.visible: ma.hovered
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
MouseArea { HoverHandler {
id: ma id: ma
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
} }
} }

View file

@ -1,4 +1,4 @@
import QtQuick 2.6 import QtQuick 2.12
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
@ -31,7 +31,15 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.timeline.saveMedia(model.data.id) cursorShape: Qt.PointingHandCursor
}
TapHandler {
onSingleTapped: TimelineManager.timeline.saveMedia(model.data.id)
}
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }

View file

@ -1,4 +1,4 @@
import QtQuick 2.6 import QtQuick 2.12
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
@ -32,20 +32,20 @@ Item {
smooth: true smooth: true
mipmap: true mipmap: true
MouseArea { TapHandler {
id: mouseArea
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
hoverEnabled: true onSingleTapped: TimelineManager.openImageOverlay(model.data.url, model.data.id)
anchors.fill: parent }
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
HoverHandler {
id: mouseArea
} }
Item { Item {
id: overlay id: overlay
anchors.fill: parent anchors.fill: parent
visible: mouseArea.containsMouse visible: mouseArea.hovered
Rectangle { Rectangle {
id: container id: container

View file

@ -1,5 +1,5 @@
import QtMultimedia 5.6 import QtMultimedia 5.6
import QtQuick 2.6 import QtQuick 2.12
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
@ -140,9 +140,8 @@ Rectangle {
fillMode: Image.Pad fillMode: Image.Pad
} }
MouseArea { TapHandler {
anchors.fill: parent onSingleTapped: {
onClicked: {
switch (button.state) { switch (button.state) {
case "": case "":
TimelineManager.timeline.cacheMedia(model.data.id); TimelineManager.timeline.cacheMedia(model.data.id);
@ -159,6 +158,10 @@ Rectangle {
break; break;
} }
} }
}
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }

View file

@ -1,4 +1,4 @@
import QtQuick 2.6 import QtQuick 2.12
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
@ -13,10 +13,12 @@ Item {
width: parent.width width: parent.width
height: replyContainer.height height: replyContainer.height
MouseArea { TapHandler {
onSingleTapped: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain)
}
CursorShape {
anchors.fill: parent anchors.fill: parent
preventStealing: false
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
@ -43,10 +45,8 @@ Item {
color: replyComponent.userColor color: replyComponent.userColor
textFormat: Text.RichText textFormat: Text.RichText
MouseArea { TapHandler {
anchors.fill: parent onSingleTapped: chat.model.openUserProfile(reply.modelData.userId)
onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor
} }
} }

View file

@ -8,5 +8,6 @@ MatrixText {
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
clip: isReply clip: isReply
selectByMouse: !Settings.mobileMode && !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
} }

View file

@ -21,6 +21,7 @@
#include "dialogs/ImageOverlay.h" #include "dialogs/ImageOverlay.h"
#include "emoji/EmojiModel.h" #include "emoji/EmojiModel.h"
#include "emoji/Provider.h" #include "emoji/Provider.h"
#include "ui/NhekoCursorShape.h"
#include "ui/NhekoDropArea.h" #include "ui/NhekoDropArea.h"
#include <iostream> //only for debugging #include <iostream> //only for debugging
@ -118,6 +119,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea"); qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
qmlRegisterUncreatableType<DeviceVerificationFlow>( qmlRegisterUncreatableType<DeviceVerificationFlow>(
"im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!"); "im.nheko", 1, 0, "DeviceVerificationFlow", "Can't create verification flow from QML!");
qmlRegisterUncreatableType<UserProfile>( qmlRegisterUncreatableType<UserProfile>(

View file

@ -0,0 +1,25 @@
#include "NhekoCursorShape.h"
#include <QCursor>
NhekoCursorShape::NhekoCursorShape(QQuickItem *parent)
: QQuickItem(parent)
, currentShape_(Qt::CursorShape::ArrowCursor)
{}
Qt::CursorShape
NhekoCursorShape::cursorShape() const
{
return cursor().shape();
}
void
NhekoCursorShape::setCursorShape(Qt::CursorShape cursorShape)
{
if (currentShape_ == cursorShape)
return;
currentShape_ = cursorShape;
setCursor(cursorShape);
emit cursorShapeChanged();
}

26
src/ui/NhekoCursorShape.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
// see
// https://stackoverflow.com/questions/27821054/how-to-change-cursor-shape-in-qml-when-mousearea-is-covered-with-another-mousear/29382092#29382092
#include <QQuickItem>
class NhekoCursorShape : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape NOTIFY
cursorShapeChanged)
public:
explicit NhekoCursorShape(QQuickItem *parent = 0);
private:
Qt::CursorShape cursorShape() const;
void setCursorShape(Qt::CursorShape cursorShape);
Qt::CursorShape currentShape_;
signals:
void cursorShapeChanged();
};