| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776 |
- import QtQuick 2.15
- import QtQuick.Controls 2.15
- import QtQuick.Layouts 1.15
- import StandardOfIron 1.0
- Item {
- id: root
- property var mapsModel: (typeof game !== "undefined" && game.available_maps) ? game.available_maps : []
- property bool mapsLoading: (typeof game !== "undefined" && game.maps_loading) ? game.maps_loading : false
- property int selectedMapIndex: -1
- property var selectedMapData: null
- property string selectedMapPath: ""
- property string validationError: ""
- property var availableNations: []
- signal mapChosen(string map_path, var playerConfigs)
- signal cancelled()
- function refreshAvailableNations() {
- if (typeof game !== "undefined" && game.available_nations)
- availableNations = game.available_nations;
- else
- availableNations = [];
- }
- function defaultNationEntry() {
- if (availableNations && availableNations.length > 0)
- return availableNations[0];
- return {
- "id": "roman_republic",
- "name": qsTr("Roman Republic")
- };
- }
- function hasMinimumDistinctTeams() {
- let enabledPlayers = [];
- for (let i = 0; i < playersModel.count; i++) {
- let p = playersModel.get(i);
- if (p.isEnabled)
- enabledPlayers.push(p);
- }
- if (enabledPlayers.length < 2)
- return false;
- let teams = new Set();
- for (let i = 0; i < enabledPlayers.length; i++) {
- teams.add(enabledPlayers[i].team_id);
- }
- return teams.size >= 2;
- }
- function updateValidationError() {
- let enabledCount = 0;
- for (let i = 0; i < playersModel.count; i++) {
- if (playersModel.get(i).isEnabled)
- enabledCount++;
- }
- if (enabledCount < 2)
- validationError = "Need at least 2 enabled players to start";
- else if (!hasMinimumDistinctTeams())
- validationError = "At least two teams must be selected to start a match";
- else
- validationError = "";
- }
- function field(obj, key) {
- return (obj && obj[key] !== undefined) ? String(obj[key]) : "";
- }
- function nationEmblemFor(nationId) {
- if (!nationId || !Theme.nationEmblems)
- return "";
- let emblem = Theme.nationEmblems[nationId];
- if (emblem === undefined || emblem === null)
- return "";
- return String(emblem);
- }
- function getMapData(index) {
- if (!mapsModel || index < 0 || index >= (mapsModel.length || list.count))
- return null;
- let m = (mapsModel.length !== undefined) ? mapsModel[index] : null;
- if (m && m.get)
- return m.get(index);
- return m;
- }
- function initializePlayers(mapData) {
- playersModel.clear();
- if (!mapData || !mapData.player_ids || mapData.player_ids.length === 0)
- return ;
- let humanPlayerId = mapData.player_ids.length > 0 ? mapData.player_ids[0] : 1;
- let defaultNation = defaultNationEntry();
- playersModel.append({
- "player_id": humanPlayerId,
- "playerName": "Player " + (humanPlayerId + 1),
- "colorIndex": 0,
- "colorHex": Theme.playerColors[0].hex,
- "colorName": Theme.playerColors[0].name,
- "team_id": 0,
- "teamIcon": Theme.teamIcons[0],
- "nationId": defaultNation.id,
- "nationName": defaultNation.name,
- "isHuman": true,
- "isEnabled": true
- });
- let cpuId = mapData.player_ids.find(function(id) {
- return id !== humanPlayerId;
- });
- if (cpuId !== undefined)
- addCPU();
- updateValidationError();
- }
- function addCPU() {
- if (!selectedMapData || !selectedMapData.player_ids)
- return ;
- if (playersModel.count >= selectedMapData.player_ids.length)
- return ;
- let usedIds = [];
- for (let i = 0; i < playersModel.count; i++) usedIds.push(playersModel.get(i).player_id)
- let nextId = -1;
- for (let j = 0; j < selectedMapData.player_ids.length; j++) {
- if (usedIds.indexOf(selectedMapData.player_ids[j]) === -1) {
- nextId = selectedMapData.player_ids[j];
- break;
- }
- }
- if (nextId === -1)
- return ;
- let usedColors = [];
- for (let k = 0; k < playersModel.count; k++) usedColors.push(playersModel.get(k).colorIndex)
- let colorIdx = 0;
- for (let c = 0; c < Theme.playerColors.length; c++) {
- if (usedColors.indexOf(c) === -1) {
- colorIdx = c;
- break;
- }
- }
- let defaultTeamId = playersModel.count > 0 ? 1 : 0;
- let defaultNation = defaultNationEntry();
- playersModel.append({
- "player_id": nextId,
- "playerName": "CPU " + nextId,
- "colorIndex": colorIdx,
- "colorHex": Theme.playerColors[colorIdx].hex,
- "colorName": Theme.playerColors[colorIdx].name,
- "team_id": defaultTeamId,
- "teamIcon": Theme.teamIcons[defaultTeamId],
- "nationId": defaultNation.id,
- "nationName": defaultNation.name,
- "isHuman": false,
- "isEnabled": true
- });
- updateValidationError();
- }
- function removePlayer(index) {
- if (index < 0 || index >= playersModel.count)
- return ;
- let p = playersModel.get(index);
- if (p.isHuman)
- return ;
- playersModel.remove(index);
- updateValidationError();
- }
- function cyclePlayerColor(index) {
- if (index < 0 || index >= playersModel.count)
- return ;
- let p = playersModel.get(index);
- let usedColors = [];
- for (let i = 0; i < playersModel.count; i++) {
- if (i !== index)
- usedColors.push(playersModel.get(i).colorIndex);
- }
- let startIdx = p.colorIndex;
- let newIdx = (startIdx + 1) % Theme.playerColors.length;
- let attempts = 0;
- while (usedColors.indexOf(newIdx) !== -1 && attempts < Theme.playerColors.length) {
- newIdx = (newIdx + 1) % Theme.playerColors.length;
- attempts++;
- }
- if (attempts >= Theme.playerColors.length)
- newIdx = (startIdx + 1) % Theme.playerColors.length;
- playersModel.setProperty(index, "colorIndex", newIdx);
- playersModel.setProperty(index, "colorHex", Theme.playerColors[newIdx].hex);
- playersModel.setProperty(index, "colorName", Theme.playerColors[newIdx].name);
- }
- function cyclePlayerTeam(index) {
- if (index < 0 || index >= playersModel.count)
- return ;
- let p = playersModel.get(index);
- let maxTeam = Math.min(8, playersModel.count);
- let newTeamId = (p.team_id + 1) % (maxTeam + 1);
- playersModel.setProperty(index, "team_id", newTeamId);
- playersModel.setProperty(index, "teamIcon", Theme.teamIcons[newTeamId]);
- updateValidationError();
- }
- function cyclePlayerNation(index) {
- if (index < 0 || index >= playersModel.count)
- return ;
- if (!availableNations || availableNations.length === 0)
- return ;
- let p = playersModel.get(index);
- let currentId = p.nationId || availableNations[0].id;
- let nextIndex = 0;
- for (let i = 0; i < availableNations.length; i++) {
- if (availableNations[i].id === currentId) {
- nextIndex = (i + 1) % availableNations.length;
- break;
- }
- }
- let nextNation = availableNations[nextIndex];
- playersModel.setProperty(index, "nationId", nextNation.id);
- playersModel.setProperty(index, "nationName", nextNation.name);
- }
- function togglePlayerEnabled(index) {
- if (index < 0 || index >= playersModel.count)
- return ;
- let p = playersModel.get(index);
- let newEnabled = !p.isEnabled;
- playersModel.setProperty(index, "isEnabled", newEnabled);
- updateValidationError();
- }
- function getPlayerConfigs() {
- let configs = [];
- for (let i = 0; i < playersModel.count; i++) {
- let p = playersModel.get(i);
- if (!p.isEnabled)
- continue;
- let config = {
- "player_id": p.player_id,
- "colorHex": p.colorHex,
- "team_id": p.team_id,
- "nationId": p.nationId,
- "isHuman": p.isHuman
- };
- console.log("MapSelect: Player", p.player_id, "config - Team:", p.team_id, "Color:", p.colorHex, "Nation:", p.nationId, "Human:", p.isHuman);
- configs.push(config);
- }
- return configs;
- }
- function acceptSelection() {
- if (selectedMapIndex < 0 || !selectedMapPath)
- return ;
- let enabledCount = 0;
- for (let i = 0; i < playersModel.count; i++) {
- if (playersModel.get(i).isEnabled)
- enabledCount++;
- }
- if (enabledCount < 2) {
- console.log("MapSelect: Need at least 2 enabled players to start");
- updateValidationError();
- return ;
- }
- if (!hasMinimumDistinctTeams()) {
- console.log("MapSelect: Need at least 2 different teams to start");
- updateValidationError();
- return ;
- }
- validationError = "";
- let configs = getPlayerConfigs();
- console.log("MapSelect: Starting game with", configs.length, "enabled players");
- root.mapChosen(selectedMapPath, configs);
- }
- function playerColorClicked(index) {
- cyclePlayerColor(index);
- }
- function playerTeamClicked(index) {
- cyclePlayerTeam(index);
- }
- function addCPUClicked() {
- addCPU();
- }
- function removePlayerClicked(index) {
- removePlayer(index);
- }
- Component.onCompleted: refreshAvailableNations()
- anchors.fill: parent
- focus: true
- onVisibleChanged: {
- if (visible) {
- root.focus = true;
- selectedMapIndex = -1;
- selectedMapData = null;
- selectedMapPath = "";
- playersModel.clear();
- refreshAvailableNations();
- if (typeof game !== "undefined" && game.start_loading_maps)
- game.start_loading_maps();
- }
- }
- Keys.onPressed: function(event) {
- if (!visible)
- return ;
- if (event.key === Qt.Key_Escape) {
- root.cancelled();
- event.accepted = true;
- } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
- acceptSelection();
- event.accepted = true;
- } else if (event.key === Qt.Key_Down) {
- if (list.count > 0)
- list.currentIndex = Math.min(list.currentIndex + 1, list.count - 1);
- event.accepted = true;
- } else if (event.key === Qt.Key_Up) {
- if (list.count > 0)
- list.currentIndex = Math.max(list.currentIndex - 1, 0);
- event.accepted = true;
- }
- }
- ListModel {
- id: playersModel
- }
- Rectangle {
- anchors.fill: parent
- color: Theme.dim
- }
- Rectangle {
- id: panel
- anchors.centerIn: parent
- width: Math.min(parent.width * 0.9, 1300)
- height: Math.min(parent.height * 0.88, 800)
- radius: Theme.radiusPanel
- color: Theme.panelBase
- border.color: Theme.panelBr
- border.width: 1
- opacity: 0.98
- clip: true
- Item {
- id: left
- width: Math.max(360, Math.min(panel.width * 0.38, 460))
- anchors {
- top: parent.top
- left: parent.left
- bottom: footer.top
- topMargin: Theme.spacingXLarge
- leftMargin: Theme.spacingXLarge
- bottomMargin: Theme.spacingMedium
- }
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 0
- spacing: Theme.spacingMedium
- Text {
- id: leftTitle
- text: qsTr("Maps")
- color: Theme.textMain
- font.pixelSize: 20
- font.bold: true
- Layout.fillWidth: true
- }
- MapPreview {
- id: mapPreviewLeft
- Layout.fillWidth: true
- Layout.preferredHeight: visible ? 240 : 0
- visible: selectedMapData !== null
- mapPath: selectedMapPath
- playerConfigs: getPlayerConfigs()
- }
- Rectangle {
- id: listFrame
- color: Qt.rgba(0, 0, 0, 0)
- radius: Theme.radiusLarge
- border.color: Theme.panelBr
- border.width: 1
- clip: true
- Layout.fillWidth: true
- Layout.fillHeight: (!mapsLoading && list.count > 0)
- ListView {
- id: list
- anchors.fill: parent
- anchors.margins: Theme.spacingSmall
- model: mapsModel
- spacing: Theme.spacingSmall
- currentIndex: (count > 0 ? 0 : -1)
- keyNavigationWraps: false
- boundsBehavior: Flickable.StopAtBounds
- onCurrentIndexChanged: {
- if (currentIndex < 0) {
- selectedMapIndex = -1;
- selectedMapData = null;
- selectedMapPath = "";
- playersModel.clear();
- return ;
- }
- selectedMapIndex = currentIndex;
- selectedMapData = getMapData(currentIndex);
- selectedMapPath = selectedMapData ? (selectedMapData.path || selectedMapData.file || "") : "";
- initializePlayers(selectedMapData);
- }
- delegate: Item {
- id: row
- width: list.width
- height: 72
- MouseArea {
- id: rowMouse
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.LeftButton
- cursorShape: Qt.PointingHandCursor
- onClicked: list.currentIndex = index
- onDoubleClicked: acceptSelection()
- }
- Rectangle {
- id: card
- anchors.fill: parent
- radius: Theme.radiusLarge
- clip: true
- color: rowMouse.containsPress ? Theme.hoverBg : (index === list.currentIndex ? Theme.selectedBg : (rowMouse.containsMouse ? Qt.rgba(1, 1, 1, 0.03) : Qt.rgba(0, 0, 0, 0)))
- border.width: 1
- border.color: (index === list.currentIndex) ? Theme.selectedBr : (rowMouse.containsMouse ? Qt.rgba(1, 1, 1, 0.15) : Theme.thumbBr)
- Rectangle {
- id: thumbWrap
- width: 76
- height: 54
- radius: Theme.radiusMedium
- color: Theme.cardBase
- border.color: Theme.thumbBr
- border.width: 1
- clip: true
- anchors {
- left: parent.left
- leftMargin: Theme.spacingSmall
- verticalCenter: parent.verticalCenter
- }
- Image {
- id: thumbImage
- anchors.fill: parent
- source: (typeof thumbnail !== "undefined" && thumbnail !== "") ? thumbnail : ""
- asynchronous: true
- fillMode: Image.PreserveAspectCrop
- visible: status === Image.Ready
- }
- Rectangle {
- anchors.fill: parent
- color: Theme.cardBase
- visible: !thumbImage.visible
- Text {
- anchors.centerIn: parent
- text: "🗺"
- font.pixelSize: 24
- color: Theme.textDim
- }
- }
- }
- Item {
- height: 54
- anchors {
- left: thumbWrap.right
- right: parent.right
- leftMargin: Theme.spacingSmall
- rightMargin: Theme.spacingSmall
- verticalCenter: parent.verticalCenter
- }
- Text {
- id: map_name
- text: (typeof name !== "undefined") ? String(name) : (typeof modelData === "string" ? modelData : (modelData && modelData.name ? String(modelData.name) : ""))
- color: (index === list.currentIndex) ? Theme.textMain : Theme.textBright
- font.pixelSize: (index === list.currentIndex) ? 17 : 15
- font.bold: (index === list.currentIndex)
- elide: Text.ElideRight
- anchors {
- top: parent.top
- left: parent.left
- right: parent.right
- }
- Behavior on font.pixelSize {
- NumberAnimation {
- duration: Theme.animNormal
- }
- }
- }
- Text {
- text: (typeof description !== "undefined") ? String(description) : (modelData && modelData.description ? String(modelData.description) : "")
- color: (index === list.currentIndex) ? Theme.accentBright : Theme.textSub
- font.pixelSize: 12
- elide: Text.ElideRight
- anchors {
- left: parent.left
- right: parent.right
- bottom: parent.bottom
- }
- }
- Text {
- text: "›"
- font.pointSize: 18
- color: (index === list.currentIndex) ? Theme.textMain : Theme.textHint
- anchors {
- right: parent.right
- rightMargin: 0
- verticalCenter: parent.verticalCenter
- }
- }
- }
- Behavior on color {
- ColorAnimation {
- duration: Theme.animNormal
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animNormal
- }
- }
- }
- }
- }
- }
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: visible
- visible: list.count === 0 && !mapsLoading
- Text {
- text: qsTr("No maps available")
- color: Theme.textSub
- font.pixelSize: 14
- anchors.centerIn: parent
- }
- }
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: visible
- visible: mapsLoading
- Column {
- anchors.centerIn: parent
- spacing: Theme.spacingSmall
- Text {
- text: "⟳"
- font.pixelSize: 24
- color: Theme.accent
- anchors.horizontalCenter: parent.horizontalCenter
- RotationAnimator on rotation {
- from: 0
- to: 360
- duration: 1500
- loops: Animation.Infinite
- running: mapsLoading
- }
- }
- Text {
- text: qsTr("Loading maps...")
- color: Theme.textSub
- font.pixelSize: 12
- anchors.horizontalCenter: parent.horizontalCenter
- }
- }
- }
- }
- }
- Item {
- id: right
- anchors {
- top: parent.top
- bottom: footer.top
- right: parent.right
- left: left.right
- leftMargin: Theme.spacingXLarge
- rightMargin: Theme.spacingXLarge
- topMargin: Theme.spacingXLarge
- bottomMargin: Theme.spacingMedium
- }
- Text {
- id: breadcrumb
- text: selectedMapData ? qsTr("► %1").arg(field(selectedMapData, "name")) : qsTr("Select a map to continue")
- color: selectedMapData ? Theme.accent : Theme.textHint
- font.pixelSize: 13
- font.italic: !selectedMapData
- elide: Text.ElideRight
- anchors {
- top: parent.top
- left: parent.left
- right: parent.right
- }
- }
- Item {
- id: loadingIndicator
- visible: mapsLoading && list.count === 0
- height: 100
- anchors {
- top: breadcrumb.bottom
- left: parent.left
- right: parent.right
- topMargin: Theme.spacingXLarge * 2
- }
- Column {
- anchors.centerIn: parent
- spacing: Theme.spacingMedium
- Text {
- text: "⟳"
- font.pixelSize: 40
- color: Theme.accent
- anchors.horizontalCenter: parent.horizontalCenter
- RotationAnimator on rotation {
- from: 0
- to: 360
- duration: 1500
- loops: Animation.Infinite
- running: loadingIndicator.visible
- }
- }
- Text {
- text: qsTr("Loading maps...")
- color: Theme.textSub
- font.pixelSize: 14
- anchors.horizontalCenter: parent.horizontalCenter
- }
- }
- }
- Item {
- id: loadingSkeleton
- visible: !selectedMapData && !mapsLoading && list.currentIndex >= 0
- height: 200
- anchors {
- top: breadcrumb.bottom
- left: parent.left
- right: parent.right
- topMargin: Theme.spacingMedium
- }
- Column {
- anchors.fill: parent
- spacing: Theme.spacingMedium
- Rectangle {
- width: parent.width * 0.6
- height: 28
- radius: Theme.radiusSmall
- color: Theme.cardBase
- opacity: 0.3
- SequentialAnimation on opacity {
- loops: Animation.Infinite
- running: loadingSkeleton.visible
- NumberAnimation {
- to: 0.6
- duration: 800
- }
- NumberAnimation {
- to: 0.3
- duration: 800
- }
- }
- }
- Rectangle {
- width: parent.width * 0.8
- height: 16
- radius: Theme.radiusSmall
- color: Theme.cardBase
- opacity: 0.3
- SequentialAnimation on opacity {
- loops: Animation.Infinite
- running: loadingSkeleton.visible
- NumberAnimation {
- to: 0.6
- duration: 800
- easing.type: Easing.InOutQuad
- }
- NumberAnimation {
- to: 0.3
- duration: 800
- easing.type: Easing.InOutQuad
- }
- }
- }
- Rectangle {
- width: parent.width * 0.7
- height: 16
- radius: Theme.radiusSmall
- color: Theme.cardBase
- opacity: 0.3
- SequentialAnimation on opacity {
- loops: Animation.Infinite
- running: loadingSkeleton.visible
- NumberAnimation {
- to: 0.6
- duration: 800
- easing.type: Easing.InOutQuad
- }
- NumberAnimation {
- to: 0.3
- duration: 800
- easing.type: Easing.InOutQuad
- }
- }
- }
- Text {
- text: qsTr("Loading map details...")
- color: Theme.textHint
- font.pixelSize: 12
- font.italic: true
- anchors.horizontalCenter: parent.horizontalCenter
- }
- }
- }
- Text {
- id: title
- text: {
- var it = selectedMapData;
- var t = field(it, "name");
- return t || field(it, "path") || qsTr("No Map Selected");
- }
- visible: selectedMapData !== null
- color: Theme.textMain
- font.pixelSize: 24
- font.bold: true
- elide: Text.ElideRight
- anchors {
- top: breadcrumb.bottom
- left: parent.left
- right: parent.right
- topMargin: Theme.spacingSmall
- }
- }
- Text {
- id: descr
- text: field(selectedMapData, "description")
- visible: selectedMapData !== null
- color: Theme.textSubLite
- font.pixelSize: 13
- wrapMode: Text.WordWrap
- maximumLineCount: 2
- lineHeight: 1.3
- anchors {
- top: title.bottom
- left: parent.left
- right: parent.right
- topMargin: Theme.spacingSmall
- }
- }
- Rectangle {
- id: playerConfigPanel
- height: Math.min(240, (playersModel.count * 60) + 90)
- radius: Theme.radiusLarge
- color: Theme.cardBaseA
- border.color: Theme.panelBr
- border.width: 1
- visible: selectedMapData !== null
- anchors {
- top: descr.bottom
- left: parent.left
- right: parent.right
- topMargin: Theme.spacingMedium
- }
- Column {
- spacing: Theme.spacingMedium
- anchors {
- fill: parent
- margins: Theme.spacingMedium + 2
- }
- Row {
- spacing: Theme.spacingSmall + 2
- Text {
- text: qsTr("Players")
- color: Theme.textMain
- font.pixelSize: 17
- font.bold: true
- }
- Rectangle {
- width: 30
- height: 22
- radius: Theme.radiusSmall
- color: Theme.selectedBg
- anchors.verticalCenter: parent.verticalCenter
- Text {
- anchors.centerIn: parent
- text: playersModel.count
- color: Theme.textMain
- font.pixelSize: 13
- font.bold: true
- }
- }
- Text {
- text: qsTr("• Click color/team to cycle")
- color: Theme.textSubLite
- font.pixelSize: 11
- font.italic: true
- anchors.verticalCenter: parent.verticalCenter
- }
- Text {
- text: qsTr("• Click nation tag to change")
- color: Theme.textSubLite
- font.pixelSize: 11
- font.italic: true
- anchors.verticalCenter: parent.verticalCenter
- }
- }
- ListView {
- id: playersList
- width: parent.width
- height: Math.min(200, playersModel.count * 60)
- model: playersModel
- spacing: Theme.spacingMedium
- clip: true
- delegate: Rectangle {
- id: playerCard
- width: playersList.width
- height: 52
- radius: Theme.radiusMedium
- color: playerCardMouse.containsMouse ? Qt.lighter(Theme.cardBaseB, 1.1) : Theme.cardBaseB
- border.color: model.isHuman ? Theme.accent : (playerCardMouse.containsMouse ? Theme.selectedBr : Theme.thumbBr)
- border.width: model.isHuman ? 1.5 : (playerCardMouse.containsMouse ? 1.5 : 1)
- MouseArea {
- id: playerCardMouse
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.NoButton
- }
- Rectangle {
- height: 1
- color: Qt.rgba(1, 1, 1, 0.05)
- anchors {
- left: parent.left
- right: parent.right
- top: parent.top
- }
- }
- Item {
- anchors.fill: parent
- anchors.margins: Theme.spacingSmall + 2
- Rectangle {
- id: enabledCheckbox
- width: 32
- height: 32
- radius: Theme.radiusSmall
- anchors.left: parent.left
- anchors.leftMargin: 4
- anchors.verticalCenter: parent.verticalCenter
- color: enabledCheckMA.containsMouse ? Qt.lighter(Theme.cardBase, 1.2) : Theme.cardBase
- border.color: model.isEnabled ? Theme.accent : Theme.thumbBr
- border.width: enabledCheckMA.containsMouse ? 2 : 1
- ToolTip.visible: enabledCheckMA.containsMouse
- ToolTip.text: model.isEnabled ? qsTr("Disable player (spectator mode)") : qsTr("Enable player")
- Text {
- anchors.centerIn: parent
- text: model.isEnabled ? "✓" : ""
- color: Theme.accent
- font.pixelSize: 18
- font.bold: true
- }
- MouseArea {
- id: enabledCheckMA
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: togglePlayerEnabled(index)
- }
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- Text {
- id: playerNameText
- anchors.left: enabledCheckbox.right
- anchors.leftMargin: Theme.spacingSmall
- anchors.verticalCenter: parent.verticalCenter
- text: model.playerName || ""
- color: model.isEnabled ? (model.isHuman ? Theme.accentBright : Theme.textBright) : Theme.textDim
- font.pixelSize: model.isHuman ? 15 : 14
- font.bold: true
- opacity: model.isEnabled ? 1 : 0.5
- }
- Row {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- spacing: Theme.spacingMedium
- opacity: model.isEnabled ? 1 : 0.4
- Rectangle {
- width: 105
- height: playerCard.height - (Theme.spacingSmall + 2) * 2 - 4
- radius: Theme.radiusSmall + 1
- anchors.verticalCenter: parent.verticalCenter
- color: Theme.cardBase
- border.color: model.colorHex || Theme.textDim
- border.width: colorMA.containsMouse ? 3 : 2
- ToolTip.visible: colorMA.containsMouse
- ToolTip.text: qsTr("Player color: %1 - Click to change").arg(model.colorName || qsTr("Color"))
- Rectangle {
- anchors.fill: parent
- anchors.margins: 1
- radius: parent.radius - 1
- color: "transparent"
- border.color: model.colorHex || Theme.textDim
- border.width: 1
- opacity: 0.3
- }
- Text {
- anchors.centerIn: parent
- text: model.colorName || "Color"
- color: model.colorHex || Theme.textMain
- font.pixelSize: 13
- font.bold: true
- }
- MouseArea {
- id: colorMA
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: cyclePlayerColor(index)
- }
- Rectangle {
- anchors.fill: parent
- radius: parent.radius
- color: model.colorHex || Theme.textDim
- opacity: colorMA.containsMouse ? 0.15 : 0
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- Rectangle {
- readonly property real emblemSize: Math.min(playerCard.height - Theme.spacingSmall * 2, 72)
- property string emblemSource: root.nationEmblemFor(model.nationId)
- width: emblemSize
- height: emblemSize
- radius: Theme.radiusSmall
- anchors.verticalCenter: parent.verticalCenter
- color: nationMA.containsMouse ? Qt.lighter(Theme.cardBaseB, 1.1) : Theme.cardBaseB
- border.color: nationMA.containsMouse ? Theme.selectedBr : Theme.thumbBr
- border.width: nationMA.containsMouse ? 2 : 1
- ToolTip.visible: nationMA.containsMouse
- ToolTip.text: qsTr("Nation: %1 - Click to change").arg(model.nationName || qsTr("Nation"))
- Image {
- anchors.centerIn: parent
- visible: parent.emblemSource !== ""
- source: parent.emblemSource
- width: parent.width * 0.8
- height: width
- fillMode: Image.PreserveAspectFit
- smooth: true
- mipmap: true
- }
- Text {
- anchors.centerIn: parent
- visible: parent.emblemSource === ""
- text: model.nationName || qsTr("Nation")
- color: Theme.textMain
- font.pixelSize: 11
- font.bold: true
- }
- MouseArea {
- id: nationMA
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: cyclePlayerNation(index)
- }
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- Rectangle {
- width: 70
- height: playerCard.height - (Theme.spacingSmall + 2) * 2 - 4
- radius: Theme.radiusSmall
- anchors.verticalCenter: parent.verticalCenter
- color: teamMA.containsMouse ? Qt.lighter(Theme.hoverBg, 1.2) : Theme.hoverBg
- border.color: teamMA.containsMouse ? Theme.selectedBr : Theme.thumbBr
- border.width: teamMA.containsMouse ? 2 : 1
- ToolTip.visible: teamMA.containsMouse
- ToolTip.text: qsTr("Team %1 - Click to change").arg(model.team_id || 0)
- Column {
- anchors.centerIn: parent
- spacing: 2
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: model.teamIcon || "⚪"
- color: Theme.textMain
- font.pixelSize: 20
- font.bold: true
- }
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: qsTr("Team %1").arg(model.team_id || 0)
- color: Theme.textBright
- font.pixelSize: 10
- font.bold: true
- }
- }
- MouseArea {
- id: teamMA
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: cyclePlayerTeam(index)
- }
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- Rectangle {
- width: 36
- height: playerCard.height - (Theme.spacingSmall + 2) * 2 - 4
- radius: Theme.radiusSmall
- anchors.verticalCenter: parent.verticalCenter
- color: removeMA.containsMouse ? Theme.removeColor : Theme.cardBaseA
- border.color: Theme.removeColor
- border.width: removeMA.containsMouse ? 2 : 1
- visible: !model.isHuman
- ToolTip.visible: removeMA.containsMouse
- ToolTip.text: qsTr("Remove player")
- Text {
- anchors.centerIn: parent
- text: "✕"
- color: Theme.textMain
- font.pixelSize: 16
- font.bold: true
- }
- MouseArea {
- id: removeMA
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: removePlayer(index)
- }
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- }
- }
- Behavior on color {
- ColorAnimation {
- duration: Theme.animNormal
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animNormal
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animNormal
- }
- }
- }
- }
- Rectangle {
- width: parent.width
- height: 8
- color: "transparent"
- }
- Button {
- text: qsTr("+ Add CPU")
- enabled: playersModel.count < (selectedMapData && selectedMapData.player_ids ? selectedMapData.player_ids.length : 0)
- onClicked: addCPU()
- hoverEnabled: true
- implicitHeight: 38
- implicitWidth: 120
- ToolTip.visible: addCpuHover.containsMouse && parent.enabled
- ToolTip.text: qsTr("Add AI opponent")
- MouseArea {
- id: addCpuHover
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.NoButton
- cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- }
- contentItem: Text {
- text: parent.text
- font.pixelSize: 13
- font.bold: true
- color: parent.enabled ? Theme.textMain : Theme.textDim
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- background: Rectangle {
- radius: Theme.radiusMedium
- color: {
- if (!parent.enabled)
- return Theme.cardBase;
- if (parent.down)
- return Qt.darker(Theme.addColor, 1.2);
- if (addCpuHover.containsMouse)
- return Qt.lighter(Theme.addColor, 1.2);
- return Theme.addColor;
- }
- border.width: parent.enabled && addCpuHover.containsMouse ? 2 : 1
- border.color: parent.enabled ? Qt.lighter(Theme.addColor, 1.3) : Theme.thumbBr
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- }
- }
- }
- Rectangle {
- id: playerSelectionPanel
- radius: Theme.radiusLarge
- color: Theme.cardBaseA
- border.color: Theme.panelBr
- border.width: 1
- visible: false
- height: playerSelectionContent.height + 20
- anchors {
- top: playerConfigPanel.bottom
- left: parent.left
- right: parent.right
- topMargin: Theme.spacingMedium
- }
- Column {
- id: playerSelectionContent
- spacing: Theme.spacingSmall
- anchors {
- left: parent.left
- right: parent.right
- top: parent.top
- margins: Theme.spacingSmall
- }
- Text {
- text: "Available Player Slots: " + (function() {
- var it = selectedMapData;
- return (it && typeof it.playerCount !== 'undefined') ? it.playerCount : 0;
- })()
- color: Theme.textMain
- font.pixelSize: 14
- font.bold: true
- }
- Text {
- text: "Select your player ID:"
- color: Theme.textSubLite
- font.pixelSize: 12
- }
- Flow {
- width: parent.width
- spacing: Theme.spacingSmall
- Repeater {
- model: {
- var it = selectedMapData;
- return (it && it.player_ids) ? it.player_ids : [];
- }
- delegate: Rectangle {
- width: 60
- height: 32
- radius: Theme.radiusMedium
- color: {
- var pid = modelData;
- if (typeof game === 'undefined')
- return Theme.cardBaseB;
- return (game.selected_player_id === pid) ? Theme.selectedBg : Theme.cardBaseB;
- }
- border.color: {
- var pid = modelData;
- if (typeof game === 'undefined')
- return Theme.thumbBr;
- return (game.selected_player_id === pid) ? Theme.selectedBr : Theme.thumbBr;
- }
- border.width: 1
- Text {
- anchors.centerIn: parent
- text: "ID " + modelData
- color: {
- var pid = modelData;
- if (typeof game === 'undefined')
- return Theme.textSub;
- return (game.selected_player_id === pid) ? Theme.textMain : Theme.textSub;
- }
- font.pixelSize: 12
- font.bold: {
- var pid = modelData;
- if (typeof game === 'undefined')
- return false;
- return game.selected_player_id === pid;
- }
- }
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- if (typeof game !== 'undefined')
- game.selected_player_id = modelData;
- }
- }
- }
- }
- }
- Text {
- text: {
- if (typeof game === 'undefined')
- return "";
- var it = selectedMapData;
- if (!it || !it.player_ids)
- return "";
- var others = [];
- for (var i = 0; i < it.player_ids.length; i++) {
- if (it.player_ids[i] !== game.selected_player_id)
- others.push(it.player_ids[i]);
- }
- if (others.length === 0)
- return "All other slots will be CPU-controlled";
- return "CPU will control: ID " + others.join(", ID ");
- }
- color: Theme.textSubLite
- font.pixelSize: 11
- wrapMode: Text.WordWrap
- width: parent.width
- }
- }
- }
- }
- Rectangle {
- id: footer
- height: 60
- color: "transparent"
- anchors {
- left: parent.left
- right: parent.right
- bottom: parent.bottom
- leftMargin: Theme.spacingXLarge
- rightMargin: Theme.spacingXLarge
- bottomMargin: Theme.spacingMedium
- }
- Rectangle {
- height: 1
- color: Theme.panelBr
- anchors {
- left: parent.left
- right: parent.right
- top: parent.top
- }
- }
- Text {
- id: validationErrorText
- text: validationError
- visible: validationError !== ""
- color: Theme.removeColor
- font.pixelSize: 13
- font.bold: true
- wrapMode: Text.WordWrap
- horizontalAlignment: Text.AlignHCenter
- anchors {
- left: parent.left
- right: parent.right
- verticalCenter: parent.verticalCenter
- leftMargin: 140
- rightMargin: 140
- }
- }
- Button {
- text: qsTr("Back")
- onClicked: root.cancelled()
- hoverEnabled: true
- implicitHeight: 42
- implicitWidth: 120
- ToolTip.visible: backHover.containsMouse
- ToolTip.text: qsTr("Return to main menu (Esc)")
- anchors {
- left: parent.left
- verticalCenter: parent.verticalCenter
- topMargin: Theme.spacingSmall
- }
- MouseArea {
- id: backHover
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.NoButton
- cursorShape: Qt.PointingHandCursor
- }
- contentItem: Text {
- text: parent.text
- font.pixelSize: backHover.containsMouse ? 14 : 13
- font.bold: backHover.containsMouse
- color: Theme.textBright
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- Behavior on font.pixelSize {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- background: Rectangle {
- radius: Theme.radiusLarge
- color: {
- if (parent.down)
- return Theme.hover;
- if (backHover.containsMouse)
- return Theme.cardBase;
- return Qt.rgba(0, 0, 0, 0);
- }
- border.width: backHover.containsMouse ? 2 : 1
- border.color: backHover.containsMouse ? Theme.thumbBr : Theme.panelBr
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- }
- Button {
- text: qsTr("Play")
- enabled: list.currentIndex >= 0 && list.count > 0 && playersModel.count >= 2 && hasMinimumDistinctTeams()
- onClicked: acceptSelection()
- hoverEnabled: true
- implicitHeight: 42
- implicitWidth: 130
- ToolTip.visible: playHover.containsMouse
- ToolTip.text: {
- if (validationError !== "")
- return validationError;
- return qsTr("Start game (Enter)");
- }
- anchors {
- right: parent.right
- verticalCenter: parent.verticalCenter
- topMargin: Theme.spacingSmall
- }
- MouseArea {
- id: playHover
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.NoButton
- cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- }
- contentItem: Text {
- text: parent.text
- font.pixelSize: parent.enabled ? (playHover.containsMouse ? 15 : 14) : 14
- font.bold: true
- color: parent.enabled ? Theme.textMain : Theme.textDim
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- Behavior on font.pixelSize {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- background: Rectangle {
- radius: Theme.radiusLarge
- color: {
- if (!parent.enabled)
- return Theme.cardBaseB;
- if (parent.down)
- return Theme.selectedBr;
- if (playHover.containsMouse)
- return Qt.lighter(Theme.selectedBg, 1.2);
- return Theme.selectedBg;
- }
- border.width: parent.enabled && playHover.containsMouse ? 2 : 1
- border.color: parent.enabled ? Theme.selectedBr : Theme.panelBr
- Behavior on color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.color {
- ColorAnimation {
- duration: Theme.animFast
- }
- }
- Behavior on border.width {
- NumberAnimation {
- duration: Theme.animFast
- }
- }
- }
- }
- }
- }
- }
|