Basic sticker pack editor

This commit is contained in:
Nicolas Werner 2021-08-06 01:45:47 +02:00
parent 6d83b7c675
commit a57a15a2e0
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
17 changed files with 751 additions and 167 deletions

View file

@ -11,10 +11,11 @@ import im.nheko 1.0
Rectangle {
id: avatar
property alias url: img.source
property string url
property string userid
property string displayName
property alias textColor: label.color
property bool crop: true
signal clicked(var mouse)
@ -44,12 +45,13 @@ Rectangle {
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
mipmap: true
smooth: true
sourceSize.width: avatar.width
sourceSize.height: avatar.height
layer.enabled: true
source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale")
MouseArea {
id: mouseArea

View file

@ -154,7 +154,7 @@ ApplicationWindow {
GridLayout {
columns: 2
rowSpacing: 10
rowSpacing: Nheko.paddingLarge
MatrixText {
text: qsTr("SETTINGS")
@ -180,7 +180,7 @@ ApplicationWindow {
}
MatrixText {
text: "Room access"
text: qsTr("Room access")
Layout.fillWidth: true
}

View file

@ -30,6 +30,10 @@ MouseArea {
property alias enabled: root.enabled
function calculateNewPosition(flickableItem, wheel) {
// breaks ListView's with headers...
//if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
// minYExtent += flickableItem.headerItem.height;
//Nothing to scroll
if (flickableItem.contentHeight < flickableItem.height)
return flickableItem.contentY;
@ -55,9 +59,6 @@ MouseArea {
var minYExtent = flickableItem.originY + flickableItem.topMargin;
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
minYExtent += flickableItem.headerItem.height;
//Avoid overscrolling
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
}

View file

@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import im.nheko 1.0
Rectangle {
id: tile
property color background: Nheko.colors.window
property color importantText: Nheko.colors.text
property color unimportantText: Nheko.colors.buttonText
property color bubbleBackground: Nheko.colors.highlight
property color bubbleText: Nheko.colors.highlightedText
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl
required property string title
required property string subtitle
required property int index
required property int selectedIndex
property bool crop: true
color: background
height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width
state: "normal"
states: [
State {
name: "highlight"
when: hovered.hovered && !(index == selectedIndex)
PropertyChanges {
target: tile
background: Nheko.colors.dark
importantText: Nheko.colors.brightText
unimportantText: Nheko.colors.brightText
bubbleBackground: Nheko.colors.highlight
bubbleText: Nheko.colors.highlightedText
}
},
State {
name: "selected"
when: index == selectedIndex
PropertyChanges {
target: tile
background: Nheko.colors.highlight
importantText: Nheko.colors.highlightedText
unimportantText: Nheko.colors.highlightedText
bubbleBackground: Nheko.colors.highlightedText
bubbleText: Nheko.colors.highlight
}
}
]
HoverHandler {
id: hovered
}
RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
Avatar {
id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter
height: avatarSize
width: avatarSize
url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: title
crop: tile.crop
}
ColumnLayout {
id: textContent
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
Layout.minimumWidth: 100
width: parent.width - avatar.width
Layout.preferredWidth: parent.width - avatar.width
spacing: Nheko.paddingSmall
RowLayout {
Layout.fillWidth: true
spacing: 0
ElidedLabel {
Layout.alignment: Qt.AlignBottom
color: tile.importantText
elideWidth: textContent.width - Nheko.paddingMedium
fullText: title
textFormat: Text.PlainText
}
Item {
Layout.fillWidth: true
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
ElidedLabel {
color: tile.unimportantText
font.pixelSize: fontMetrics.font.pixelSize * 0.9
elideWidth: textContent.width - Nheko.paddingSmall
fullText: subtitle
textFormat: Text.PlainText
}
Item {
Layout.fillWidth: true
}
}
}
}
}

View file

@ -0,0 +1,283 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
import "../components"
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import im.nheko 1.0
ApplicationWindow {
//Component.onCompleted: Nheko.reparent(win)
id: win
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
property SingleImagePackModel imagePack
property int currentImageIndex: -1
readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Editing image pack")
height: 600
width: 600
palette: Nheko.colors
color: Nheko.colors.base
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint
AdaptiveLayout {
id: adaptiveView
anchors.fill: parent
singlePageMode: false
pageIndex: 0
AdaptiveLayoutElement {
id: packlistC
visible: Settings.groupView
minimumWidth: 200
collapsedWidth: 200
preferredWidth: 300
maximumWidth: 300
clip: true
ListView {
//required property bool isEmote
//required property bool isSticker
model: imagePack
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
header: AvatarListTile {
title: imagePack.packname
avatarUrl: imagePack.avatarUrl
subtitle: imagePack.statekey
index: -1
selectedIndex: currentImageIndex
TapHandler {
onSingleTapped: currentImageIndex = -1
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: parent.height - Nheko.paddingSmall * 2
width: 3
color: Nheko.colors.highlight
}
}
delegate: AvatarListTile {
id: packItem
property color background: Nheko.colors.window
property color importantText: Nheko.colors.text
property color unimportantText: Nheko.colors.buttonText
property color bubbleBackground: Nheko.colors.highlight
property color bubbleText: Nheko.colors.highlightedText
required property string shortCode
required property string url
required property string body
title: shortCode
subtitle: body
avatarUrl: url
selectedIndex: currentImageIndex
crop: false
TapHandler {
onSingleTapped: currentImageIndex = index
}
}
}
}
AdaptiveLayoutElement {
id: packinfoC
Rectangle {
color: Nheko.colors.window
GridLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
visible: currentImageIndex == -1
enabled: visible
columns: 2
rowSpacing: Nheko.paddingLarge
Avatar {
Layout.columnSpan: 2
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: imagePack.packname
height: 130
width: 130
crop: false
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
visible: imagePack.roomid
text: qsTr("State key")
}
MatrixTextField {
visible: imagePack.roomid
Layout.fillWidth: true
text: imagePack.statekey
onTextEdited: imagePack.statekey = text
}
MatrixText {
text: qsTr("Packname")
}
MatrixTextField {
Layout.fillWidth: true
text: imagePack.packname
onTextEdited: imagePack.packname = text
}
MatrixText {
text: qsTr("Attrbution")
}
MatrixTextField {
Layout.fillWidth: true
text: imagePack.attribution
onTextEdited: imagePack.attribution = text
}
MatrixText {
text: qsTr("Use as Emoji")
}
ToggleButton {
checked: imagePack.isEmotePack
onToggled: imagePack.isEmotePack = checked
Layout.alignment: Qt.AlignRight
}
MatrixText {
text: qsTr("Use as Sticker")
}
ToggleButton {
checked: imagePack.isStickerPack
onToggled: imagePack.isStickerPack = checked
Layout.alignment: Qt.AlignRight
}
Item {
Layout.columnSpan: 2
Layout.fillHeight: true
}
}
GridLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
visible: currentImageIndex >= 0
enabled: visible
columns: 2
rowSpacing: Nheko.paddingLarge
Avatar {
Layout.columnSpan: 2
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/")
displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
height: 130
width: 130
crop: false
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: qsTr("Shortcode")
}
MatrixTextField {
Layout.fillWidth: true
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
}
MatrixText {
text: qsTr("Body")
}
MatrixTextField {
Layout.fillWidth: true
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
}
MatrixText {
text: qsTr("Use as Emoji")
}
ToggleButton {
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsEmote)
Layout.alignment: Qt.AlignRight
}
MatrixText {
text: qsTr("Use as Sticker")
}
ToggleButton {
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsSticker)
Layout.alignment: Qt.AlignRight
}
Item {
Layout.columnSpan: 2
Layout.fillHeight: true
}
}
}
}
}
footer: DialogButtonBox {
id: buttons
Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
onClicked: win.close()
}
Button {
text: qsTr("Save")
DialogButtonBox.buttonRole: DialogButtonBox.ApplyRole
onClicked: {
imagePack.save();
win.close();
}
}
}
}

View file

@ -20,14 +20,22 @@ ApplicationWindow {
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Image pack settings")
height: 400
width: 600
height: 600
width: 800
palette: Nheko.colors
color: Nheko.colors.base
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint
Component.onCompleted: Nheko.reparent(win)
Component {
id: packEditor
ImagePackEditorDialog {
}
}
AdaptiveLayout {
id: adaptiveView
@ -54,7 +62,7 @@ ApplicationWindow {
enabled: !Settings.mobileMode
}
delegate: Rectangle {
delegate: AvatarListTile {
id: packItem
property color background: Nheko.colors.window
@ -63,131 +71,24 @@ ApplicationWindow {
property color bubbleBackground: Nheko.colors.highlight
property color bubbleText: Nheko.colors.highlightedText
required property string displayName
required property string avatarUrl
required property bool fromAccountData
required property bool fromCurrentRoom
required property int index
color: background
height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width
state: "normal"
states: [
State {
name: "highlight"
when: hovered.hovered && !(index == currentPackIndex)
PropertyChanges {
target: packItem
background: Nheko.colors.dark
importantText: Nheko.colors.brightText
unimportantText: Nheko.colors.brightText
bubbleBackground: Nheko.colors.highlight
bubbleText: Nheko.colors.highlightedText
}
},
State {
name: "selected"
when: index == currentPackIndex
PropertyChanges {
target: packItem
background: Nheko.colors.highlight
importantText: Nheko.colors.highlightedText
unimportantText: Nheko.colors.highlightedText
bubbleBackground: Nheko.colors.highlightedText
bubbleText: Nheko.colors.highlight
}
}
]
title: displayName
subtitle: {
if (fromAccountData)
return qsTr("Private pack");
else if (fromCurrentRoom)
return qsTr("Pack from this room");
else
return qsTr("Globally enabled pack");
}
selectedIndex: currentPackIndex
TapHandler {
margin: -Nheko.paddingSmall
onSingleTapped: currentPackIndex = index
}
HoverHandler {
id: hovered
}
RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
Avatar {
// In the future we could show an online indicator by setting the userid for the avatar
//userid: Nheko.currentUser.userid
id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter
height: avatarSize
width: avatarSize
url: avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: packItem.displayName
}
ColumnLayout {
id: textContent
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
Layout.minimumWidth: 100
width: parent.width - avatar.width
Layout.preferredWidth: parent.width - avatar.width
spacing: Nheko.paddingSmall
RowLayout {
Layout.fillWidth: true
spacing: 0
ElidedLabel {
Layout.alignment: Qt.AlignBottom
color: packItem.importantText
elideWidth: textContent.width - Nheko.paddingMedium
fullText: displayName
textFormat: Text.PlainText
}
Item {
Layout.fillWidth: true
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
ElidedLabel {
color: packItem.unimportantText
font.pixelSize: fontMetrics.font.pixelSize * 0.9
elideWidth: textContent.width - Nheko.paddingSmall
fullText: {
if (fromAccountData)
return qsTr("Private pack");
else if (fromCurrentRoom)
return qsTr("Pack from this room");
else
return qsTr("Globally enabled pack");
}
textFormat: Text.PlainText
}
Item {
Layout.fillWidth: true
}
}
}
}
}
}
@ -201,15 +102,10 @@ ApplicationWindow {
color: Nheko.colors.window
ColumnLayout {
//Button {
// Layout.alignment: Qt.AlignHCenter
// text: qsTr("Edit")
// enabled: currentPack.canEdit
//}
id: packinfo
property string packName: currentPack ? currentPack.packname : ""
property string attribution: currentPack ? currentPack.attribution : ""
property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
anchors.fill: parent
@ -227,8 +123,18 @@ ApplicationWindow {
MatrixText {
text: packinfo.packName
font.pixelSize: 24
font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
}
MatrixText {
text: packinfo.attribution
wrapMode: TextEdit.Wrap
horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
}
GridLayout {
@ -250,6 +156,18 @@ ApplicationWindow {
}
Button {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Edit")
enabled: currentPack.canEdit
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": currentPack
});
dialog.show();
}
}
GridView {
Layout.fillHeight: true
Layout.fillWidth: true
@ -272,7 +190,7 @@ ApplicationWindow {
width: stickerDim
height: stickerDim
hoverEnabled: true
ToolTip.text: ":" + model.shortcode + ": - " + model.body
ToolTip.text: ":" + model.shortCode + ": - " + model.body
ToolTip.visible: hovered
contentItem: Image {

View file

@ -160,6 +160,7 @@
<file>qml/device-verification/Success.qml</file>
<file>qml/dialogs/InputDialog.qml</file>
<file>qml/dialogs/ImagePackSettingsDialog.qml</file>
<file>qml/dialogs/ImagePackEditorDialog.qml</file>
<file>qml/ui/Ripple.qml</file>
<file>qml/ui/Spinner.qml</file>
<file>qml/ui/animations/BlinkAnimation.qml</file>
@ -173,6 +174,7 @@
<file>qml/voip/VideoCall.qml</file>
<file>qml/components/AdaptiveLayout.qml</file>
<file>qml/components/AdaptiveLayoutElement.qml</file>
<file>qml/components/AvatarListTile.qml</file>
<file>qml/components/FlatButton.qml</file>
<file>qml/RoomMembers.qml</file>
<file>qml/InviteDialog.qml</file>