import QtQuick 6.2 import QtQuick.Controls 6.2 import QtQuick.Dialogs 6.2 import QtQuick.Layouts 6.2 ApplicationWindow { id: mainWindow title: "Persefone.jl GUI" width: 1024 height: 768 visibility: "Maximized" visible: true // these are the coordinates of the map image property var mapCoords: ({ x: 0, y: 0, width: 0, height: 0 }) // calculate the visible coordinates of an Image function calculateVisibleGeometry(img) { var containerWidth = img.width; var containerHeight = img.height; var imgRatio = img.sourceSize.width / img.sourceSize.height; var containerRatio = containerWidth / containerHeight; var visibleWidth, visibleHeight, visibleX, visibleY; if (imgRatio > containerRatio) { visibleWidth = containerWidth; visibleHeight = containerWidth / imgRatio; visibleX = 0; visibleY = (containerHeight - visibleHeight) / 2; } else { visibleHeight = containerHeight; visibleWidth = containerHeight * imgRatio; visibleX = (containerWidth - visibleWidth) / 2; visibleY = 0; } return {x: visibleX, y: visibleY, width: visibleWidth, height: visibleHeight}; } menuBar: MenuBar { Menu { title: "&Simulation" // Action { // text: "&New Simulation" // onTriggered: { vars.launching = true } //TODO select config file // } // Action { // text: "&Configure Simulation" // onTriggered: { Julia.configwindow() } // } // Action { // text: "&Load Saved State" // onTriggered: { loadFileChooser.open() } // } // Action { // text: "&Save Current State" // onTriggered: { saveFileChooser.open() } // } MenuSeparator { } Action { text: "&Quit" onTriggered: { mainWindow.close() } } } //Menu { //title: "&Data" // Action { // text: "Show &Population Graph" // onTriggered: { populationGraph.visible = true } // } // Action { // text: "Save &Simulation Output" // onTriggered: { Julia.saveoutput() } // } //} Menu { title: "&Help" Action { text: "&Documentation" onTriggered: { Qt.openUrlExternally("https://persefone-model.eu/documentation") } } Action { text: "&Website" onTriggered: { Qt.openUrlExternally("https://persefone-model.eu/") } } // Action { // text: "&About" // onTriggered: { aboutDialog.open() } // } } } ColumnLayout { anchors.fill: parent //anchors.horizontalCenter: parent.horizontalCenter Image { id: mapImage source: mapImagePath Layout.fillWidth: true Layout.fillHeight: true height: parent.height - 50 // Leave some space for the row with play/pause //anchors.fill: parent //fillMode: Image.PreserveAspectFit //anchors.top: parent.top //anchors.bottom: buttonRow.top //anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.PreserveAspectFit onStatusChanged: { if (status == Image.Error) console.log("Error loading image: ", source); if (status == Image.Ready) { mapCoords = calculateVisibleGeometry(mapImage); //console.log("Image loaded successfully."); } } Canvas { id: canvas //anchors.fill: parent //height: parent.height //width: parent.width // anchors.horizontalCenter: parent.horizontalCenter width: mapImage.width height: mapImage.height x: mapImage.x y: mapImage.y z: 1 onPaint: { var ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); // Adjust based on the image's scaling and aspect ratio // var imageScaleX = mapImage.width; // / mapImage.width; // var imageScaleY = mapImage.height; // / mapImage.height; // Define circles (example positions and radii) var circles = [ { x: 0.4, y: 0.5, radius: 5 }, { x: 0.8, y: 0.3, radius: 10 }, { x: 1.0, y: 0.5, radius: 20 } ]; for (var i = 0; i < circles.length; i++) { var circle = circles[i]; var color = colorList[i % colorList.length]; // Use colors from colorList ctx.fillStyle = Qt.rgba(color.r / 255, color.g / 255, color.b / 255, 1); ctx.beginPath(); ctx.arc(mapCoords.x + circle.x * mapCoords.width, mapCoords.y + circle.y * mapCoords.height, circle.radius, 0, 2 * Math.PI); ctx.fill(); } } Timer { id: moveTimer interval: 100 // TODO: what is the unit, maybe milliseconds? repeat: true onTriggered: { // TODO: circle movement is random for now, should come // from simulation for (var i = 0; i < parent.circles.length; i++) { parent.circles[i].x += 2 * Math.random() - 1; parent.circles[i].y += 2 * Math.random() - 1; } parent.requestPaint(); } } } } RowLayout { Layout.alignment: Qt.AlignCenter Layout.fillWidth: true height: 50 //anchors.bottom: parent.bottom //anchors.horizontalCenter: parent.horizontalCenter Button { text: "Play" onClicked: moveTimer.start() } Button { text: "Stop" onClicked: moveTimer.stop() } } } // Redraw when the window is resized Component.onCompleted: canvas.requestPaint() onWidthChanged: canvas.requestPaint() onHeightChanged: canvas.requestPaint() } // visualise the model map and the locations of animals // MakieViewport { // id: mapviewport // anchors.fill: parent // renderFunction: render_map_callback // // // the main control bar, with pause/step/run buttons, the progress // // bar and a speed slider // footer: ToolBar { // RowLayout { // //TODO change button texts to icons // // (https://doc.qt.io/qt-6/qtquickcontrols-icons.html) // id: controlBar // anchors.fill: parent // Layout.alignment: Qt.AlignVCenter // Layout.fillWidth: true // // anchors.topMargin: 5 //FIXME // // anchors.bottomMargin: 5 // Button { // id: backButton // text: "<" // ToolTip.text: "Back" // ToolTip.visible: hovered // onClicked: { Julia.previousstep() } // } // Button { // id: stepButton // text: ">" // ToolTip.text: "Step" // ToolTip.visible: hovered // onClicked: { Julia.nextstep() } // } // Button { // id: runButton // text: vars.runbuttontext // ToolTip.text: vars.runbuttontip // ToolTip.visible: hovered // onClicked: { vars.running = !vars.running } // } // ProgressBar { // id: progressBar // value: vars.progress // Layout.fillWidth: true // ToolTip.text: "Simulation progress" // ToolTip.visible: hovered // } // Slider { // id: speedSlider // from: 0.0 // to: 2.0 // value: vars.delay // stepSize: 0.1 // snapMode: Slider.SnapAlways // ToolTip.text: "Time delay between updates" // ToolTip.visible: hovered // onValueChanged: vars.delay = value // } // Text { // id: dateText // text: Julia.datestring() // //width: //TODO // } // } // } // TODO: MessageDialog is available since Qt 6.7 // extra windows // MessageDialog { // id: aboutDialog // text: "Persefone.jl GUI" // informativeText: "A mechanistic model of agricultural landscapes \ // and ecosystems in Europe.\n\n\ // © 2023 Daniel Vedder, Lea Kolb, Guy Pe'er\n\ // Distributed under the MIT license." // } // FileDialog { // id: loadFileChooser // defaultSuffix: "dat" // nameFilters: ["Save files (*.dat)"] // onAccepted: { Julia.loadsimulation(selectedFile.toString()) } // } // FileDialog { // id: saveFileChooser // defaultSuffix: "dat" // fileMode: FileDialog.SaveFile // nameFilters: ["Save files (*.dat)"] // onAccepted: { Julia.savesimulation(selectedFile.toString()) } // } // Window { // id: populationGraph // title: "Population Graph" // width: 512 // height: 512 // visible: false // MakieViewport { // id: plotviewport // anchors.fill: parent // renderFunction: render_plot_callback // } // } // Popup { // id: splashPopup // parent: Overlay.overlay // closePolicy: Popup.NoAutoClose // modal: true // padding: 0 // width: 600 // height: 250 // x: Math.round((parent.width - width) / 2) // y: Math.round((parent.height - height) / 2) // Image { // anchors.fill: parent // source: "persefonejl_logo_v3_splash.png" // } // } // set up connections and signals to update the simulation and the display // Connections { // target: timer // function onTimeout() { vars.ticks += 1 } // } // JuliaSignals { // signal updateMakie() // onUpdateMakie: { // dateText.text = Julia.datestring(); // mapviewport.update(); // plotviewport.update(); // } // signal showSplash() // onShowSplash: { // splashPopup.open() // } // signal closeSplash() // onCloseSplash: { // splashPopup.close() // } // }