Files
2026-04-29 07:19:21 +03:00

326 lines
10 KiB
QML

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import QtQuick.Dialogs
import QtQuick3D
import QtQuick3D.Helpers
import QtQuick3D.lightmapviewer
import LightmapFile 1.0
ApplicationWindow {
width: 1024
height: 768
visible: true
title: qsTr("Lightmap Viewer")
id: window
property var selectedEntry: listView.model.length ? listView.model[0] : null
property real imageZoom: 1
property real imageCenterX: 0
property real imageCenterY: 0
function isImage(entry) {
return entry && entry.kind === "image"
}
function isMesh(entry) {
return entry && entry.kind === "mesh"
}
Dialog {
id: sceneMetadataDialog
modal: true
standardButtons: Dialog.NoButton
x: Math.round((window.width - width) / 2)
y: Math.round((window.height - height) / 2)
visible: false
width: 220
height: 360
contentItem: SceneMetadataView {}
}
header: ToolBar {
RowLayout {
Button {
text: qsTr("Open Lightmap...")
onClicked: fileDialog.open()
}
Rectangle {
width: 1
color: "darkgray"
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
}
Button {
text: qsTr("Scene Metadata...")
onClicked: sceneMetadataDialog.open()
}
Label {
text: "Zoom: " + window.imageZoom.toFixed(1)
}
Rectangle {
width: 1
color: "darkgray"
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
}
Switch {
id: alphaSwitch
padding: 0
checked: true
text: "Alpha"
}
Rectangle {
width: 1
color: "darkgray"
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
}
Text {
text: "Path: " + LightmapFile.source
}
}
}
FileDialog {
id: fileDialog
onAccepted: {
LightmapFile.source = selectedFile
LightmapFile.loadData()
}
}
Shortcut {
sequences: [StandardKey.Open]
onActivated: {
fileDialog.open()
}
}
SplitView {
anchors.fill: parent
orientation: Qt.Horizontal
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Up) {
listView.currentIndex = Math.max(
0, listView.currentIndex - 1)
selectedEntry = listView.model[listView.currentIndex]
} else if (event.key === Qt.Key_Down) {
listView.currentIndex = Math.min(
listView.model.length - 1,
listView.currentIndex + 1)
selectedEntry = listView.model[listView.currentIndex]
}
}
SplitView {
id: leftSplit
SplitView.preferredWidth: 220
SplitView.minimumWidth: 120
orientation: Qt.Vertical
Item {
id: metaArea
SplitView.preferredHeight: 120
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
anchors.fill: parent
spacing: 4
Pane {
id: metaPane
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ScrollView {
anchors.fill: parent
ColumnLayout {
id: metadataColumn
Layout.fillWidth: true
spacing: 4
Repeater {
model: LightmapFile.metadataFor(selectedEntry)
delegate: RowLayout {
width: metadataColumn.width
spacing: 8
Label {
text: (modelData.key ?? "—") + ":"
font.bold: true
}
Label {
text: modelData.value
!== undefined ? String(
modelData.value) : "—"
Layout.fillWidth: true
}
}
}
}
}
}
}
}
ListView {
id: listView
clip: true
spacing: 2
highlightMoveVelocity: -1
highlightMoveDuration: 1
model: LightmapFile.dataList
property var sectionExpanded: ({})
section.property: "owner"
section.criteria: ViewSection.FullString
section.delegate: Rectangle {
width: listView.width
height: 26
color: Qt.rgba(0, 0, 0, 0.05)
radius: 4
Row {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 6
anchors.verticalCenter: parent.verticalCenter
Text {
text: (listView.sectionExpanded[section] === false) ? "▸" : "▾"
verticalAlignment: Text.AlignVCenter
}
Text {
text: section
font.bold: true
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.sectionExpanded[section]
= !(listView.sectionExpanded[section] !== false)
listView.sectionExpanded = Object.assign(
{}, listView.sectionExpanded)
}
}
}
delegate: Item {
width: listView.width
property bool isExpanded: listView.sectionExpanded[modelData.owner] !== false
height: isExpanded ? Math.max(
24, rowText.implicitHeight + 6) : 0
opacity: isExpanded ? 1 : 0
Behavior on height {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
Row {
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
Text {
id: rowText
text: modelData.display
elide: Text.ElideRight
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: isExpanded
onClicked: {
listView.currentIndex = index
selectedEntry = modelData
}
}
}
highlight: Rectangle {
color: Qt.rgba(76 / 255, 134 / 255, 191 / 255, 0.25)
radius: 6
anchors.margins: 2
}
ScrollBar.vertical: ScrollBar {}
}
}
Item {
id: rightSplit
SplitView.fillWidth: true
SplitView.fillHeight: true
// These are toggled based on what is currently selected
Loader {
id: imageLoader
anchors.fill: parent
sourceComponent: ImageViewer {}
active: true
visible: isImage(selectedEntry)
enabled: visible
}
Loader {
id: meshLoader
anchors.fill: parent
sourceComponent: MeshViewer {}
active: true
visible: isMesh(selectedEntry)
enabled: visible
}
}
}
DropArea {
id: dropArea
anchors.fill: parent
onEntered: drag => {
drag.accept(Qt.LinkAction)
}
// Just take first url if several
onDropped: drop => {
if (drop.hasUrls) {
LightmapFile.source = drop.urls[0]
LightmapFile.loadData()
}
}
}
}