Files
noctalia-shell/Widgets/NComboBox.qml
ItsLemmy b6c1f6e90a autofmt
2025-11-30 14:36:23 -05:00

210 lines
6.0 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
RowLayout {
id: root
property real minimumWidth: 200 * Style.uiScaleRatio
property real popupHeight: 180 * Style.uiScaleRatio
property string label: ""
property string description: ""
property var model
property string currentKey: ""
property string placeholder: ""
readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio
signal selected(string key)
spacing: Style.marginL
Layout.fillWidth: true
function itemCount() {
if (!root.model)
return 0;
if (typeof root.model.count === 'number')
return root.model.count;
if (Array.isArray(root.model))
return root.model.length;
return 0;
}
function getItem(index) {
if (!root.model)
return null;
if (typeof root.model.get === 'function')
return root.model.get(index);
if (Array.isArray(root.model))
return root.model[index];
return null;
}
function findIndexByKey(key) {
for (var i = 0; i < itemCount(); i++) {
var item = getItem(i);
if (item && item.key === key)
return i;
}
return -1;
}
NLabel {
label: root.label
description: root.description
}
ComboBox {
id: combo
Layout.minimumWidth: root.minimumWidth
Layout.preferredHeight: root.preferredHeight
model: model
currentIndex: findIndexByKey(currentKey)
onActivated: {
var item = getItem(combo.currentIndex);
if (item && item.key !== undefined)
root.selected(item.key);
}
background: Rectangle {
implicitWidth: Style.baseWidgetSize * 3.75
implicitHeight: preferredHeight
color: Color.mSurface
border.color: combo.activeFocus ? Color.mSecondary : Color.mOutline
border.width: Style.borderS
radius: Style.radiusM
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
}
contentItem: NText {
leftPadding: Style.marginL
rightPadding: combo.indicator.width + Style.marginL
pointSize: Style.fontSizeM
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
color: (combo.currentIndex >= 0 && combo.currentIndex < itemCount()) ? Color.mOnSurface : Color.mOnSurfaceVariant
text: (combo.currentIndex >= 0 && combo.currentIndex < itemCount()) ? (getItem(combo.currentIndex) ? getItem(combo.currentIndex).name : root.placeholder) : root.placeholder
}
indicator: NIcon {
x: combo.width - width - Style.marginM
y: combo.topPadding + (combo.availableHeight - height) / 2
icon: "caret-down"
pointSize: Style.fontSizeL
}
popup: Popup {
y: combo.height
implicitWidth: combo.width - Style.marginM
implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * 2)
padding: Style.marginM
contentItem: NListView {
model: combo.popup.visible ? root.model : null
implicitHeight: contentHeight
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
delegate: ItemDelegate {
property var parentComboBox: combo
property int itemIndex: index
width: ListView.view ? ListView.view.width : (parentComboBox ? parentComboBox.width - Style.marginM * 3 : 0)
hoverEnabled: true
highlighted: ListView.view.currentIndex === itemIndex
property bool pendingClick: false
Timer {
id: clickRetryTimer
interval: 50
repeat: false
onTriggered: {
if (parent.pendingClick && parent.ListView.view && !parent.ListView.view.flicking && !parent.ListView.view.moving) {
parent.pendingClick = false;
var item = root.getItem(parent.itemIndex);
if (item && item.key !== undefined && parent.parentComboBox) {
root.selected(item.key);
parent.parentComboBox.currentIndex = parent.itemIndex;
parent.parentComboBox.popup.close();
}
} else if (parent.pendingClick) {
restart();
}
}
}
onHoveredChanged: {
if (hovered) {
ListView.view.currentIndex = itemIndex;
}
}
onClicked: {
if (ListView.view && (ListView.view.flicking || ListView.view.moving)) {
ListView.view.cancelFlick();
pendingClick = true;
clickRetryTimer.start();
} else {
var item = root.getItem(itemIndex);
if (item && item.key !== undefined && parentComboBox) {
root.selected(item.key);
parentComboBox.currentIndex = itemIndex;
parentComboBox.popup.close();
}
}
}
background: Rectangle {
anchors.fill: parent
color: highlighted ? Color.mHover : Color.transparent
radius: Style.radiusS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
contentItem: NText {
text: {
var item = root.getItem(index);
return item && item.name ? item.name : "";
}
pointSize: Style.fontSizeM
color: highlighted ? Color.mOnHover : Color.mOnSurface
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
background: Rectangle {
color: Color.mSurfaceVariant
border.color: Color.mOutline
border.width: Style.borderS
radius: Style.radiusM
}
}
Connections {
target: root
function onCurrentKeyChanged() {
combo.currentIndex = root.findIndexByKey(currentKey);
}
}
}
}