251 lines
9.7 KiB
QML
251 lines
9.7 KiB
QML
import QtQuick
|
||
import QtQuick.Controls 2.15
|
||
|
||
// Выпадающий список версий: сверху установленные, ниже через разделитель
|
||
// весь каталог Mojang, сверху поле поиска.
|
||
//
|
||
// Это Item с Popup, а не ComboBox: editable-ComboBox привязывает поле ввода
|
||
// к currentText и запускает своё автодополнение, а при активном фильтре
|
||
// currentIndex перестаёт указывать в исходную модель.
|
||
Item {
|
||
id: vcombo
|
||
|
||
property var catalog: []
|
||
property string selectedId: ""
|
||
property string placeholder: qsTr("Выберите версию")
|
||
property string filterText: ""
|
||
signal aboutToOpen()
|
||
|
||
implicitHeight: 36
|
||
height: implicitHeight
|
||
|
||
// Фильтр по номеру версии. ~900 строк за нажатие — доли миллисекунды,
|
||
// а делегаты ListView создаёт только для видимых строк.
|
||
function filterEntries(needle) {
|
||
const query = needle.trim().toLowerCase()
|
||
if (query === "")
|
||
return vcombo.catalog
|
||
const out = []
|
||
for (var i = 0; i < vcombo.catalog.length; ++i) {
|
||
const entry = vcombo.catalog[i]
|
||
if (entry.selectable === false)
|
||
continue
|
||
if (entry.search.indexOf(query) !== -1)
|
||
out.push(entry)
|
||
}
|
||
return out
|
||
}
|
||
|
||
readonly property var visibleEntries: vcombo.filterEntries(vcombo.filterText)
|
||
|
||
function openPopup() {
|
||
vcombo.aboutToOpen()
|
||
vcombo.filterText = ""
|
||
vcomboFilter.text = ""
|
||
vcomboPopup.open()
|
||
vcomboFilter.forceActiveFocus()
|
||
}
|
||
|
||
Rectangle {
|
||
id: vcomboField
|
||
anchors.fill: parent
|
||
color: "#2a2a2a"
|
||
radius: 6
|
||
border.color: vcomboPopup.opened ? "#91B315" : "#444444"
|
||
border.width: 1
|
||
|
||
Text {
|
||
anchors.fill: parent
|
||
leftPadding: 10
|
||
rightPadding: 32
|
||
verticalAlignment: Text.AlignVCenter
|
||
elide: Text.ElideRight
|
||
color: vcombo.selectedId === "" ? "#666666" : "#ffffff"
|
||
text: vcombo.selectedId === "" ? vcombo.placeholder : vcombo.selectedId
|
||
}
|
||
|
||
// Та же стрелка, что у остальных списков окна.
|
||
Image {
|
||
width: 10; height: 10
|
||
x: vcomboField.width - width - 12
|
||
y: (vcomboField.height - height) / 2
|
||
source: vcomboPopup.opened ? "images/Profile_Box/Asset_23.svg"
|
||
: "images/Profile_Box/Asset_24.svg"
|
||
rotation: 180
|
||
sourceSize.width: 10; sourceSize.height: 10
|
||
fillMode: Image.PreserveAspectFit
|
||
}
|
||
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
onClicked: vcombo.openPopup()
|
||
}
|
||
}
|
||
|
||
Popup {
|
||
id: vcomboPopup
|
||
y: vcombo.height + 2
|
||
width: vcombo.width
|
||
padding: 1
|
||
height: 44 + Math.min(vcomboList.contentHeight, 240)
|
||
|
||
background: Rectangle {
|
||
color: "#2a2a2a"
|
||
radius: 6
|
||
border.color: "#444444"
|
||
border.width: 1
|
||
}
|
||
|
||
contentItem: Item {
|
||
TextField {
|
||
id: vcomboFilter
|
||
anchors.top: parent.top
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.margins: 4
|
||
height: 30
|
||
placeholderText: qsTr("Поиск версии…")
|
||
color: "#ffffff"
|
||
placeholderTextColor: "#666666"
|
||
background: Rectangle {
|
||
color: "#232323"
|
||
radius: 5
|
||
border.color: vcomboFilter.activeFocus ? "#91B315" : "#444444"
|
||
border.width: 1
|
||
}
|
||
onTextChanged: {
|
||
vcombo.filterText = text
|
||
vcomboList.currentIndex = vcomboList.firstSelectable(0, 1)
|
||
}
|
||
Keys.onEscapePressed: vcomboPopup.close()
|
||
Keys.onDownPressed: vcomboList.step(1)
|
||
Keys.onUpPressed: vcomboList.step(-1)
|
||
Keys.onReturnPressed: vcomboList.acceptCurrent()
|
||
Keys.onEnterPressed: vcomboList.acceptCurrent()
|
||
}
|
||
|
||
ListView {
|
||
id: vcomboList
|
||
anchors.top: vcomboFilter.bottom
|
||
anchors.topMargin: 4
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.bottom: parent.bottom
|
||
clip: true
|
||
model: vcombo.visibleEntries
|
||
ScrollIndicator.vertical: ScrollIndicator {}
|
||
|
||
// Разделитель и служебные строки клавишами пропускаем.
|
||
function firstSelectable(from, delta) {
|
||
for (var i = from; i >= 0 && i < count; i += delta) {
|
||
if (model[i].selectable !== false)
|
||
return i
|
||
}
|
||
return -1
|
||
}
|
||
function step(delta) {
|
||
const next = firstSelectable(currentIndex + delta, delta)
|
||
if (next >= 0) {
|
||
currentIndex = next
|
||
positionViewAtIndex(next, ListView.Contain)
|
||
}
|
||
}
|
||
function acceptCurrent() {
|
||
if (currentIndex < 0 || currentIndex >= count)
|
||
return
|
||
const entry = model[currentIndex]
|
||
if (entry.selectable === false)
|
||
return
|
||
vcombo.selectedId = entry.id
|
||
vcomboPopup.close()
|
||
}
|
||
|
||
delegate: Item {
|
||
id: vcomboRow
|
||
required property var modelData
|
||
required property int index
|
||
|
||
width: vcomboList.width
|
||
height: modelData.kind === "separator" ? 26
|
||
: modelData.kind === "status" ? 30 : 34
|
||
|
||
// Разделитель между установленными и каталогом.
|
||
Item {
|
||
anchors.fill: parent
|
||
visible: vcomboRow.modelData.kind === "separator"
|
||
|
||
Rectangle {
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.margins: 8
|
||
height: 1
|
||
color: "#3a3a3a"
|
||
}
|
||
Rectangle {
|
||
anchors.centerIn: parent
|
||
color: "#2a2a2a"
|
||
width: separatorLabel.implicitWidth + 12
|
||
height: 18
|
||
Text {
|
||
id: separatorLabel
|
||
anchors.centerIn: parent
|
||
text: vcomboRow.modelData.label
|
||
color: "#888888"
|
||
font.pixelSize: 10
|
||
}
|
||
}
|
||
}
|
||
|
||
// «Загрузка списка…» или сообщение о недоступности.
|
||
Text {
|
||
anchors.centerIn: parent
|
||
visible: vcomboRow.modelData.kind === "status"
|
||
text: vcomboRow.modelData.label
|
||
color: "#888888"
|
||
font.pixelSize: 11
|
||
font.italic: true
|
||
}
|
||
|
||
Rectangle {
|
||
anchors.fill: parent
|
||
visible: vcomboRow.modelData.selectable !== false
|
||
color: (vcomboRowArea.containsMouse || vcomboList.currentIndex === vcomboRow.index)
|
||
? "#3a3a3a" : "#2a2a2a"
|
||
|
||
Text {
|
||
anchors.left: parent.left
|
||
anchors.leftMargin: 10
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
width: parent.width - 100
|
||
elide: Text.ElideRight
|
||
text: vcomboRow.modelData.label
|
||
color: "#ffffff"
|
||
font.pixelSize: 12
|
||
}
|
||
Text {
|
||
anchors.right: parent.right
|
||
anchors.rightMargin: 10
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
text: vcomboRow.modelData.installed ? qsTr("установлена")
|
||
: vcomboRow.modelData.type
|
||
color: vcomboRow.modelData.installed ? "#91B315" : "#666666"
|
||
font.pixelSize: 10
|
||
}
|
||
|
||
MouseArea {
|
||
id: vcomboRowArea
|
||
anchors.fill: parent
|
||
hoverEnabled: true
|
||
onClicked: {
|
||
vcombo.selectedId = vcomboRow.modelData.id
|
||
vcomboPopup.close()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|