271 lines
9.4 KiB
QML
271 lines
9.4 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Controls 0.1
|
|
import StatusQ.Components 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Core.Utils 0.1 as StatusQUtils
|
|
|
|
import utils 1.0
|
|
|
|
import shared.controls 1.0
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
StatusDropdown {
|
|
id: root
|
|
|
|
required property StatusEmojiModel emojiModel
|
|
required property var settings
|
|
|
|
property alias searchString: searchBox.text
|
|
property string emojiSize: ""
|
|
|
|
signal emojiSelected(string emoji, bool atCu)
|
|
|
|
modal: false
|
|
width: 360
|
|
padding: 0
|
|
|
|
function addEmoji(hexcode) {
|
|
const extensionIndex = hexcode.lastIndexOf('.');
|
|
let iconCodePoint = hexcode
|
|
if (extensionIndex > -1) {
|
|
iconCodePoint = iconCodePoint.substring(0, extensionIndex)
|
|
}
|
|
|
|
const encodedIcon = StatusQUtils.Emoji.getEmojiCodepoint(iconCodePoint)
|
|
|
|
root.emojiModel.addRecentEmoji(hexcode)
|
|
|
|
// Adding a space because otherwise, some emojis would fuse since emoji is just a string
|
|
root.emojiSelected(StatusQUtils.Emoji.parse(encodedIcon, root.emojiSize || undefined) + ' ', true)
|
|
root.close()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
root.emojiModel.recentEmojis = settings.recentEmojis
|
|
}
|
|
|
|
onOpened: {
|
|
searchBox.input.edit.forceActiveFocus()
|
|
emojiGrid.positionViewAtBeginning()
|
|
}
|
|
|
|
onClosed: {
|
|
const recent = root.emojiModel.recentEmojis
|
|
if (recent.length)
|
|
settings.recentEmojis = recent
|
|
searchBox.text = ""
|
|
root.emojiSize = ""
|
|
skinToneEmoji.expandSkinColorOptions = false
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
readonly property string searchStringLowercase: searchBox.text.toLowerCase()
|
|
readonly property int headerMargin: 8
|
|
readonly property int imageWidth: 26
|
|
readonly property int imageMargin: 4
|
|
|
|
readonly property var filteredModel: SortFilterProxyModel {
|
|
sourceModel: root.emojiModel
|
|
|
|
filters: [
|
|
FastExpressionFilter {
|
|
expression: {
|
|
if (model.category === root.emojiModel.recentCategoryName) // don't show/duplicate recents when searching
|
|
return false
|
|
|
|
d.searchStringLowercase
|
|
return model.name.toLowerCase().includes(d.searchStringLowercase) ||
|
|
model.shortname.toLowerCase().includes(d.searchStringLowercase) ||
|
|
model.aliases.some(a => a.includes(d.searchStringLowercase)) ||
|
|
model.keywords.some(k => k.includes(d.searchStringLowercase))
|
|
}
|
|
expectedRoles: ["name", "shortname", "aliases", "keywords", "category"]
|
|
enabled: !!d.searchStringLowercase
|
|
},
|
|
AnyOf {
|
|
ValueFilter {
|
|
roleName: "skinColor"
|
|
value: ""
|
|
}
|
|
ValueFilter {
|
|
roleName: "skinColor"
|
|
value: root.emojiModel.baseSkinColorName
|
|
}
|
|
enabled: settings.skinColor === ""
|
|
},
|
|
AnyOf {
|
|
ValueFilter {
|
|
roleName: "skinColor"
|
|
value: ""
|
|
}
|
|
ValueFilter {
|
|
roleName: "skinColor"
|
|
value: settings.skinColor
|
|
}
|
|
enabled: settings.skinColor !== ""
|
|
}
|
|
]
|
|
|
|
sorters: RoleSorter {
|
|
roleName: "emoji_order"
|
|
}
|
|
}
|
|
}
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 0
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: searchBox.height + d.headerMargin
|
|
|
|
SearchBox {
|
|
input.edit.objectName: "StatusEmojiPopup_searchBox"
|
|
id: searchBox
|
|
anchors.right: skinToneEmoji.left
|
|
anchors.rightMargin: d.headerMargin
|
|
anchors.top: parent.top
|
|
anchors.topMargin: d.headerMargin
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: d.headerMargin
|
|
minimumHeight: 36
|
|
maximumHeight: 36
|
|
input.topPadding: 0
|
|
input.bottomPadding: 0
|
|
}
|
|
|
|
Row {
|
|
id: skinToneEmoji
|
|
property bool expandSkinColorOptions: false
|
|
clip: true
|
|
width: expandSkinColorOptions ? (22 * skinColorEmojiRepeater.count) : 22
|
|
height: 22
|
|
opacity: expandSkinColorOptions ? 1.0 : 0.0
|
|
Behavior on width { NumberAnimation { duration: 400 } }
|
|
Behavior on opacity { NumberAnimation { duration: 200 } }
|
|
visible: (opacity > 0.1)
|
|
anchors.verticalCenter: searchBox.verticalCenter
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: d.headerMargin
|
|
Repeater {
|
|
id: skinColorEmojiRepeater
|
|
model: ["1f590-1f3fb", "1f590-1f3fc", "1f590-1f3fd", "1f590-1f3fe", "1f590-1f3ff", "1f590"]
|
|
delegate: StatusEmoji {
|
|
width: 22
|
|
height: 22
|
|
emojiId: modelData
|
|
MouseArea {
|
|
cursorShape: Qt.PointingHandCursor
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
settings.skinColor = (index === 5) ? "" : modelData.split("-")[1];
|
|
skinToneEmoji.expandSkinColorOptions = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StatusEmoji {
|
|
width: 22
|
|
height: 22
|
|
anchors.verticalCenter: searchBox.verticalCenter
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: d.headerMargin
|
|
visible: !skinToneEmoji.expandSkinColorOptions
|
|
emojiId: "1f590" + ((settings.skinColor !== "" && visible) ? ("-" + settings.skinColor) : "")
|
|
MouseArea {
|
|
cursorShape: Qt.PointingHandCursor
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
skinToneEmoji.expandSkinColorOptions = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StatusBaseText {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: d.headerMargin
|
|
Layout.topMargin: 8
|
|
Layout.bottomMargin: 4
|
|
font.weight: Font.Medium
|
|
color: Theme.palette.secondaryText
|
|
font.pixelSize: Theme.additionalTextSize
|
|
text: d.searchStringLowercase ? (d.filteredModel.count ? qsTr("Search Results") : qsTr("No results found"))
|
|
: emojiGrid.currentCategory
|
|
font.capitalization: Font.AllUppercase
|
|
}
|
|
|
|
StatusGridView {
|
|
id: emojiGrid
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.leftMargin: d.headerMargin
|
|
|
|
readonly property string currentCategory: {
|
|
const item = emojiGrid.itemAt(contentX, contentY + (contentY !== originY ? cellHeight : 0)) // taking the 2nd row; 1st might be split between 2 categories
|
|
return !!item ? item.category : root.emojiModel.recentCategoryName
|
|
}
|
|
readonly property string currentCategoryIndex: root.emojiModel.categories.indexOf(currentCategory)
|
|
|
|
model: d.filteredModel
|
|
|
|
cellWidth: d.imageWidth + d.imageMargin * 2
|
|
cellHeight: d.imageWidth + d.imageMargin * 2
|
|
|
|
ScrollBar.vertical: StatusScrollBar {
|
|
policy: ScrollBar.AsNeeded
|
|
}
|
|
|
|
delegate: Item {
|
|
readonly property string category: model.category
|
|
|
|
width: emojiGrid.cellWidth
|
|
height: emojiGrid.cellHeight
|
|
|
|
StatusEmoji {
|
|
objectName: "statusEmoji_" + model.shortname.replace(/:/g, "")
|
|
anchors.centerIn: parent
|
|
width: d.imageWidth
|
|
height: d.imageWidth
|
|
emojiId: model.unicode
|
|
|
|
MouseArea {
|
|
cursorShape: Qt.PointingHandCursor
|
|
anchors.fill: parent
|
|
onClicked: root.addEmoji(model.unicode)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
Layout.fillWidth: true
|
|
height: 40
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: StatusQUtils.Emoji.emojiJSON.emojiCategories
|
|
|
|
StatusTabBarIconButton {
|
|
icon.name: modelData
|
|
highlighted: !!d.searchStringLowercase ? index === 0 : index == emojiGrid.currentCategoryIndex
|
|
onClicked: {
|
|
const offset = d.filteredModel.mapFromSource(root.emojiModel.getCategoryOffset(index))
|
|
emojiGrid.positionViewAtIndex(offset, GridView.Beginning)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|