MainMenu.qml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.3
  4. import QtQuick.Window 2.15
  5. import StandardOfIron 1.0
  6. Item {
  7. id: root
  8. property bool gameStarted: false
  9. signal openSkirmish()
  10. signal openCampaign()
  11. signal openObjectives()
  12. signal openSettings()
  13. signal loadSave()
  14. signal saveGame()
  15. signal exitRequested()
  16. anchors.fill: parent
  17. z: 10
  18. focus: true
  19. Keys.onPressed: function(event) {
  20. if (event.key === Qt.Key_Down) {
  21. var newIndex = container.selectedIndex + 1;
  22. while (newIndex < menuModel.count) {
  23. var m = menuModel.get(newIndex);
  24. if (!m.requiresGame || root.gameStarted) {
  25. container.selectedIndex = newIndex;
  26. break;
  27. }
  28. newIndex++;
  29. }
  30. event.accepted = true;
  31. } else if (event.key === Qt.Key_Up) {
  32. var newIndex = container.selectedIndex - 1;
  33. while (newIndex >= 0) {
  34. var m = menuModel.get(newIndex);
  35. if (!m.requiresGame || root.gameStarted) {
  36. container.selectedIndex = newIndex;
  37. break;
  38. }
  39. newIndex--;
  40. }
  41. event.accepted = true;
  42. } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
  43. var m = menuModel.get(container.selectedIndex);
  44. if (m.requiresGame && !root.gameStarted) {
  45. event.accepted = true;
  46. return ;
  47. }
  48. if (m.idStr === "skirmish")
  49. root.openSkirmish();
  50. else if (m.idStr === "campaign")
  51. root.openCampaign();
  52. else if (m.idStr === "objectives")
  53. root.openObjectives();
  54. else if (m.idStr === "save")
  55. root.saveGame();
  56. else if (m.idStr === "load")
  57. root.loadSave();
  58. else if (m.idStr === "settings")
  59. root.openSettings();
  60. else if (m.idStr === "exit")
  61. root.exitRequested();
  62. event.accepted = true;
  63. } else if (event.key === Qt.Key_Escape) {
  64. if (typeof mainWindow !== 'undefined' && mainWindow.menuVisible && mainWindow.gameStarted) {
  65. mainWindow.menuVisible = false;
  66. event.accepted = true;
  67. }
  68. }
  69. }
  70. Rectangle {
  71. anchors.fill: parent
  72. color: Theme.dim
  73. }
  74. Rectangle {
  75. id: container
  76. property int selectedIndex: 0
  77. width: Math.min(parent.width * 0.78, 1100)
  78. height: Math.min(parent.height * 0.78, 700)
  79. anchors.centerIn: parent
  80. radius: Theme.radiusPanel
  81. color: Theme.panelBase
  82. border.color: Theme.panelBr
  83. border.width: 1
  84. opacity: 0.98
  85. clip: true
  86. GridLayout {
  87. id: grid
  88. anchors.fill: parent
  89. anchors.margins: Theme.spacingXLarge
  90. rowSpacing: Theme.spacingMedium
  91. columnSpacing: 18
  92. columns: parent.width > 900 ? 2 : 1
  93. ColumnLayout {
  94. Layout.preferredWidth: parent.width > 900 ? parent.width * 0.45 : parent.width
  95. spacing: Theme.spacingLarge
  96. ColumnLayout {
  97. spacing: Theme.spacingSmall
  98. Label {
  99. text: qsTr("STANDARD OF IRON")
  100. color: Theme.textMain
  101. font.pointSize: Theme.fontSizeHero
  102. font.bold: true
  103. horizontalAlignment: Text.AlignLeft
  104. Layout.fillWidth: true
  105. elide: Label.ElideRight
  106. }
  107. Label {
  108. text: qsTr("A tiny but ambitious RTS")
  109. color: Theme.textSub
  110. font.pointSize: Theme.fontSizeMedium
  111. horizontalAlignment: Text.AlignLeft
  112. Layout.fillWidth: true
  113. elide: Label.ElideRight
  114. }
  115. }
  116. ListModel {
  117. id: menuModel
  118. ListElement {
  119. idStr: "skirmish"
  120. title: QT_TR_NOOP("Play — Skirmish")
  121. subtitle: QT_TR_NOOP("Select a map and start")
  122. requiresGame: false
  123. }
  124. ListElement {
  125. idStr: "campaign"
  126. title: QT_TR_NOOP("Play — Campaign")
  127. subtitle: QT_TR_NOOP("Story missions and battles")
  128. requiresGame: false
  129. }
  130. ListElement {
  131. idStr: "objectives"
  132. title: QT_TR_NOOP("Objectives")
  133. subtitle: QT_TR_NOOP("View current mission objectives")
  134. requiresGame: true
  135. }
  136. ListElement {
  137. idStr: "save"
  138. title: QT_TR_NOOP("Save Game")
  139. subtitle: QT_TR_NOOP("Save your current progress")
  140. requiresGame: true
  141. }
  142. ListElement {
  143. idStr: "load"
  144. title: QT_TR_NOOP("Load Game")
  145. subtitle: QT_TR_NOOP("Resume a previous game")
  146. requiresGame: false
  147. }
  148. ListElement {
  149. idStr: "settings"
  150. title: QT_TR_NOOP("Settings")
  151. subtitle: QT_TR_NOOP("Adjust graphics & controls")
  152. requiresGame: false
  153. }
  154. ListElement {
  155. idStr: "exit"
  156. title: QT_TR_NOOP("Exit")
  157. subtitle: QT_TR_NOOP("Quit the game")
  158. requiresGame: false
  159. }
  160. }
  161. Repeater {
  162. model: menuModel
  163. delegate: Item {
  164. id: menuItem
  165. property int idx: index
  166. property bool itemEnabled: !model.requiresGame || root.gameStarted
  167. Layout.fillWidth: true
  168. Layout.preferredHeight: container.width > 900 ? 64 : 56
  169. Rectangle {
  170. anchors.fill: parent
  171. radius: Theme.radiusLarge
  172. clip: true
  173. color: container.selectedIndex === idx ? Theme.selectedBg : menuItemMouse.containsPress ? Theme.hoverBg : Qt.rgba(0, 0, 0, 0)
  174. border.width: 1
  175. border.color: container.selectedIndex === idx ? Theme.selectedBr : Theme.cardBorder
  176. opacity: itemEnabled ? 1 : 0.4
  177. RowLayout {
  178. anchors.fill: parent
  179. anchors.margins: Theme.spacingSmall
  180. spacing: Theme.spacingMedium
  181. Item {
  182. Layout.fillWidth: true
  183. Layout.preferredWidth: 1
  184. }
  185. ColumnLayout {
  186. Layout.fillWidth: true
  187. spacing: Theme.spacingTiny
  188. Text {
  189. text: qsTr(model.title)
  190. Layout.fillWidth: true
  191. elide: Text.ElideRight
  192. color: itemEnabled ? (container.selectedIndex === idx ? Theme.textMain : Theme.textBright) : Theme.textDim
  193. font.pointSize: Theme.fontSizeLarge
  194. font.bold: container.selectedIndex === idx
  195. }
  196. Text {
  197. text: qsTr(model.subtitle)
  198. Layout.fillWidth: true
  199. elide: Text.ElideRight
  200. color: itemEnabled ? (container.selectedIndex === idx ? Theme.accentBright : Theme.textSubLite) : Theme.textHint
  201. font.pointSize: Theme.fontSizeSmall
  202. }
  203. }
  204. Text {
  205. text: "›"
  206. font.pointSize: Theme.fontSizeTitle
  207. color: itemEnabled ? (container.selectedIndex === idx ? Theme.textMain : Theme.textHint) : Theme.textDim
  208. opacity: itemEnabled ? 1 : 0.3
  209. }
  210. }
  211. Behavior on color {
  212. ColorAnimation {
  213. duration: Theme.animNormal
  214. }
  215. }
  216. Behavior on border.color {
  217. ColorAnimation {
  218. duration: Theme.animNormal
  219. }
  220. }
  221. }
  222. MouseArea {
  223. id: menuItemMouse
  224. anchors.fill: parent
  225. hoverEnabled: true
  226. acceptedButtons: Qt.LeftButton
  227. cursorShape: itemEnabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
  228. onEntered: {
  229. if (itemEnabled)
  230. container.selectedIndex = idx;
  231. }
  232. onClicked: {
  233. if (!itemEnabled)
  234. return ;
  235. if (model.idStr === "skirmish")
  236. root.openSkirmish();
  237. else if (model.idStr === "campaign")
  238. root.openCampaign();
  239. else if (model.idStr === "objectives")
  240. root.openObjectives();
  241. else if (model.idStr === "save")
  242. root.saveGame();
  243. else if (model.idStr === "load")
  244. root.loadSave();
  245. else if (model.idStr === "settings")
  246. root.openSettings();
  247. else if (model.idStr === "exit")
  248. root.exitRequested();
  249. }
  250. }
  251. }
  252. }
  253. Item {
  254. Layout.fillHeight: true
  255. }
  256. RowLayout {
  257. spacing: Theme.spacingSmall
  258. Label {
  259. text: qsTr("v0.9 — prototype")
  260. color: Theme.textDim
  261. font.pointSize: Theme.fontSizeSmall
  262. }
  263. Item {
  264. Layout.fillWidth: true
  265. }
  266. Label {
  267. text: Qt.formatDateTime(new Date(), "yyyy-MM-dd")
  268. color: Theme.textHint
  269. font.pointSize: Theme.fontSizeSmall
  270. elide: Label.ElideRight
  271. }
  272. }
  273. }
  274. Rectangle {
  275. color: Qt.rgba(0, 0, 0, 0)
  276. radius: Theme.radiusMedium
  277. Layout.preferredWidth: parent.width > 900 ? parent.width * 0.45 : parent.width
  278. ColumnLayout {
  279. anchors.fill: parent
  280. anchors.margins: Theme.spacingSmall
  281. spacing: Theme.spacingMedium
  282. Rectangle {
  283. id: promo
  284. color: Theme.cardBase
  285. radius: Theme.radiusLarge
  286. border.color: Theme.border
  287. border.width: 1
  288. Layout.preferredHeight: 260
  289. clip: true
  290. ColumnLayout {
  291. anchors.fill: parent
  292. anchors.margins: Theme.spacingMedium
  293. spacing: Theme.spacingSmall
  294. Label {
  295. text: qsTr("Featured")
  296. color: Theme.accent
  297. font.pointSize: Theme.fontSizeMedium
  298. Layout.fillWidth: true
  299. elide: Label.ElideRight
  300. }
  301. Label {
  302. text: qsTr("Skirmish Mode")
  303. color: Theme.textMain
  304. font.pointSize: Theme.fontSizeTitle
  305. font.bold: true
  306. Layout.fillWidth: true
  307. elide: Label.ElideRight
  308. }
  309. Text {
  310. text: qsTr("Pick a map, adjust your forces and jump into battle. Modern controls and responsive UI.")
  311. color: Theme.textSubLite
  312. wrapMode: Text.WordWrap
  313. maximumLineCount: 3
  314. elide: Text.ElideRight
  315. Layout.fillWidth: true
  316. }
  317. }
  318. }
  319. Rectangle {
  320. color: Theme.cardBase
  321. radius: Theme.radiusLarge
  322. border.color: Theme.border
  323. border.width: 1
  324. Layout.preferredHeight: 120
  325. clip: true
  326. ColumnLayout {
  327. anchors.fill: parent
  328. anchors.margins: Theme.spacingSmall
  329. spacing: Theme.spacingSmall
  330. Label {
  331. text: qsTr("Tips")
  332. color: Theme.accent
  333. font.pointSize: Theme.fontSizeMedium
  334. Layout.fillWidth: true
  335. elide: Label.ElideRight
  336. }
  337. Text {
  338. text: qsTr("Hover menu items or use Up/Down and Enter to navigate. Play opens map selection.")
  339. color: Theme.textSubLite
  340. wrapMode: Text.WordWrap
  341. maximumLineCount: 3
  342. elide: Text.ElideRight
  343. Layout.fillWidth: true
  344. }
  345. }
  346. }
  347. }
  348. }
  349. }
  350. }
  351. }