Files
minecraft-launcher/VersionCatalogCombo.qml
T

251 lines
9.7 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}
}
}
}
}
}
}
}