diff --git a/qml/main.qml b/qml/main.qml index 3c3b6f7..98724f9 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -6,6 +6,7 @@ import QtQuick.Layouts 1.3 import QtQuick.Window 2.11 import "fonts/Twemoji.js" as T +import "overlays" import "panes" import "widgets" @@ -117,7 +118,7 @@ Item { } } - Rectangle { // THE RIGHT PANE WHERE THE MESSAGES AND STuFF GO + Rectangle { // THE RIGHT PANE WHERE THE MESSAGES AND STUFF GO color: "#EEEEFF" Layout.fillWidth: true Layout.fillHeight: true @@ -139,7 +140,7 @@ Item { Item {} // empty - MessageList { // messagePane + OverlayPane { // messagePane anchors.fill: parent } diff --git a/qml/overlays/BulletinOverlay.qml b/qml/overlays/BulletinOverlay.qml new file mode 100644 index 0000000..d6679a8 --- /dev/null +++ b/qml/overlays/BulletinOverlay.qml @@ -0,0 +1,85 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 + +import "../widgets" +import "../widgets/controls" as Awesome +import "../fonts/Twemoji.js" as T + +ColumnLayout { + Layout.fillWidth: true + + + Flickable { // THE MESSAGE LIST ITSELF + id: sv + clip: true + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: true + Layout.minimumWidth: parent.width + Layout.maximumWidth: parent.width + Layout.fillWidth: true + contentWidth: colMessages.width + contentHeight: colMessages.height + boundsBehavior: Flickable.StopAtBounds + maximumFlickVelocity: 800 + + + Connections { + target: gcd + + onClearMessages: function() { + messagesModel.clear() + } + + onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts) { + messagesModel.append({ + "_handle": handle, + "_from": from, + "_displayName": displayName, + "_message": parse(message, 12), + "_image": image, + "_mid": mid, + "_fromMe": fromMe, + "_ts": ts, + }) + + if (sv.contentY + sv.height >= sv.contentHeight - colMessages.height && sv.contentHeight > sv.height) { + sv.contentY = sv.contentHeight - sv.height + } + } + } + + + ScrollBar.vertical: ScrollBar{ + policy: ScrollBar.AlwaysOn + } + + ColumnLayout { + id: colMessages + width: sv.width + + + ListModel { // MESSAGE OBJECTS ARE STORED HERE ... + id: messagesModel + } + + Item { height: 6 } + + Repeater { // ... AND DISPLAYED HERE + model: messagesModel + delegate: Message { + handle: _handle + from: _from + displayName: _displayName + message: "bulletinbulletinbulletin" + image: _image + messageID: _mid + fromMe: _fromMe + timestamp: _ts + } + } + } + } +} \ No newline at end of file diff --git a/qml/widgets/MessageList.qml b/qml/overlays/ChatOverlay.qml similarity index 93% rename from qml/widgets/MessageList.qml rename to qml/overlays/ChatOverlay.qml index 494a289..02e3dfc 100644 --- a/qml/widgets/MessageList.qml +++ b/qml/overlays/ChatOverlay.qml @@ -4,22 +4,14 @@ import QtQuick.Controls 2.4 import QtQuick.Controls.Material 2.0 import QtQuick.Layouts 1.3 -import "controls" as Awesome +import "../widgets" +import "../widgets/controls" as Awesome import "../fonts/Twemoji.js" as T ColumnLayout { Layout.fillWidth: true - StackToolbar { - text: "open privacy exec" - - aux.onClicked: { - theStack.pane = gcd.currentOpenConversation.length == 32 ? theStack.groupProfilePane : theStack.userProfilePane - gcd.requestGroupSettings() - } - } - Flickable { // THE MESSAGE LIST ITSELF id: sv clip: true @@ -42,11 +34,19 @@ ColumnLayout { } onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts) { + var msg + try { + msg = JSON.parse(message) + } catch (e) { + msg = {"o": 1, "d": "(legacy message type) " + message} + } + if (msg.o != 1) return + messagesModel.append({ "_handle": handle, "_from": from, "_displayName": displayName, - "_message": parse(message, 12), + "_message": parse(msg.d, 12), "_image": image, "_mid": mid, "_fromMe": fromMe, @@ -226,7 +226,8 @@ ColumnLayout { onClicked: { if (txtMessage.text != "") { txtHidden.text = restoreEmoji(txtMessage.text) - gcd.sendMessage(txtHidden.getText(0, txtHidden.text.length), nextMessageID++) + var msg = JSON.stringify({"o":1, "d":txtHidden.getText(0, txtHidden.text.length)}) + gcd.sendMessage(msg, nextMessageID++) } txtMessage.text = "" } diff --git a/qml/overlays/Game1Overlay.qml b/qml/overlays/Game1Overlay.qml new file mode 100644 index 0000000..b534919 --- /dev/null +++ b/qml/overlays/Game1Overlay.qml @@ -0,0 +1,18 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 + +import "../widgets" +import "../widgets/controls" as Awesome +import "../fonts/Twemoji.js" as T + +ColumnLayout { + Layout.fillWidth: true + + + ScalingLabel { + text: "gardening game or maybe some other cool cwtch-enabled game" + } +} \ No newline at end of file diff --git a/qml/overlays/Game2Overlay.qml b/qml/overlays/Game2Overlay.qml new file mode 100644 index 0000000..4cae0c4 --- /dev/null +++ b/qml/overlays/Game2Overlay.qml @@ -0,0 +1,18 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 + +import "../widgets" +import "../widgets/controls" as Awesome +import "../fonts/Twemoji.js" as T + +ColumnLayout { + Layout.fillWidth: true + + + ScalingLabel { + text: "rock paper scissors. or chess? basically the same thing" + } +} \ No newline at end of file diff --git a/qml/overlays/ListOverlay.qml b/qml/overlays/ListOverlay.qml new file mode 100644 index 0000000..b53ddd9 --- /dev/null +++ b/qml/overlays/ListOverlay.qml @@ -0,0 +1,262 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 + +import "../widgets" +import "../widgets/controls" as Awesome +import "../fonts/Twemoji.js" as T + +ColumnLayout { + Layout.fillWidth: true + + + Flickable { // THE MESSAGE LIST ITSELF + id: sv + clip: true + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillHeight: true + Layout.minimumWidth: parent.width + Layout.maximumWidth: parent.width + Layout.fillWidth: true + contentWidth: colMessages.width + contentHeight: colMessages.height + boundsBehavior: Flickable.StopAtBounds + maximumFlickVelocity: 800 + + + Connections { + target: gcd + + onClearMessages: function() { + messagesModel.clear() + } + + onAppendMessage: function(handle, from, displayName, message, image, mid, fromMe, ts) { + var msg + try { + msg = JSON.parse(message) + } catch (e) { + msg = {"o": 1, "d": "(legacy message type) " + message} + } + if (msg.o != 2) return + + messagesModel.append({ + "_handle": handle, + "_from": from, + "_displayName": displayName, + "_message": parse(msg.d, 12), + "_image": image, + "_mid": mid, + "_fromMe": fromMe, + "_ts": ts, + }) + + if (sv.contentY + sv.height >= sv.contentHeight - colMessages.height && sv.contentHeight > sv.height) { + sv.contentY = sv.contentHeight - sv.height + } + } + } + + + ScrollBar.vertical: ScrollBar{ + policy: ScrollBar.AlwaysOn + } + + ColumnLayout { + id: colMessages + width: sv.width + + + ListModel { // MESSAGE OBJECTS ARE STORED HERE ... + id: messagesModel + } + + Item { height: 6 } + + Repeater { // ... AND DISPLAYED HERE + model: messagesModel + delegate: Message { + handle: _handle + from: _from + displayName: _displayName + message: "be gay, do crimes, and make lists" + image: _image + messageID: _mid + fromMe: _fromMe + timestamp: _ts + } + } + } + } + + RowLayout { // THE BOTTOM DRAWER + Rectangle { // MESSAGE ENTRY TEXTFIELD + id: rectMessage + Layout.fillWidth: true + Layout.minimumHeight: 40 * gcd.themeScale + Layout.maximumHeight: 40 * gcd.themeScale + color: "#EDEDED" + border.color: "#AAAAAA" + radius: 10 + + + Flickable { + id: flkMessage + anchors.fill: parent//this does nothing! bug in qt + Layout.minimumWidth: parent.width + Layout.maximumWidth: parent.width + Layout.minimumHeight: rectMessage.height + Layout.maximumHeight: rectMessage.height + contentWidth: txtMessage.width + contentHeight: txtMessage.height + boundsBehavior: Flickable.StopAtBounds + clip:true + maximumFlickVelocity: 300 + + + ScrollBar.vertical: ScrollBar{} + + TextEdit { + id: txtMessage + font.pixelSize: 10 + text: "" + padding: 6 + wrapMode: TextEdit.Wrap + textFormat: Text.RichText + width: rectMessage.width + //height: parent.height + + property bool skipOneUpdate: false + + Keys.onReturnPressed: { // CTRL+ENTER = LINEBREAK + if (event.modifiers & Qt.ControlModifier) { + txtMessage.insert(txtMessage.cursorPosition, "
") + } else if (event.modifiers == Qt.NoModifier) { + btnSend.clicked() + } + } + + // welcome to the emoji parser! it is horrifying code that needs to leave in + // while also stripping any other tag, including other images. + // TODO: this probably breaks if people actually do want to paste html + onTextChanged: { + //console.log("onTextChanged()") + + // we're taking advantage of TextEdit.getText()'s parsing capability, which means occasionally + // passing text into it to be filtered. this prevents recursive calls putting us into an + // infinite loop + if (skipOneUpdate) { + console.log("skipping one update") + skipOneUpdate = false + return + } + + //console.log("1: " + txtMessage.getText(0, txtMessage.text.length)) + + // convert tags back to their emoji form + var nt = restoreEmoji(txtMessage.text) + if (nt != txtMessage.text) { + skipOneUpdate = true + txtMessage.text = nt + } + + //console.log("2: " + txtMessage.getText(0, txtMessage.text.length)) + + // strip all HTML tags + var theText = txtMessage.getText(0, txtMessage.text.length) + //console.log("3: " + theText) + + // convert emoji back to tags + nt = parse(theText, 10) + //console.log("4: " + nt) + + // if there were changes... + if (nt != txtMessage.getText(0, txtMessage.text.length)) { + // first we need to update the cursor position to be the same distance from the end + var oldcursor = txtMessage.cursorPosition + var oldlen = txtMessage.getText(0, txtMessage.text.length).length + + // then we actually put the updated text in + skipOneUpdate = true + txtMessage.text = nt + + // and then restore the cursor + var newlen = txtMessage.getText(0, txtMessage.text.length).length + txtMessage.cursorPosition = newlen - (oldlen - oldcursor) + } + + // autoscroll down only when the scrollbar is already all the way down + if (flkMessage.contentY + flkMessage.height >= flkMessage.contentHeight - txtMessage.height && flkMessage.contentHeight > flkMessage.height) { + flkMessage.contentY = flkMessage.contentHeight - flkMessage.height + } + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: txtMessage.focus = true + } + } + + ColumnLayout { + id: colRight + spacing: 1 + + + SimpleButton { // SEND MESSAGE BUTTON + id: btnSend + icon: "regular/paper-plane" + text: "send" + Layout.minimumWidth: btnEmoji.width + btnAttach.width + 2 + Layout.maximumWidth: btnEmoji.width + btnAttach.width + 2 + anchors.right: parent.right + anchors.rightMargin: 2 + + property int nextMessageID: 1 + + TextEdit { + id: txtHidden + visible: false + textFormat: Text.RichText + } + + onClicked: { + if (txtMessage.text != "") { + txtHidden.text = restoreEmoji(txtMessage.text) + var msg = JSON.stringify({"o":2, "d":txtHidden.getText(0, txtHidden.text.length)}) + gcd.sendMessage(msg, nextMessageID++) + } + txtMessage.text = "" + } + } + + RowLayout { + spacing: 1 + + + SimpleButton { // EMOJI DRAWER BUTTON + id: btnEmoji + icon: "regular/smile" + anchors.right: btnAttach.left + anchors.rightMargin: 2 + + onClicked: gcd.popup("emoji not yet implemented, sorry") + } + + SimpleButton { + id: btnAttach + icon: "solid/paperclip" + anchors.right: parent.right + anchors.rightMargin: 2 + + onClicked: { + gcd.popup("attachments not yet implemented, sorry") + } + } + } + } + } +} \ No newline at end of file diff --git a/qml/panes/OverlayPane.qml b/qml/panes/OverlayPane.qml new file mode 100644 index 0000000..c8b83fd --- /dev/null +++ b/qml/panes/OverlayPane.qml @@ -0,0 +1,89 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 + +import "../widgets" +import "../overlays" + +ColumnLayout { + Layout.fillWidth: true + + + StackToolbar { + text: "open privacy exec" + + aux.onClicked: { + theStack.pane = gcd.currentOpenConversation.length == 32 ? theStack.groupProfilePane : theStack.userProfilePane + gcd.requestGroupSettings() + } + } + + Row { + id: switcher + + + SimpleButton { + text: "Chat" + + + onClicked: overlayStack.overlay = overlayStack.chatOverlay + } + + SimpleButton { + text: "Lists" + + + onClicked: overlayStack.overlay = overlayStack.listOverlay + } + + SimpleButton { + text: "Bulletins" + + + onClicked: overlayStack.overlay = overlayStack.bulletinOverlay + } + + SimpleButton { + text: "Game 1" + + + onClicked: overlayStack.overlay = overlayStack.game1Overlay + } + + SimpleButton { + text: "Game 2" + + + onClicked: overlayStack.overlay = overlayStack.game2Overlay + } + } + + StackLayout { + id: overlayStack + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: switcher.bottom + currentIndex: 0 + + property alias overlay: overlayStack.currentIndex + readonly property int chatOverlay: 0 + readonly property int listOverlay: 1 + readonly property int bulletinOverlay: 2 + readonly property int game1Overlay: 3 + readonly property int game2Overlay: 4 + + + ChatOverlay {} //0 + + ListOverlay{} //1 + + BulletinOverlay{} //2 + + Game1Overlay{} //3 + + Game2Overlay{} //4 + } +} \ No newline at end of file