From dd6c10e3821a245bf6486364ce19225e65036564 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 18 Nov 2018 10:47:12 +0800 Subject: [PATCH] Emoji Picker. --- .../Spectral/Component/Emoji/EmojiPicker.qml | 149 ++++--- imports/Spectral/Panel/RoomListPanel.qml | 2 + imports/Spectral/Panel/RoomPanelInput.qml | 419 +++++++++--------- qml/main.qml | 4 +- 4 files changed, 306 insertions(+), 268 deletions(-) diff --git a/imports/Spectral/Component/Emoji/EmojiPicker.qml b/imports/Spectral/Component/Emoji/EmojiPicker.qml index aa64a7be2..c4b9cd4bc 100644 --- a/imports/Spectral/Component/Emoji/EmojiPicker.qml +++ b/imports/Spectral/Component/Emoji/EmojiPicker.qml @@ -3,84 +3,105 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 +import Spectral.Component 2.0 + import Spectral 0.1 +import Spectral.Setting 0.1 -Popup { - property var emojiModel - property var textArea +ColumnLayout { property string emojiCategory: "people" + property var textArea + property var emojiModel - ColumnLayout { - anchors.fill: parent + spacing: 0 - GridView { - Layout.fillWidth: true - Layout.fillHeight: true + ListView { + Layout.fillWidth: true + Layout.preferredHeight: 48 + Layout.leftMargin: 24 + Layout.rightMargin: 24 - cellWidth: 36 - cellHeight: 36 + boundsBehavior: Flickable.DragOverBounds - boundsBehavior: Flickable.DragOverBounds + clip: true - clip: true + orientation: ListView.Horizontal - model: emojiModel.model[emojiCategory] - - delegate: ItemDelegate { - width: 36 - height: 36 - - contentItem: Text { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - font.pointSize: 20 - font.family: "Emoji" - text: modelData.unicode - } - - onClicked: textArea.insert(textArea.cursorPosition, modelData.unicode) - } - - ScrollBar.vertical: ScrollBar {} + model: ListModel { + ListElement { label: "😏"; category: "people" } + ListElement { label: "🌲"; category: "nature" } + ListElement { label: "🍛"; category: "food"} + ListElement { label: "🚁"; category: "activity" } + ListElement { label: "🚅"; category: "travel" } + ListElement { label: "💡"; category: "objects" } + ListElement { label: "🔣"; category: "symbols" } + ListElement { label: "🏁"; category: "flags" } } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 + delegate: ItemDelegate { + width: 64 + height: 48 - color: Material.accent - } + contentItem: Text { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter - Row { - Repeater { - model: ListModel { - ListElement { label: "😏"; category: "people" } - ListElement { label: "🌲"; category: "nature" } - ListElement { label: "🍛"; category: "food"} - ListElement { label: "🚁"; category: "activity" } - ListElement { label: "🚅"; category: "travel" } - ListElement { label: "💡"; category: "objects" } - ListElement { label: "🔣"; category: "symbols" } - ListElement { label: "🏁"; category: "flags" } - } - - delegate: ItemDelegate { - width: 36 - height: 36 - - contentItem: Text { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - font.pointSize: 20 - font.family: "Emoji" - text: label - } - - onClicked: emojiCategory = category - } + font.pointSize: 24 + font.family: "Emoji" + text: label } + + Rectangle { + anchors.bottom: parent.bottom + + width: parent.width + height: 2 + + visible: emojiCategory === category + + color: Material.accent + } + + onClicked: emojiCategory = category } } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + + color: MSettings.darkTheme ? "#424242" : "#e7ebeb" + } + + GridView { + Layout.fillWidth: true + Layout.preferredHeight: 180 + + cellWidth: 48 + cellHeight: 48 + + boundsBehavior: Flickable.DragOverBounds + + clip: true + + model: emojiModel.model[emojiCategory] + + delegate: ItemDelegate { + width: 48 + height: 48 + + contentItem: Text { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.pointSize: 20 + font.family: "Emoji" + text: modelData.unicode + } + + onClicked: textArea.insert(textArea.cursorPosition, modelData.unicode) + } + + ScrollBar.vertical: ScrollBar {} + } } diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml index f1cb284bb..c35beb9e3 100644 --- a/imports/Spectral/Panel/RoomListPanel.qml +++ b/imports/Spectral/Panel/RoomListPanel.qml @@ -151,6 +151,8 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ColumnLayout { diff --git a/imports/Spectral/Panel/RoomPanelInput.qml b/imports/Spectral/Panel/RoomPanelInput.qml index 2450b4889..30e839f29 100644 --- a/imports/Spectral/Panel/RoomPanelInput.qml +++ b/imports/Spectral/Panel/RoomPanelInput.qml @@ -23,6 +23,8 @@ Control { property int autoCompleteBeginPosition property int autoCompleteEndPosition + id: root + padding: 0 background: Rectangle { @@ -105,228 +107,241 @@ Control { } } - contentItem: RowLayout { + contentItem: Column { spacing: 0 - ToolButton { - Layout.alignment: Qt.AlignTop - - id: uploadButton - visible: !isReply - - contentItem: MaterialIcon { - icon: "\ue226" - } - - onClicked: currentRoom.chooseAndUploadFile() - - BusyIndicator { - anchors.fill: parent - - running: currentRoom && currentRoom.hasFileUploading - } + add: Transition { + NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 250 } } - ToolButton { - id: cancelReplyButton - visible: isReply + EmojiPicker { + width: parent.width - contentItem: MaterialIcon { - icon: "\ue5cd" - } + id: emojiPicker - onClicked: clearReply() - } + visible: false - TextArea { - property real progress: 0 - - Layout.fillWidth: true - Layout.minimumHeight: 48 - - id: inputField - - wrapMode: Text.Wrap - placeholderText: isReply ? "Reply to " + replyUserID : "Send a Message" - topPadding: 0 - bottomPadding: 0 - selectByMouse: true - verticalAlignment: TextEdit.AlignVCenter - - text: currentRoom ? currentRoom.cachedInput : "" - - background: Item { - } + textArea: inputField + emojiModel: EmojiModel { id: emojiModel } Rectangle { - width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0 - height: parent.height + Layout.fillWidth: true + Layout.preferredHeight: 1 - opacity: 0.2 - color: Material.accent - } - - Timer { - id: timeoutTimer - - repeat: false - interval: 2000 - onTriggered: { - repeatTimer.stop() - currentRoom.sendTypingNotification(false) - } - } - - Timer { - id: repeatTimer - - repeat: true - interval: 5000 - triggeredOnStart: true - onTriggered: currentRoom.sendTypingNotification(true) - } - - ToolTip { - visible: currentRoom - && currentRoom.hasUsersTyping - text: currentRoom ? currentRoom.usersTyping : "" - - Material.foreground: "white" - } - - Keys.onReturnPressed: { - if (event.modifiers & Qt.ShiftModifier) { - insert(cursorPosition, "\n") - } else { - postMessage(text) - text = "" - } - } - - Keys.onBacktabPressed: if (isAutoCompleting) autoCompleteListView.decrementCurrentIndex() - - Keys.onTabPressed: { - if (isAutoCompleting) { - autoCompleteListView.incrementCurrentIndex() - } else { - autoCompleteBeginPosition = text.substring(0, cursorPosition).lastIndexOf(" ") + 1 - var autoCompletePrefix = text.substring(0, cursorPosition).split(" ").pop() - if (!autoCompletePrefix) return - if (autoCompletePrefix.startsWith(":")) { - autoCompleteBeginPosition = text.substring(0, cursorPosition).lastIndexOf(" ") + 1 - autoCompleteModel = emojiModel.filterModel(autoCompletePrefix) - if (autoCompleteModel.length === 0) return - isAutoCompleting = true - autoCompleteEndPosition = cursorPosition - } else { - autoCompleteModel = currentRoom.getUsers(autoCompletePrefix) - if (autoCompleteModel.length === 0) return - isAutoCompleting = true - autoCompleteEndPosition = cursorPosition - } - } - replaceAutoComplete(autoCompleteListView.currentItem.autoCompleteText) - } - - onTextChanged: { - timeoutTimer.restart() - repeatTimer.start() - currentRoom.cachedInput = text - - if (cursorPosition !== autoCompleteBeginPosition && cursorPosition !== autoCompleteEndPosition) { - isAutoCompleting = false - autoCompleteListView.currentIndex = 0 - } - } - - function replaceAutoComplete(word) { - remove(autoCompleteBeginPosition, autoCompleteEndPosition) - autoCompleteEndPosition = autoCompleteBeginPosition + word.length - insert(cursorPosition, word) - } - - function postMessage(text) { - if (text.trim().length === 0) { return } - if(!currentRoom) { return } - - var PREFIX_ME = '/me ' - var PREFIX_NOTICE = '/notice ' - var PREFIX_RAINBOW = '/rainbow ' - var PREFIX_HTML = '/html ' - var PREFIX_MARKDOWN = '/md ' - - if (isReply) { - currentRoom.sendReply(replyUserID, replyEventID, replyContent, text) - clearReply() - return - } - - if (text.indexOf(PREFIX_ME) === 0) { - text = text.substr(PREFIX_ME.length) - currentRoom.postMessage(text, RoomMessageEvent.Emote) - return - } - if (text.indexOf(PREFIX_NOTICE) === 0) { - text = text.substr(PREFIX_NOTICE.length) - currentRoom.postMessage(text, RoomMessageEvent.Notice) - return - } - if (text.indexOf(PREFIX_RAINBOW) === 0) { - text = text.substr(PREFIX_RAINBOW.length) - - var parsedText = "" - var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] - for (var i = 0; i < text.length; i++) { - parsedText = parsedText + "" + text.charAt(i) + "" - } - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_HTML) === 0) { - text = text.substr(PREFIX_HTML.length) - var re = new RegExp("<.*?>") - var plainText = text.replace(re, "") - currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_MARKDOWN) === 0) { - text = text.substr(PREFIX_MARKDOWN.length) - var parsedText = Markdown.markdown_parser(text) - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - - currentRoom.postPlainText(text) + color: MSettings.darkTheme ? "#424242" : "#e7ebeb" } } - ToolButton { - Layout.alignment: Qt.AlignTop + RowLayout { + width: parent.width - id: emojiButton + spacing: 0 - contentItem: MaterialIcon { - icon: "\ue24e" - } + ToolButton { + Layout.alignment: Qt.AlignBottom - onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.open() + id: uploadButton + visible: !isReply - EmojiPicker { - x: -width + parent.width - y: -height - 16 - - width: 360 - height: 320 - - id: emojiPicker - - emojiModel: EmojiModel { - id: emojiModel + contentItem: MaterialIcon { + icon: "\ue226" } - Material.elevation: 2 + onClicked: currentRoom.chooseAndUploadFile() - textArea: inputField + BusyIndicator { + anchors.fill: parent + + running: currentRoom && currentRoom.hasFileUploading + } + } + + ToolButton { + Layout.alignment: Qt.AlignBottom + + id: cancelReplyButton + + visible: isReply + + contentItem: MaterialIcon { + icon: "\ue5cd" + } + + onClicked: clearReply() + } + + TextArea { + property real progress: 0 + + Layout.fillWidth: true + Layout.minimumHeight: 48 + + id: inputField + + wrapMode: Text.Wrap + placeholderText: isReply ? "Reply to " + replyUserID : "Send a Message" + topPadding: 0 + bottomPadding: 0 + selectByMouse: true + verticalAlignment: TextEdit.AlignVCenter + + text: currentRoom ? currentRoom.cachedInput : "" + + background: Item { + } + + Rectangle { + width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0 + height: parent.height + + opacity: 0.2 + color: Material.accent + } + + Timer { + id: timeoutTimer + + repeat: false + interval: 2000 + onTriggered: { + repeatTimer.stop() + currentRoom.sendTypingNotification(false) + } + } + + Timer { + id: repeatTimer + + repeat: true + interval: 5000 + triggeredOnStart: true + onTriggered: currentRoom.sendTypingNotification(true) + } + + ToolTip { + visible: currentRoom + && currentRoom.hasUsersTyping + text: currentRoom ? currentRoom.usersTyping : "" + + Material.foreground: "white" + } + + Keys.onReturnPressed: { + if (event.modifiers & Qt.ShiftModifier) { + insert(cursorPosition, "\n") + } else { + postMessage(text) + text = "" + } + } + + Keys.onBacktabPressed: if (isAutoCompleting) autoCompleteListView.decrementCurrentIndex() + + Keys.onTabPressed: { + if (isAutoCompleting) { + autoCompleteListView.incrementCurrentIndex() + } else { + autoCompleteBeginPosition = text.substring(0, cursorPosition).lastIndexOf(" ") + 1 + var autoCompletePrefix = text.substring(0, cursorPosition).split(" ").pop() + if (!autoCompletePrefix) return + if (autoCompletePrefix.startsWith(":")) { + autoCompleteBeginPosition = text.substring(0, cursorPosition).lastIndexOf(" ") + 1 + autoCompleteModel = emojiModel.filterModel(autoCompletePrefix) + if (autoCompleteModel.length === 0) return + isAutoCompleting = true + autoCompleteEndPosition = cursorPosition + } else { + autoCompleteModel = currentRoom.getUsers(autoCompletePrefix) + if (autoCompleteModel.length === 0) return + isAutoCompleting = true + autoCompleteEndPosition = cursorPosition + } + } + replaceAutoComplete(autoCompleteListView.currentItem.autoCompleteText) + } + + onTextChanged: { + timeoutTimer.restart() + repeatTimer.start() + currentRoom.cachedInput = text + + if (cursorPosition !== autoCompleteBeginPosition && cursorPosition !== autoCompleteEndPosition) { + isAutoCompleting = false + autoCompleteListView.currentIndex = 0 + } + } + + function replaceAutoComplete(word) { + remove(autoCompleteBeginPosition, autoCompleteEndPosition) + autoCompleteEndPosition = autoCompleteBeginPosition + word.length + insert(cursorPosition, word) + } + + function postMessage(text) { + if (text.trim().length === 0) { return } + if(!currentRoom) { return } + + var PREFIX_ME = '/me ' + var PREFIX_NOTICE = '/notice ' + var PREFIX_RAINBOW = '/rainbow ' + var PREFIX_HTML = '/html ' + var PREFIX_MARKDOWN = '/md ' + + if (isReply) { + currentRoom.sendReply(replyUserID, replyEventID, replyContent, text) + clearReply() + return + } + + if (text.indexOf(PREFIX_ME) === 0) { + text = text.substr(PREFIX_ME.length) + currentRoom.postMessage(text, RoomMessageEvent.Emote) + return + } + if (text.indexOf(PREFIX_NOTICE) === 0) { + text = text.substr(PREFIX_NOTICE.length) + currentRoom.postMessage(text, RoomMessageEvent.Notice) + return + } + if (text.indexOf(PREFIX_RAINBOW) === 0) { + text = text.substr(PREFIX_RAINBOW.length) + + var parsedText = "" + var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] + for (var i = 0; i < text.length; i++) { + parsedText = parsedText + "" + text.charAt(i) + "" + } + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_HTML) === 0) { + text = text.substr(PREFIX_HTML.length) + var re = new RegExp("<.*?>") + var plainText = text.replace(re, "") + currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_MARKDOWN) === 0) { + text = text.substr(PREFIX_MARKDOWN.length) + var parsedText = Markdown.markdown_parser(text) + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + + currentRoom.postPlainText(text) + } + } + + ToolButton { + Layout.alignment: Qt.AlignBottom + + id: emojiButton + + contentItem: MaterialIcon { + icon: "\ue24e" + } + + onClicked: emojiPicker.visible = !emojiPicker.visible } } } diff --git a/qml/main.qml b/qml/main.qml index 487ae7117..a6eb9d15e 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -28,8 +28,8 @@ ApplicationWindow { visible: true title: qsTr("Spectral") - Material.foreground: Material.theme == Material.Dark ? "#FFFFFF" : "#1D333E" - Material.background: Material.theme == Material.Dark ? "#303030" : "#FFFFFF" + Material.foreground: MSettings.darkTheme ? "#FFFFFF" : "#1D333E" + Material.background: MSettings.darkTheme ? "#303030" : "#FFFFFF" Platform.SystemTrayIcon { visible: MSettings.showTray