LightmapViewer.qml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // Copyright (C) 2025 The Qt Company Ltd.
  2. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
  3. import QtQuick
  4. import QtQuick.Controls
  5. import QtQuick.Layouts
  6. import QtQuick.Window
  7. import QtQuick.Dialogs
  8. import QtQuick3D
  9. import QtQuick3D.Helpers
  10. import QtQuick3D.lightmapviewer
  11. import LightmapFile 1.0
  12. ApplicationWindow {
  13. width: 1024
  14. height: 768
  15. visible: true
  16. title: qsTr("Lightmap Viewer")
  17. id: window
  18. property var selectedEntry: listView.model.length ? listView.model[0] : null
  19. property real imageZoom: 1
  20. property real imageCenterX: 0
  21. property real imageCenterY: 0
  22. function isImage(entry) {
  23. return entry && entry.kind === "image"
  24. }
  25. function isMesh(entry) {
  26. return entry && entry.kind === "mesh"
  27. }
  28. Dialog {
  29. id: sceneMetadataDialog
  30. modal: true
  31. standardButtons: Dialog.NoButton
  32. x: Math.round((window.width - width) / 2)
  33. y: Math.round((window.height - height) / 2)
  34. visible: false
  35. width: 220
  36. height: 360
  37. contentItem: SceneMetadataView {}
  38. }
  39. header: ToolBar {
  40. RowLayout {
  41. Button {
  42. text: qsTr("Open Lightmap...")
  43. onClicked: fileDialog.open()
  44. }
  45. Rectangle {
  46. width: 1
  47. color: "darkgray"
  48. Layout.fillHeight: true
  49. Layout.alignment: Qt.AlignVCenter
  50. }
  51. Button {
  52. text: qsTr("Scene Metadata...")
  53. onClicked: sceneMetadataDialog.open()
  54. }
  55. Label {
  56. text: "Zoom: " + window.imageZoom.toFixed(1)
  57. }
  58. Rectangle {
  59. width: 1
  60. color: "darkgray"
  61. Layout.fillHeight: true
  62. Layout.alignment: Qt.AlignVCenter
  63. }
  64. Switch {
  65. id: alphaSwitch
  66. padding: 0
  67. checked: true
  68. text: "Alpha"
  69. }
  70. Rectangle {
  71. width: 1
  72. color: "darkgray"
  73. Layout.fillHeight: true
  74. Layout.alignment: Qt.AlignVCenter
  75. }
  76. Text {
  77. text: "Path: " + LightmapFile.source
  78. }
  79. }
  80. }
  81. FileDialog {
  82. id: fileDialog
  83. onAccepted: {
  84. LightmapFile.source = selectedFile
  85. LightmapFile.loadData()
  86. }
  87. }
  88. Shortcut {
  89. sequences: [StandardKey.Open]
  90. onActivated: {
  91. fileDialog.open()
  92. }
  93. }
  94. SplitView {
  95. anchors.fill: parent
  96. orientation: Qt.Horizontal
  97. focus: true
  98. Keys.onPressed: event => {
  99. if (event.key === Qt.Key_Up) {
  100. listView.currentIndex = Math.max(
  101. 0, listView.currentIndex - 1)
  102. selectedEntry = listView.model[listView.currentIndex]
  103. } else if (event.key === Qt.Key_Down) {
  104. listView.currentIndex = Math.min(
  105. listView.model.length - 1,
  106. listView.currentIndex + 1)
  107. selectedEntry = listView.model[listView.currentIndex]
  108. }
  109. }
  110. SplitView {
  111. id: leftSplit
  112. SplitView.preferredWidth: 220
  113. SplitView.minimumWidth: 120
  114. orientation: Qt.Vertical
  115. Item {
  116. id: metaArea
  117. SplitView.preferredHeight: 120
  118. anchors.left: parent.left
  119. anchors.right: parent.right
  120. ColumnLayout {
  121. anchors.fill: parent
  122. spacing: 4
  123. Pane {
  124. id: metaPane
  125. Layout.fillWidth: true
  126. Layout.fillHeight: true
  127. clip: true
  128. ScrollView {
  129. anchors.fill: parent
  130. ColumnLayout {
  131. id: metadataColumn
  132. Layout.fillWidth: true
  133. spacing: 4
  134. Repeater {
  135. model: LightmapFile.metadataFor(selectedEntry)
  136. delegate: RowLayout {
  137. width: metadataColumn.width
  138. spacing: 8
  139. Label {
  140. text: (modelData.key ?? "—") + ":"
  141. font.bold: true
  142. }
  143. Label {
  144. text: modelData.value
  145. !== undefined ? String(
  146. modelData.value) : "—"
  147. Layout.fillWidth: true
  148. }
  149. }
  150. }
  151. }
  152. }
  153. }
  154. }
  155. }
  156. ListView {
  157. id: listView
  158. clip: true
  159. spacing: 2
  160. highlightMoveVelocity: -1
  161. highlightMoveDuration: 1
  162. model: LightmapFile.dataList
  163. property var sectionExpanded: ({})
  164. section.property: "owner"
  165. section.criteria: ViewSection.FullString
  166. section.delegate: Rectangle {
  167. width: listView.width
  168. height: 26
  169. color: Qt.rgba(0, 0, 0, 0.05)
  170. radius: 4
  171. Row {
  172. anchors.fill: parent
  173. anchors.leftMargin: 8
  174. anchors.rightMargin: 8
  175. spacing: 6
  176. anchors.verticalCenter: parent.verticalCenter
  177. Text {
  178. text: (listView.sectionExpanded[section] === false) ? "▸" : "▾"
  179. verticalAlignment: Text.AlignVCenter
  180. }
  181. Text {
  182. text: section
  183. font.bold: true
  184. verticalAlignment: Text.AlignVCenter
  185. }
  186. }
  187. MouseArea {
  188. anchors.fill: parent
  189. onClicked: {
  190. listView.sectionExpanded[section]
  191. = !(listView.sectionExpanded[section] !== false)
  192. listView.sectionExpanded = Object.assign(
  193. {}, listView.sectionExpanded)
  194. }
  195. }
  196. }
  197. delegate: Item {
  198. width: listView.width
  199. property bool isExpanded: listView.sectionExpanded[modelData.owner] !== false
  200. height: isExpanded ? Math.max(
  201. 24, rowText.implicitHeight + 6) : 0
  202. opacity: isExpanded ? 1 : 0
  203. Behavior on height {
  204. NumberAnimation {
  205. duration: 120
  206. easing.type: Easing.OutCubic
  207. }
  208. }
  209. Behavior on opacity {
  210. NumberAnimation {
  211. duration: 120
  212. easing.type: Easing.OutCubic
  213. }
  214. }
  215. Row {
  216. anchors.fill: parent
  217. anchors.leftMargin: 16
  218. anchors.rightMargin: 8
  219. anchors.verticalCenter: parent.verticalCenter
  220. Text {
  221. id: rowText
  222. text: modelData.display
  223. elide: Text.ElideRight
  224. }
  225. }
  226. MouseArea {
  227. anchors.fill: parent
  228. hoverEnabled: true
  229. enabled: isExpanded
  230. onClicked: {
  231. listView.currentIndex = index
  232. selectedEntry = modelData
  233. }
  234. }
  235. }
  236. highlight: Rectangle {
  237. color: Qt.rgba(76 / 255, 134 / 255, 191 / 255, 0.25)
  238. radius: 6
  239. anchors.margins: 2
  240. }
  241. ScrollBar.vertical: ScrollBar {}
  242. }
  243. }
  244. Item {
  245. id: rightSplit
  246. SplitView.fillWidth: true
  247. SplitView.fillHeight: true
  248. // These are toggled based on what is currently selected
  249. Loader {
  250. id: imageLoader
  251. anchors.fill: parent
  252. sourceComponent: ImageViewer {}
  253. active: true
  254. visible: isImage(selectedEntry)
  255. enabled: visible
  256. }
  257. Loader {
  258. id: meshLoader
  259. anchors.fill: parent
  260. sourceComponent: MeshViewer {}
  261. active: true
  262. visible: isMesh(selectedEntry)
  263. enabled: visible
  264. }
  265. }
  266. }
  267. DropArea {
  268. id: dropArea
  269. anchors.fill: parent
  270. onEntered: drag => {
  271. drag.accept(Qt.LinkAction)
  272. }
  273. // Just take first url if several
  274. onDropped: drop => {
  275. if (drop.hasUrls) {
  276. LightmapFile.source = drop.urls[0]
  277. LightmapFile.loadData()
  278. }
  279. }
  280. }
  281. }