diff --git a/resources/qml/ImageButton.qml b/resources/qml/ImageButton.qml
index dd100503..dd67d597 100644
--- a/resources/qml/ImageButton.qml
+++ b/resources/qml/ImageButton.qml
@@ -3,7 +3,8 @@ import QtQuick.Controls 2.3
AbstractButton {
property string image: undefined
-
+ width: 16
+ height: 16
id: button
Image {
diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml
index f42e8612..c06dc826 100644
--- a/resources/qml/Reactions.qml
+++ b/resources/qml/Reactions.qml
@@ -1,7 +1,19 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
+// This class is for showing Reactions in the timeline row, not for
+// adding new reactions via the emoji picker
Flow {
+ id: reactionFlow
+
+ // highlight colors for selfReactedEvent background
+ property real highlightHue: colors.highlight.hslHue
+ property real highlightSat: colors.highlight.hslSaturation
+ property real highlightLight: colors.highlight.hslLightness
+
+ property string eventId
+ property string roomId
+
anchors.left: parent.left
anchors.right: parent.right
spacing: 4
@@ -11,9 +23,8 @@ Flow {
Repeater {
id: repeater
- AbstractButton {
+ delegate: AbstractButton {
id: reaction
- text: model.key
hoverEnabled: true
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding*2
implicitHeight: contentItem.childrenRect.height
@@ -21,6 +32,11 @@ Flow {
ToolTip.visible: hovered
ToolTip.text: model.users
+ onClicked: {
+ console.debug("Picked " + model.key + "in response to " + reactionFlow.eventId + " in room " + reactionFlow.roomId + ". selfReactedEvent: " + model.selfReactedEvent)
+ timelineManager.reactToMessage(reactionFlow.roomId, reactionFlow.eventId, model.key, model.selfReactedEvent)
+ }
+
contentItem: Row {
anchors.centerIn: parent
@@ -33,13 +49,13 @@ Flow {
font.family: settings.emojiFont
elide: Text.ElideRight
elideWidth: 150
- text: reaction.text
+ text: model.key
}
Text {
anchors.baseline: reactionCounter.baseline
id: reactionText
- text: textMetrics.elidedText + (textMetrics.elidedText == textMetrics.text ? "" : "…")
+ text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…")
font.family: settings.emojiFont
color: reaction.hovered ? colors.highlight : colors.text
maximumLineCount: 1
@@ -49,7 +65,7 @@ Flow {
id: divider
height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1
- color: reaction.hovered ? colors.highlight : colors.text
+ color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text
}
Text {
@@ -63,10 +79,11 @@ Flow {
background: Rectangle {
anchors.centerIn: parent
+
implicitWidth: reaction.implicitWidth
- height: reaction.implicitHeight
- border.color: (reaction.hovered || model.selfReacted )? colors.highlight : colors.text
- color: colors.base
+ implicitHeight: reaction.implicitHeight
+ border.color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text
+ color: model.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : colors.base
border.width: 1
radius: reaction.height / 2.0
}
diff --git a/resources/qml/ScrollHelper.qml b/resources/qml/ScrollHelper.qml
index cdb4a23a..30bf9ac2 100644
--- a/resources/qml/ScrollHelper.qml
+++ b/resources/qml/ScrollHelper.qml
@@ -71,7 +71,7 @@ MouseArea {
pixelDelta = wheel.pixelDelta.y
}
- pixelDelta = Math.round(pixelDelta)
+ pixelDelta = Math.round(pixelDelta)
if (!pixelDelta) {
return flickableItem.contentY;
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 528dce85..dfee62dc 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -6,6 +6,7 @@ import QtQuick.Window 2.2
import im.nheko 1.0
import "./delegates"
+import "./emoji"
MouseArea {
anchors.left: parent.left
@@ -58,7 +59,10 @@ MouseArea {
}
Reactions {
+ id: reactionRow
reactions: model.reactions
+ roomId: model.roomId
+ eventId: model.id
}
}
@@ -76,7 +80,19 @@ MouseArea {
Layout.preferredHeight: 16
width: 16
}
-
+ EmojiButton {
+ visible: settings.buttonsInTimeline
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredHeight: 16
+ width: 16
+ id: reactButton
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("React")
+ emojiPicker: emojiPopup
+ room_id: model.roomId
+ event_id: model.id
+ }
ImageButton {
visible: settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index ea6bf093..f3d5d219 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -5,14 +5,19 @@ import QtGraphicalEffects 1.0
import QtQuick.Window 2.2
import im.nheko 1.0
+import im.nheko.EmojiModel 1.0
import "./delegates"
+import "./emoji"
Page {
property var colors: currentActivePalette
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40
+ property real highlightHue: colors.highlight.hslHue
+ property real highlightSat: colors.highlight.hslSaturation
+ property real highlightLight: colors.highlight.hslLightness
palette: colors
@@ -20,6 +25,17 @@ Page {
id: fontMetrics
}
+ EmojiPicker {
+ id: emojiPopup
+ width: 7 * 52 + 20
+ height: 6 * 52
+ colors: palette
+ model: EmojiProxyModel {
+ category: EmojiCategory.People
+ sourceModel: EmojiModel {}
+ }
+ }
+
Menu {
id: messageContextMenu
modal: true
@@ -34,7 +50,10 @@ Page {
property string eventId
property int eventType
property bool isEncrypted
-
+ MenuItem {
+ text: qsTr("React")
+ onClicked: chat.model.reactAction(messageContextMenu.eventId)
+ }
MenuItem {
text: qsTr("Reply")
onClicked: chat.model.replyAction(messageContextMenu.eventId)
diff --git a/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml
index dbf0e5f6..27985b58 100644
--- a/resources/qml/delegates/Pill.qml
+++ b/resources/qml/delegates/Pill.qml
@@ -11,5 +11,4 @@ Label {
radius: parent.height / 2
color: colors.dark
}
-
}
diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml
new file mode 100644
index 00000000..f8f75e3e
--- /dev/null
+++ b/resources/qml/emoji/EmojiButton.qml
@@ -0,0 +1,18 @@
+import QtQuick 2.10
+import QtQuick.Controls 2.1
+import im.nheko 1.0
+import im.nheko.EmojiModel 1.0
+
+import "../"
+
+ImageButton {
+ property var colors: currentActivePalette
+ property var emojiPicker
+ property string room_id
+ property string event_id
+
+ image: ":/icons/icons/ui/smile.png"
+ id: emojiButton
+ onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, room_id, event_id)
+
+}
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
new file mode 100644
index 00000000..b70923ae
--- /dev/null
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -0,0 +1,290 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.9
+import QtQuick.Layouts 1.3
+import QtGraphicalEffects 1.9
+
+import im.nheko 1.0
+import im.nheko.EmojiModel 1.0
+
+import "../"
+
+Popup {
+
+ function show(showAt, room_id, event_id) {
+ console.debug("Showing emojiPicker for " + event_id + "in room " + room_id)
+ parent = showAt
+ x = Math.round((showAt.width - width) / 2)
+ y = showAt.height
+ emojiPopup.room_id = room_id
+ emojiPopup.event_id = event_id
+ open()
+ }
+
+ property string room_id
+ property string event_id
+ property var colors
+ property alias model: gridView.model
+ property var textArea
+ property string emojiCategory: "people"
+ property real highlightHue: colors.highlight.hslHue
+ property real highlightSat: colors.highlight.hslSaturation
+ property real highlightLight: colors.highlight.hslLightness
+
+ id: emojiPopup
+
+ margins: 0
+ bottomPadding: 1
+ leftPadding: 1
+ rightPadding: 1
+
+ modal: true
+ focus: true
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+
+ ColumnLayout {
+ id: columnView
+ anchors.fill: parent
+ spacing: 0
+ Layout.bottomMargin: 0
+ Layout.leftMargin: 3
+ Layout.rightMargin: 3
+ Layout.topMargin: 2
+
+ // emoji grid
+ GridView {
+ id: gridView
+
+ Layout.preferredHeight: emojiPopup.height
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.leftMargin: 4
+
+ cellWidth: 52
+ cellHeight: 52
+
+ boundsBehavior: Flickable.StopAtBounds
+
+ clip: true
+
+ // Individual emoji
+ delegate: AbstractButton {
+ width: 48
+ height: 48
+ contentItem: Text {
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.family: settings.emojiFont
+
+ font.pixelSize: 36
+ text: model.unicode
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: hovered ? colors.highlight : 'transparent'
+ radius: 5
+ }
+
+ hoverEnabled: true
+ ToolTip.text: model.shortName
+ ToolTip.visible: hovered
+
+ // give the emoji a little oomf
+ DropShadow {
+ width: parent.width;
+ height: parent.height;
+ horizontalOffset: 3
+ verticalOffset: 3
+ radius: 8.0
+ samples: 17
+ color: "#80000000"
+ source: parent.contentItem
+ }
+ // TODO: maybe add favorites at some point?
+ onClicked: {
+ console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id + " in room " + emojiPopup.room_id)
+ emojiPopup.close()
+ timelineManager.queueReactionMessage(emojiPopup.room_id, emojiPopup.event_id, model.unicode)
+ }
+ }
+
+ // Search field
+ header: TextField {
+ id: emojiSearch
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: emojiScroll.width + 4
+ placeholderText: qsTr("Search")
+ selectByMouse: true
+ rightPadding: clearSearch.width
+
+ Timer {
+ id: searchTimer
+ interval: 350 // tweak as needed?
+ onTriggered: {
+ emojiPopup.model.filter = emojiSearch.text
+ emojiPopup.model.category = EmojiCategory.Search
+ }
+ }
+
+ ToolButton {
+ id: clearSearch
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ }
+ // clear the default hover effects.
+ background: Item {}
+ visible: emojiSearch.text !== ''
+ icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
+ focusPolicy: Qt.NoFocus
+ onClicked: emojiSearch.clear()
+ }
+
+ onTextChanged: searchTimer.restart()
+ onVisibleChanged: if (visible) forceActiveFocus()
+ }
+
+ ScrollBar.vertical: ScrollBar {
+ id: emojiScroll
+ }
+ }
+
+ // Separator
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+
+ color: emojiPopup.colors.dark
+ }
+
+ // Category picker row
+ RowLayout {
+ Layout.bottomMargin: 0
+ Layout.preferredHeight: 42
+ implicitHeight: 42
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ // Display the normal categories
+ Repeater {
+ model: ListModel {
+ // TODO: Would like to get 'simple' icons for the categories
+ ListElement { image: ":/icons/icons/emoji-categories/people.png"; category: EmojiCategory.People }
+ ListElement { image: ":/icons/icons/emoji-categories/nature.png"; category: EmojiCategory.Nature }
+ ListElement { image: ":/icons/icons/emoji-categories/foods.png"; category: EmojiCategory.Food }
+ ListElement { image: ":/icons/icons/emoji-categories/activity.png"; category: EmojiCategory.Activity }
+ ListElement { image: ":/icons/icons/emoji-categories/travel.png"; category: EmojiCategory.Travel }
+ ListElement { image: ":/icons/icons/emoji-categories/objects.png"; category: EmojiCategory.Objects }
+ ListElement { image: ":/icons/icons/emoji-categories/symbols.png"; category: EmojiCategory.Symbols }
+ ListElement { image: ":/icons/icons/emoji-categories/flags.png"; category: EmojiCategory.Flags }
+ }
+
+ delegate: AbstractButton {
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+
+ contentItem: Image {
+ horizontalAlignment: Image.AlignHCenter
+ verticalAlignment: Image.AlignVCenter
+ fillMode: Image.Pad
+ sourceSize.width: 32
+ sourceSize.height: 32
+ source: "image://colorimage/" + model.image + "?" + (hovered ? colors.highlight : colors.buttonText)
+ }
+
+ MouseArea
+ {
+ id: mouseArea
+ anchors.fill: parent
+ onPressed: mouse.accepted = false
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+
+ color: emojiPopup.model.category === model.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.20) : 'transparent'
+ radius: 5
+ border.color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
+ }
+
+ hoverEnabled: true
+ ToolTip.text: {
+ switch (model.category) {
+ case EmojiCategory.People:
+ return qsTr('People');
+ case EmojiCategory.Nature:
+ return qsTr('Nature');
+ case EmojiCategory.Food:
+ return qsTr('Food');
+ case EmojiCategory.Activity:
+ return qsTr('Activity');
+ case EmojiCategory.Travel:
+ return qsTr('Travel');
+ case EmojiCategory.Objects:
+ return qsTr('Objects');
+ case EmojiCategory.Symbols:
+ return qsTr('Symbols');
+ case EmojiCategory.Flags:
+ return qsTr('Flags');
+ }
+ }
+ ToolTip.visible: hovered
+
+ onClicked: {
+ emojiPopup.model.category = model.category
+ }
+ }
+ }
+
+ // Separator
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.preferredWidth: 1
+ implicitWidth: 1
+ height: parent.height
+
+ color: emojiPopup.colors.dark
+ }
+
+ // Search Button is special
+ AbstractButton {
+ id: searchBtn
+ hoverEnabled: true
+ Layout.alignment: Qt.AlignRight
+ Layout.bottomMargin: 0
+
+ ToolTip.text: qsTr("Search")
+ ToolTip.visible: hovered
+ onClicked: {
+ // clear any filters
+ emojiPopup.model.category = EmojiCategory.Search
+ gridView.positionViewAtBeginning()
+ emojiSearch.forceActiveFocus()
+ }
+ Layout.preferredWidth: 36
+ Layout.preferredHeight: 36
+ implicitWidth: 36
+ implicitHeight: 36
+
+ contentItem: Image {
+ anchors.right: parent.right
+ horizontalAlignment: Image.AlignHCenter
+ verticalAlignment: Image.AlignVCenter
+ sourceSize.width: 32
+ sourceSize.height: 32
+ fillMode: Image.Pad
+ smooth: true
+ source: "image://colorimage/:/icons/icons/ui/search.png?" + (parent.hovered ? colors.highlight : colors.buttonText)
+ }
+
+ MouseArea
+ {
+ id: mouseArea
+ anchors.fill: parent
+ onPressed: mouse.accepted = false
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ }
+ }
+}
|