matrixion/resources/qml/Completer.qml
Reilly Brogan c9f1a449d8
linux: Use kirigami mouse handling if available
Qt6 changed the mouse scroll wheel handling for QtQuick to a type that mimics how touch pads/screens work, which most people find feels very poor. KDE fixes this by creating a custom type which re-implements the QtWidgets handling (see https://invent.kde.org/frameworks/kirigami/-/merge_requests/415).

On Matrix Nico has expressed a desire not to have to deal with compiling Kirigami for Windows and Mac, which is understandable. Linux users on the other hand almost always have kirigami available in their package repos which sidesteps that particular issue. We can search for Kirigami at build time and if present define a QML context property to allow it to be used, which should fix this issue for Linux users at least.

Helps with nheko-reborn/nheko#1819 (which won't be completely resolved until this is working for Windows and Mac as well).

Signed-off-by: Reilly Brogan <reilly@reillybrogan.com>
2024-11-05 15:37:54 -06:00

301 lines
11 KiB
QML

// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./ui"
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import im.nheko 1.0
Control {
id: popup
property int avatarHeight: 24
property int avatarWidth: 24
property bool bottomToTop: true
property bool centerRowContent: true
property var completer
property string completerName
property alias count: listView.count
property alias currentIndex: listView.currentIndex
property bool fullWidth: false
property string roomId
property int rowMargin: 0
property int rowSpacing: Nheko.paddingSmall
signal completionClicked(string completion)
signal completionSelected(string id)
function changeCompleter() {
if (completerName) {
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId : room.roomId));
completer.setSearchString("");
} else {
completer = undefined;
}
currentIndex = -1;
}
function currentCompletion() {
if (currentIndex > -1 && currentIndex < listView.count)
return completer.completionAt(currentIndex);
else
return null;
}
function currentUserid() {
if (popup.completerName == "user") {
return listView.itemAtIndex(currentIndex).modelData.userid;
} else {
return "";
}
}
function down() {
if (bottomToTop)
up_();
else
down_();
}
function down_() {
currentIndex = currentIndex + 1;
if (currentIndex >= listView.count)
currentIndex = -1;
}
function finishCompletion() {
if (popup.completerName == "room")
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
else if (popup.completerName == "user")
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
}
function up() {
if (bottomToTop)
down_();
else
up_();
}
function up_() {
currentIndex = currentIndex - 1;
if (currentIndex == -2)
currentIndex = listView.count - 1;
}
bottomPadding: 1
leftPadding: 1
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
rightPadding: 1
topPadding: 1
background: Rectangle {
border.color: palette.mid
color: palette.base
}
contentItem: ListView {
id: listView
Loader {
source: NHEKO_USE_KIRIGAMI ? "components/KirigamiWheelHandler.qml" : ""
}
clip: true
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
highlightFollowsCurrentItem: true
// If we have fewer than 7 items, just use the list view's content height.
// Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins
// on each side of a row, 1px of padding above the first item and below the last item, and nominally
// some kind of content height. avatarHeight is used for just about every delegate, so we're using
// that until we find something better. Put is all together and you have the formula below!
implicitHeight: Math.min(contentHeight, 6 * rowSpacing + 7 * (popup.avatarHeight + 2 * rowMargin))
// Broken, see https://bugreports.qt.io/browse/QTBUG-102811
//reuseItems: true
implicitWidth: Math.max(listView.contentItem.childrenRect.width, 20)
model: completer
pixelAligned: true
spacing: rowSpacing
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
delegate: Rectangle {
property variant modelData: model
ListView.delayRemove: true
color: model.index == popup.currentIndex ? palette.highlight : palette.base
height: (chooser.child?.implicitHeight ?? 0) + 2 * popup.rowMargin
implicitWidth: popup.fullWidth ? ListView.view.width : chooser.child.implicitWidth + 4
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
popup.completionClicked(completer.completionAt(model.index));
if (popup.completerName == "room")
popup.completionSelected(model.roomid);
else if (popup.completerName == "user")
popup.completionSelected(model.userid);
}
onPositionChanged: if (!listView.moving && !deadTimer.running)
popup.currentIndex = model.index
}
Ripple {
color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.5)
}
DelegateChooser {
id: chooser
anchors.fill: parent
anchors.margins: popup.rowMargin
enabled: false
roleValue: popup.completerName
DelegateChoice {
roleValue: "user"
RowLayout {
anchors.centerIn: centerRowContent ? parent : undefined
spacing: rowSpacing
Avatar {
displayName: model.displayName
enabled: false
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.userid
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
text: model.displayName
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: "(" + model.userid + ")"
}
}
}
DelegateChoice {
roleValue: "emoji"
RowLayout {
anchors.centerIn: parent
spacing: rowSpacing
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
font: Settings.emojiFont
text: model.unicode
visible: !!model.unicode
}
Avatar {
crop: false
displayName: model.shortcode
enabled: false
Layout.preferredHeight: popup.avatarHeight
//userid: model.shortcode
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
visible: !model.unicode
Layout.preferredWidth: popup.avatarWidth
}
Label {
Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
text: model.shortcode
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: "(" + model.packname + ")"
}
}
}
DelegateChoice {
roleValue: "command"
RowLayout {
anchors.centerIn: parent
spacing: rowSpacing
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
font.bold: true
text: model.name
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: model.description
}
}
}
DelegateChoice {
roleValue: "room"
RowLayout {
anchors.centerIn: centerRowContent ? parent : undefined
spacing: rowSpacing
Avatar {
displayName: model.roomName
enabled: false
Layout.preferredHeight: popup.avatarHeight
roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: popup.avatarWidth
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
font.italic: model.isTombstoned
font.bold: model.isSpace
font.pixelSize: popup.avatarHeight * 0.5
text: model.roomName
textFormat: Text.RichText
}
}
}
DelegateChoice {
roleValue: "roomAliases"
RowLayout {
anchors.centerIn: parent
spacing: rowSpacing
Avatar {
displayName: model.roomName
enabled: false
Layout.preferredHeight: popup.avatarHeight
roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: popup.avatarWidth
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
font.italic: model.isTombstoned
font.bold: model.isSpace
text: model.roomName
textFormat: Text.RichText
}
Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: "(" + model.roomAlias + ")"
textFormat: Text.RichText
}
}
}
}
}
onContentYChanged: deadTimer.restart()
Timer {
id: deadTimer
interval: 50
}
}
onCompleterNameChanged: changeCompleter()
onRoomIdChanged: changeCompleter()
}