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()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|