new builds menu and modloaders implementation
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user