LoadGamePanel.qml 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import QtQml 2.15
  2. import QtQuick 2.15
  3. import QtQuick.Controls 2.15
  4. import QtQuick.Layouts 1.3
  5. import StandardOfIron 1.0
  6. Item {
  7. id: root
  8. signal cancelled()
  9. signal loadRequested(string slotName)
  10. anchors.fill: parent
  11. z: 25
  12. onVisibleChanged: {
  13. if (!visible)
  14. return ;
  15. if (typeof loadListModel !== 'undefined')
  16. loadListModel.loadFromGame();
  17. if (typeof loadListView !== 'undefined')
  18. loadListView.selectedIndex = loadListModel.count > 0 && !loadListModel.get(0).isEmpty ? 0 : -1;
  19. }
  20. Keys.onPressed: function(event) {
  21. if (event.key === Qt.Key_Escape) {
  22. root.cancelled();
  23. event.accepted = true;
  24. } else if (event.key === Qt.Key_Down) {
  25. if (loadListView.selectedIndex < loadListModel.count - 1)
  26. loadListView.selectedIndex++;
  27. event.accepted = true;
  28. } else if (event.key === Qt.Key_Up) {
  29. if (loadListView.selectedIndex > 0)
  30. loadListView.selectedIndex--;
  31. event.accepted = true;
  32. } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
  33. if (loadListView.selectedIndex >= 0 && !loadListModel.get(loadListView.selectedIndex).isEmpty)
  34. root.loadRequested(loadListModel.get(loadListView.selectedIndex).slotName);
  35. event.accepted = true;
  36. }
  37. }
  38. Component.onCompleted: {
  39. forceActiveFocus();
  40. if (loadListModel.count > 0 && !loadListModel.get(0).isEmpty)
  41. loadListView.selectedIndex = 0;
  42. }
  43. Connections {
  44. function onSave_slots_changed() {
  45. if (typeof loadListModel === 'undefined')
  46. return ;
  47. var previousSlot = "";
  48. if (typeof loadListView !== 'undefined' && loadListView.selectedIndex >= 0 && loadListView.selectedIndex < loadListModel.count) {
  49. var current = loadListModel.get(loadListView.selectedIndex);
  50. if (current && !current.isEmpty)
  51. previousSlot = current.slotName;
  52. }
  53. loadListModel.loadFromGame();
  54. if (typeof loadListView === 'undefined')
  55. return ;
  56. var newIndex = -1;
  57. if (previousSlot !== "") {
  58. for (var i = 0; i < loadListModel.count; ++i) {
  59. var slot = loadListModel.get(i);
  60. if (!slot.isEmpty && slot.slotName === previousSlot) {
  61. newIndex = i;
  62. break;
  63. }
  64. }
  65. }
  66. if (newIndex === -1) {
  67. if (loadListModel.count > 0 && !loadListModel.get(0).isEmpty)
  68. newIndex = 0;
  69. }
  70. loadListView.selectedIndex = newIndex;
  71. }
  72. target: typeof game !== 'undefined' ? game : null
  73. }
  74. Rectangle {
  75. anchors.fill: parent
  76. color: Theme.dim
  77. }
  78. Rectangle {
  79. id: container
  80. width: Math.min(parent.width * 0.7, 900)
  81. height: Math.min(parent.height * 0.8, 600)
  82. anchors.centerIn: parent
  83. radius: Theme.radiusPanel
  84. color: Theme.panelBase
  85. border.color: Theme.panelBr
  86. border.width: 1
  87. opacity: 0.98
  88. ColumnLayout {
  89. anchors.fill: parent
  90. anchors.margins: Theme.spacingXLarge
  91. spacing: Theme.spacingLarge
  92. RowLayout {
  93. Layout.fillWidth: true
  94. spacing: Theme.spacingMedium
  95. Label {
  96. text: qsTr("Load Game")
  97. color: Theme.textMain
  98. font.pointSize: Theme.fontSizeHero
  99. font.bold: true
  100. Layout.fillWidth: true
  101. }
  102. StyledButton {
  103. text: qsTr("Cancel")
  104. buttonStyle: "secondary"
  105. onClicked: root.cancelled()
  106. }
  107. }
  108. Rectangle {
  109. Layout.fillWidth: true
  110. Layout.preferredHeight: 1
  111. color: Theme.border
  112. }
  113. Rectangle {
  114. Layout.fillWidth: true
  115. Layout.fillHeight: true
  116. color: Theme.cardBase
  117. border.color: Theme.border
  118. border.width: 1
  119. radius: Theme.radiusLarge
  120. ScrollView {
  121. anchors.fill: parent
  122. anchors.margins: Theme.spacingSmall
  123. clip: true
  124. ListView {
  125. id: loadListView
  126. property int selectedIndex: -1
  127. spacing: Theme.spacingSmall
  128. model: ListModel {
  129. id: loadListModel
  130. function loadFromGame() {
  131. clear();
  132. if (typeof game === 'undefined' || !game.get_save_slots) {
  133. append({
  134. "slotName": qsTr("No saves found"),
  135. "title": "",
  136. "timestamp": 0,
  137. "map_name": "",
  138. "playTime": "",
  139. "thumbnail": "",
  140. "isEmpty": true
  141. });
  142. return ;
  143. }
  144. var slots = game.get_save_slots();
  145. for (var i = 0; i < slots.length; i++) {
  146. append({
  147. "slotName": slots[i].slotName || slots[i].name,
  148. "title": slots[i].title || slots[i].name || slots[i].slotName || "Untitled Save",
  149. "timestamp": slots[i].timestamp,
  150. "map_name": slots[i].map_name || "Unknown Map",
  151. "playTime": slots[i].playTime || "",
  152. "thumbnail": slots[i].thumbnail || "",
  153. "isEmpty": false
  154. });
  155. }
  156. if (count === 0)
  157. append({
  158. "slotName": qsTr("No saves found"),
  159. "title": "",
  160. "timestamp": 0,
  161. "map_name": "",
  162. "playTime": "",
  163. "thumbnail": "",
  164. "isEmpty": true
  165. });
  166. }
  167. Component.onCompleted: {
  168. loadFromGame();
  169. }
  170. }
  171. delegate: Rectangle {
  172. width: loadListView.width
  173. height: model.isEmpty ? 100 : 130
  174. color: loadListView.selectedIndex === index ? Theme.selectedBg : mouseArea.containsMouse ? Theme.hoverBg : Qt.rgba(0, 0, 0, 0)
  175. radius: Theme.radiusMedium
  176. border.color: loadListView.selectedIndex === index ? Theme.selectedBr : Theme.cardBorder
  177. border.width: 1
  178. visible: !model.isEmpty || loadListModel.count === 1
  179. RowLayout {
  180. anchors.fill: parent
  181. anchors.margins: Theme.spacingMedium
  182. spacing: Theme.spacingMedium
  183. Rectangle {
  184. id: loadThumbnail
  185. Layout.preferredWidth: 128
  186. Layout.preferredHeight: 80
  187. radius: Theme.radiusSmall
  188. color: Theme.cardBase
  189. border.color: Theme.cardBorder
  190. border.width: 1
  191. clip: true
  192. visible: !model.isEmpty
  193. Image {
  194. id: loadThumbnailImage
  195. anchors.fill: parent
  196. anchors.margins: 2
  197. fillMode: Image.PreserveAspectCrop
  198. source: model.thumbnail && model.thumbnail.length > 0 ? "data:image/png;base64," + model.thumbnail : ""
  199. visible: source !== ""
  200. }
  201. Label {
  202. anchors.centerIn: parent
  203. visible: !loadThumbnailImage.visible
  204. text: qsTr("No Preview")
  205. color: Theme.textHint
  206. font.pointSize: Theme.fontSizeTiny
  207. }
  208. }
  209. ColumnLayout {
  210. Layout.fillWidth: true
  211. spacing: Theme.spacingTiny
  212. visible: !model.isEmpty
  213. Label {
  214. text: model.title
  215. color: Theme.textMain
  216. font.pointSize: Theme.fontSizeLarge
  217. font.bold: true
  218. Layout.fillWidth: true
  219. elide: Label.ElideRight
  220. }
  221. Label {
  222. text: qsTr("Slot: %1").arg(model.slotName)
  223. color: Theme.textSub
  224. font.pointSize: Theme.fontSizeSmall
  225. Layout.fillWidth: true
  226. elide: Label.ElideRight
  227. }
  228. Label {
  229. text: model.map_name
  230. color: Theme.textSub
  231. font.pointSize: Theme.fontSizeMedium
  232. Layout.fillWidth: true
  233. elide: Label.ElideRight
  234. }
  235. RowLayout {
  236. Layout.fillWidth: true
  237. spacing: Theme.spacingLarge
  238. Label {
  239. text: qsTr("Last saved: %1").arg(Qt.formatDateTime(new Date(model.timestamp), "yyyy-MM-dd hh:mm:ss"))
  240. color: Theme.textHint
  241. font.pointSize: Theme.fontSizeSmall
  242. Layout.fillWidth: true
  243. elide: Label.ElideRight
  244. }
  245. Label {
  246. text: model.playTime !== "" ? qsTr("Play time: %1").arg(model.playTime) : ""
  247. color: Theme.textHint
  248. font.pointSize: Theme.fontSizeSmall
  249. visible: model.playTime !== ""
  250. }
  251. }
  252. }
  253. Label {
  254. text: model.slotName
  255. color: Theme.textDim
  256. font.pointSize: Theme.fontSizeLarge
  257. Layout.fillWidth: true
  258. horizontalAlignment: Text.AlignHCenter
  259. visible: model.isEmpty
  260. }
  261. StyledButton {
  262. text: qsTr("Load")
  263. buttonStyle: "small"
  264. visible: !model.isEmpty
  265. onClicked: {
  266. root.loadRequested(model.slotName);
  267. }
  268. }
  269. StyledButton {
  270. text: qsTr("Delete")
  271. buttonStyle: "danger"
  272. implicitWidth: 80
  273. visible: !model.isEmpty
  274. onClicked: {
  275. confirmDeleteDialog.slotName = model.slotName;
  276. confirmDeleteDialog.slotIndex = index;
  277. confirmDeleteDialog.open();
  278. }
  279. }
  280. }
  281. MouseArea {
  282. id: mouseArea
  283. anchors.fill: parent
  284. hoverEnabled: true
  285. enabled: !model.isEmpty
  286. onClicked: {
  287. loadListView.selectedIndex = index;
  288. }
  289. onDoubleClicked: {
  290. if (!model.isEmpty)
  291. root.loadRequested(model.slotName);
  292. }
  293. }
  294. }
  295. }
  296. }
  297. }
  298. RowLayout {
  299. Layout.fillWidth: true
  300. spacing: Theme.spacingMedium
  301. Item {
  302. Layout.fillWidth: true
  303. }
  304. Label {
  305. text: loadListView.selectedIndex >= 0 && !loadListModel.get(loadListView.selectedIndex).isEmpty ? qsTr("Selected: %1").arg(loadListModel.get(loadListView.selectedIndex).title) : qsTr("Select a save to load")
  306. color: Theme.textSub
  307. font.pointSize: Theme.fontSizeMedium
  308. }
  309. StyledButton {
  310. text: qsTr("Load Selected")
  311. enabled: loadListView.selectedIndex >= 0 && !loadListModel.get(loadListView.selectedIndex).isEmpty
  312. onClicked: {
  313. if (loadListView.selectedIndex >= 0 && !loadListModel.get(loadListView.selectedIndex).isEmpty)
  314. root.loadRequested(loadListModel.get(loadListView.selectedIndex).slotName);
  315. }
  316. }
  317. }
  318. }
  319. }
  320. Dialog {
  321. id: confirmDeleteDialog
  322. property string slotName: ""
  323. property int slotIndex: -1
  324. anchors.centerIn: parent
  325. width: Math.min(parent.width * 0.5, 400)
  326. title: qsTr("Confirm Delete")
  327. modal: true
  328. standardButtons: Dialog.Yes | Dialog.No
  329. onAccepted: {
  330. if (typeof game !== 'undefined' && game.delete_save_slot) {
  331. if (game.delete_save_slot(slotName)) {
  332. loadListModel.remove(slotIndex);
  333. if (loadListModel.count === 0)
  334. loadListModel.append({
  335. "slotName": qsTr("No saves found"),
  336. "title": "",
  337. "timestamp": 0,
  338. "map_name": "",
  339. "playTime": "",
  340. "thumbnail": "",
  341. "isEmpty": true
  342. });
  343. if (loadListView.selectedIndex >= loadListModel.count)
  344. loadListView.selectedIndex = loadListModel.count > 0 && !loadListModel.get(0).isEmpty ? loadListModel.count - 1 : -1;
  345. }
  346. }
  347. }
  348. contentItem: Rectangle {
  349. color: Theme.cardBase
  350. implicitHeight: warningText.implicitHeight + 40
  351. ColumnLayout {
  352. anchors.fill: parent
  353. anchors.margins: Theme.spacingMedium
  354. spacing: Theme.spacingMedium
  355. Label {
  356. id: warningText
  357. text: qsTr("Are you sure you want to delete the save:\n\"%1\"?\n\nThis action cannot be undone.").arg(confirmDeleteDialog.slotName)
  358. color: Theme.textMain
  359. wrapMode: Text.WordWrap
  360. Layout.fillWidth: true
  361. font.pointSize: Theme.fontSizeMedium
  362. }
  363. }
  364. }
  365. }
  366. }