Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
main.qml 11.36 KiB
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()
    //     }
    // }