Main.qml 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Window 2.15
  4. import StandardOfIron 1.0
  5. ApplicationWindow {
  6. id: mainWindow
  7. property alias gameView: gameViewItem
  8. property bool menuVisible: true
  9. property bool gameStarted: false
  10. property bool gamePaused: false
  11. property bool edgeScrollDisabled: false
  12. property string missionAnnouncementText: ""
  13. width: 1280
  14. height: 720
  15. visibility: Window.FullScreen
  16. visible: true
  17. title: qsTr("Standard of Iron - RTS Game")
  18. color: Theme.bg
  19. GameView {
  20. id: gameViewItem
  21. anchors.fill: parent
  22. z: 0
  23. focus: !mainWindow.menuVisible
  24. visible: gameStarted
  25. }
  26. HUD {
  27. id: hud
  28. anchors.fill: parent
  29. z: 1
  30. visible: !mainWindow.menuVisible && gameStarted
  31. onActiveFocusChanged: {
  32. if (activeFocus)
  33. gameViewItem.forceActiveFocus();
  34. }
  35. onPauseToggled: {
  36. mainWindow.gamePaused = !mainWindow.gamePaused;
  37. gameViewItem.setPaused(mainWindow.gamePaused);
  38. gameViewItem.forceActiveFocus();
  39. }
  40. onSpeedChanged: function(speed) {
  41. gameViewItem.setGameSpeed(speed);
  42. gameViewItem.forceActiveFocus();
  43. }
  44. onCommandModeChanged: function(mode) {
  45. console.log("Main: Command mode changed to:", mode);
  46. if (typeof game !== 'undefined') {
  47. console.log("Main: Setting game.cursor_mode property to", mode);
  48. game.cursor_mode = mode;
  49. } else {
  50. console.log("Main: game is undefined");
  51. }
  52. gameViewItem.forceActiveFocus();
  53. }
  54. onRecruit: function(unitType) {
  55. if (typeof game !== 'undefined' && game.recruit_near_selected)
  56. game.recruit_near_selected(unitType);
  57. gameViewItem.forceActiveFocus();
  58. }
  59. onReturnToMainMenuRequested: {
  60. mainWindow.menuVisible = true;
  61. }
  62. }
  63. Rectangle {
  64. id: missionAnnouncementToast
  65. anchors.horizontalCenter: parent.horizontalCenter
  66. anchors.top: parent.top
  67. anchors.topMargin: 26
  68. z: 12
  69. visible: gameStarted && opacity > 0.01
  70. opacity: 0
  71. radius: 8
  72. color: "#cc1f1f1f"
  73. border.color: "#d9d9d9"
  74. border.width: 1
  75. width: Math.min(parent.width * 0.7, 700)
  76. height: missionAnnouncementLabel.implicitHeight + 22
  77. Text {
  78. id: missionAnnouncementLabel
  79. anchors.centerIn: parent
  80. width: parent.width - 28
  81. text: mainWindow.missionAnnouncementText
  82. color: "#f5f5f5"
  83. font.pixelSize: 18
  84. font.bold: true
  85. horizontalAlignment: Text.AlignHCenter
  86. wrapMode: Text.WordWrap
  87. }
  88. Behavior on opacity {
  89. NumberAnimation {
  90. duration: 220
  91. }
  92. }
  93. }
  94. Timer {
  95. id: missionAnnouncementTimer
  96. interval: 3600
  97. repeat: false
  98. onTriggered: missionAnnouncementToast.opacity = 0
  99. }
  100. Rectangle {
  101. id: pauseOverlay
  102. anchors.fill: parent
  103. z: 10
  104. visible: mainWindow.gamePaused && gameStarted
  105. color: "#80000000"
  106. Rectangle {
  107. anchors.centerIn: parent
  108. width: 300
  109. height: 150
  110. color: "#2c3e50"
  111. radius: 8
  112. border.color: "#34495e"
  113. border.width: 2
  114. Column {
  115. anchors.centerIn: parent
  116. spacing: 20
  117. Text {
  118. text: qsTr("PAUSED")
  119. color: "#ecf0f1"
  120. font.pixelSize: 36
  121. font.bold: true
  122. anchors.horizontalCenter: parent.horizontalCenter
  123. }
  124. Text {
  125. text: qsTr("Press Space to resume")
  126. color: "#bdc3c7"
  127. font.pixelSize: 14
  128. anchors.horizontalCenter: parent.horizontalCenter
  129. }
  130. }
  131. }
  132. }
  133. LoadScreen {
  134. id: load_screen
  135. anchors.fill: parent
  136. z: 15
  137. is_loading: (typeof game !== 'undefined') ? game.is_loading : false
  138. progress: (typeof game !== 'undefined') ? game.loading_progress : 0
  139. stage_text: (typeof game !== 'undefined') ? game.loading_stage_text : "Loading..."
  140. Connections {
  141. function onIs_loading_changed() {
  142. if (!game.is_loading)
  143. load_screen.complete_loading();
  144. }
  145. target: game
  146. }
  147. }
  148. MainMenu {
  149. id: mainMenu
  150. anchors.fill: parent
  151. z: 20
  152. visible: mainWindow.menuVisible
  153. gameStarted: mainWindow.gameStarted
  154. Component.onCompleted: {
  155. if (mainWindow.menuVisible)
  156. mainMenu.forceActiveFocus();
  157. }
  158. onVisibleChanged: {
  159. if (visible) {
  160. mainMenu.forceActiveFocus();
  161. gameViewItem.focus = false;
  162. } else if (gameStarted) {
  163. gameViewItem.forceActiveFocus();
  164. }
  165. }
  166. onOpenSkirmish: function() {
  167. mapSelect.visible = true;
  168. mainWindow.menuVisible = false;
  169. }
  170. onOpenCampaign: function() {
  171. campaign_screen.visible = true;
  172. mainWindow.menuVisible = false;
  173. }
  174. onSaveGame: function() {
  175. if (mainWindow.gameStarted) {
  176. saveGamePanel.visible = true;
  177. mainWindow.menuVisible = false;
  178. }
  179. }
  180. onLoadSave: function() {
  181. loadGamePanel.visible = true;
  182. mainWindow.menuVisible = false;
  183. }
  184. onOpenSettings: function() {
  185. settingsPanel.visible = true;
  186. mainWindow.menuVisible = false;
  187. }
  188. onOpenObjectives: function() {
  189. objectivesPanel.visible = true;
  190. mainWindow.menuVisible = false;
  191. }
  192. onExitRequested: function() {
  193. if (typeof game !== 'undefined' && game.exit_game)
  194. game.exit_game();
  195. }
  196. }
  197. MapSelect {
  198. id: mapSelect
  199. anchors.fill: parent
  200. z: 21
  201. visible: false
  202. onVisibleChanged: {
  203. if (visible) {
  204. mapSelect.forceActiveFocus();
  205. gameViewItem.focus = false;
  206. }
  207. }
  208. onMapChosen: function(map_path, playerConfigs) {
  209. console.log("Main: onMapChosen received", map_path, "with", playerConfigs.length, "player configs");
  210. if (typeof game !== 'undefined' && game.start_skirmish)
  211. game.start_skirmish(map_path, playerConfigs);
  212. mapSelect.visible = false;
  213. mainWindow.menuVisible = false;
  214. mainWindow.gameStarted = true;
  215. mainWindow.gamePaused = false;
  216. gameViewItem.forceActiveFocus();
  217. }
  218. onCancelled: function() {
  219. mapSelect.visible = false;
  220. mainWindow.menuVisible = true;
  221. }
  222. }
  223. CampaignScreen {
  224. id: campaign_screen
  225. anchors.fill: parent
  226. z: 21
  227. visible: false
  228. onVisibleChanged: {
  229. if (visible) {
  230. campaign_screen.forceActiveFocus();
  231. gameViewItem.focus = false;
  232. }
  233. }
  234. onMission_selected: function(campaign_id, mission_id) {
  235. console.log("Main: Campaign mission selected:", campaign_id + "/" + mission_id);
  236. if (typeof game !== 'undefined' && game.start_campaign_mission) {
  237. game.start_campaign_mission(campaign_id + "/" + mission_id);
  238. campaign_screen.visible = false;
  239. mainWindow.menuVisible = false;
  240. mainWindow.gameStarted = true;
  241. mainWindow.gamePaused = false;
  242. gameViewItem.forceActiveFocus();
  243. }
  244. }
  245. onCancelled: function() {
  246. campaign_screen.visible = false;
  247. mainWindow.menuVisible = true;
  248. }
  249. }
  250. SaveGamePanel {
  251. id: saveGamePanel
  252. anchors.fill: parent
  253. z: 22
  254. visible: false
  255. onVisibleChanged: {
  256. if (visible) {
  257. saveGamePanel.forceActiveFocus();
  258. gameViewItem.focus = false;
  259. }
  260. }
  261. onSaveRequested: function(slotName) {
  262. console.log("Main: Save requested for slot:", slotName);
  263. if (typeof game !== 'undefined' && game.save_gameToSlot)
  264. game.save_gameToSlot(slotName);
  265. saveGamePanel.visible = false;
  266. mainWindow.menuVisible = true;
  267. }
  268. onCancelled: function() {
  269. saveGamePanel.visible = false;
  270. mainWindow.menuVisible = true;
  271. }
  272. }
  273. LoadGamePanel {
  274. id: loadGamePanel
  275. anchors.fill: parent
  276. z: 22
  277. visible: false
  278. onVisibleChanged: {
  279. if (visible) {
  280. loadGamePanel.forceActiveFocus();
  281. gameViewItem.focus = false;
  282. }
  283. }
  284. onLoadRequested: function(slotName) {
  285. console.log("Main: Load requested for slot:", slotName);
  286. if (typeof game !== 'undefined' && game.load_game_from_slot) {
  287. game.load_game_from_slot(slotName);
  288. loadGamePanel.visible = false;
  289. mainWindow.menuVisible = false;
  290. mainWindow.gameStarted = true;
  291. mainWindow.gamePaused = false;
  292. gameViewItem.forceActiveFocus();
  293. }
  294. }
  295. onCancelled: function() {
  296. loadGamePanel.visible = false;
  297. mainWindow.menuVisible = true;
  298. }
  299. }
  300. SettingsPanel {
  301. id: settingsPanel
  302. anchors.fill: parent
  303. z: 22
  304. visible: false
  305. onVisibleChanged: {
  306. if (visible) {
  307. settingsPanel.forceActiveFocus();
  308. gameViewItem.focus = false;
  309. }
  310. }
  311. onCancelled: function() {
  312. settingsPanel.visible = false;
  313. mainWindow.menuVisible = true;
  314. }
  315. }
  316. ObjectivesPanel {
  317. id: objectivesPanel
  318. anchors.fill: parent
  319. z: 22
  320. visible: false
  321. onVisibleChanged: {
  322. if (visible) {
  323. objectivesPanel.forceActiveFocus();
  324. gameViewItem.focus = false;
  325. }
  326. }
  327. onCloseRequested: function() {
  328. objectivesPanel.visible = false;
  329. if (typeof game !== 'undefined' && typeof game.is_campaign_mission !== 'undefined' && game.is_campaign_mission && mainWindow.gameStarted) {
  330. mainWindow.gamePaused = false;
  331. gameViewItem.setPaused(false);
  332. gameViewItem.forceActiveFocus();
  333. } else {
  334. mainWindow.menuVisible = true;
  335. }
  336. }
  337. }
  338. Item {
  339. id: edgeScrollOverlay
  340. property real horzThreshold: 12
  341. property real horzMaxSpeed: 0.15
  342. property real vertThreshold: 10
  343. property real vertMaxSpeed: 0.01
  344. property real xPos: -1
  345. property real yPos: -1
  346. property int verticalShift: 6
  347. function inHudZone(x, y) {
  348. var topH = (typeof hud !== 'undefined' && hud && hud.topPanelHeight) ? hud.topPanelHeight : 0;
  349. var bottomH = (typeof hud !== 'undefined' && hud && hud.bottomPanelHeight) ? hud.bottomPanelHeight : 0;
  350. if (y < topH)
  351. return true;
  352. if (y > (height - bottomH))
  353. return true;
  354. return false;
  355. }
  356. anchors.fill: parent
  357. z: 2
  358. visible: !mainWindow.menuVisible && !mapSelect.visible
  359. enabled: visible
  360. MouseArea {
  361. anchors.fill: parent
  362. hoverEnabled: true
  363. acceptedButtons: Qt.NoButton
  364. propagateComposedEvents: true
  365. preventStealing: false
  366. onPositionChanged: function(mouse) {
  367. edgeScrollOverlay.xPos = mouse.x;
  368. edgeScrollOverlay.yPos = mouse.y;
  369. if (typeof game !== 'undefined' && game.set_hover_at_screen) {
  370. if (!edgeScrollOverlay.inHudZone(mouse.x, mouse.y))
  371. game.set_hover_at_screen(mouse.x, mouse.y);
  372. else
  373. game.set_hover_at_screen(-1, -1);
  374. }
  375. if (typeof game !== 'undefined' && game.is_placing_formation && game.on_formation_mouse_move) {
  376. if (!edgeScrollOverlay.inHudZone(mouse.x, mouse.y))
  377. game.on_formation_mouse_move(mouse.x, mouse.y);
  378. }
  379. if (typeof game !== 'undefined' && game.is_placing_construction && game.on_construction_mouse_move) {
  380. if (!edgeScrollOverlay.inHudZone(mouse.x, mouse.y))
  381. game.on_construction_mouse_move(mouse.x, mouse.y);
  382. }
  383. }
  384. onWheel: function(w) {
  385. if (typeof game !== 'undefined' && game.is_placing_formation && game.on_formation_scroll) {
  386. var dy = (w.angleDelta ? w.angleDelta.y / 120 : w.delta / 120);
  387. if (dy !== 0)
  388. game.on_formation_scroll(dy);
  389. w.accepted = true;
  390. return ;
  391. }
  392. w.accepted = false;
  393. }
  394. onEntered: function() {
  395. edgeScrollTimer.start();
  396. if (typeof game !== 'undefined' && game.set_hover_at_screen) {
  397. if (!edgeScrollOverlay.inHudZone(edgeScrollOverlay.xPos, edgeScrollOverlay.yPos))
  398. game.set_hover_at_screen(edgeScrollOverlay.xPos, edgeScrollOverlay.yPos);
  399. else
  400. game.set_hover_at_screen(-1, -1);
  401. }
  402. }
  403. onExited: function() {
  404. edgeScrollTimer.stop();
  405. edgeScrollOverlay.xPos = -1;
  406. edgeScrollOverlay.yPos = -1;
  407. if (typeof game !== 'undefined' && game.set_hover_at_screen)
  408. game.set_hover_at_screen(-1, -1);
  409. }
  410. }
  411. Timer {
  412. id: edgeScrollTimer
  413. interval: 16
  414. repeat: true
  415. onTriggered: {
  416. if (typeof game === 'undefined')
  417. return ;
  418. const w = edgeScrollOverlay.width;
  419. const h = edgeScrollOverlay.height;
  420. const x = edgeScrollOverlay.xPos;
  421. const y = edgeScrollOverlay.yPos;
  422. if (x < 0 || y < 0)
  423. return ;
  424. if (mainWindow.edgeScrollDisabled) {
  425. if (game.set_hover_at_screen)
  426. game.set_hover_at_screen(-1, -1);
  427. return ;
  428. }
  429. if (game.set_hover_at_screen)
  430. game.set_hover_at_screen(x, y);
  431. const th = edgeScrollOverlay.horzThreshold;
  432. const tv = edgeScrollOverlay.vertThreshold;
  433. const clamp = function clamp(v, lo, hi) {
  434. return Math.max(lo, Math.min(hi, v));
  435. };
  436. const dl = x;
  437. const dr = w - x;
  438. const dt = y;
  439. const db = h - y;
  440. const il = clamp(1 - dl / th, 0, 1);
  441. const ir = clamp(1 - dr / th, 0, 1);
  442. const iu = clamp(1 - dt / tv, 0, 1);
  443. const id = clamp(1 - db / tv, 0, 1);
  444. if (il === 0 && ir === 0 && iu === 0 && id === 0)
  445. return ;
  446. const curveH = function curveH(a) {
  447. return a * a;
  448. };
  449. const curveV = function curveV(a) {
  450. return a * a * a;
  451. };
  452. const rawDx = (curveH(ir) - curveH(il)) * edgeScrollOverlay.horzMaxSpeed;
  453. const rawDz = (curveV(iu) - curveV(id)) * edgeScrollOverlay.vertMaxSpeed;
  454. const dx = rawDx / edgeScrollOverlay.horzMaxSpeed;
  455. const dz = rawDz / edgeScrollOverlay.vertMaxSpeed;
  456. if (dx !== 0 || dz !== 0)
  457. game.camera_move(dx, dz);
  458. }
  459. }
  460. }
  461. Dialog {
  462. id: errorDialog
  463. anchors.centerIn: parent
  464. width: Math.min(parent.width * 0.6, 500)
  465. title: "Error"
  466. modal: true
  467. standardButtons: Dialog.Ok
  468. onAccepted: {
  469. if (game)
  470. game.clear_error();
  471. }
  472. contentItem: Rectangle {
  473. color: "#2a2a2a"
  474. implicitHeight: errorText.implicitHeight + 40
  475. Text {
  476. id: errorText
  477. anchors.centerIn: parent
  478. width: parent.width - 40
  479. text: game ? game.last_error : ""
  480. color: "#ffcccc"
  481. wrapMode: Text.WordWrap
  482. font.pixelSize: 14
  483. }
  484. }
  485. }
  486. Connections {
  487. function onLast_error_changed() {
  488. if (game.last_error !== "")
  489. errorDialog.open();
  490. }
  491. target: game
  492. }
  493. Connections {
  494. function onCampaign_mission_changed() {
  495. if (typeof game !== 'undefined' && typeof game.is_campaign_mission !== 'undefined' && game.is_campaign_mission && !game.is_loading) {
  496. mainWindow.gamePaused = true;
  497. gameViewItem.setPaused(true);
  498. objectivesPanel.visible = true;
  499. }
  500. }
  501. target: game
  502. }
  503. Connections {
  504. function onMission_announcement(text) {
  505. if (!text || !mainWindow.gameStarted)
  506. return ;
  507. mainWindow.missionAnnouncementText = text;
  508. missionAnnouncementToast.opacity = 1;
  509. missionAnnouncementTimer.restart();
  510. }
  511. target: game
  512. }
  513. }