Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
GUI.jl 4.66 KiB
### PersefoneDesktop - a GUI to the Persefone model of agriculture and ecosystems
###
### This file links the QML UI designs to the underlying model.
###

## For references, see:
## - https://github.com/barche/QML.jl
## - https://github.com/barche/QmlJuliaExamples
## - https://doc.qt.io/qt-6/qtquick-index.html

#TODO define standard config file for Persefone Desktop

global model = nothing
global landcovermap = nothing
global runsimulation = nothing
global timer = nothing

const running = Observable(false)
const ticks = Observable(0)
const date = Observable(today())
const progress = Observable(0.0)
const delay = Observable(0.5)
const runbuttontext = Observable(">>")
const runbuttontip = Observable("Run")

function newsimulation()
    global model, landcovermap
    running[] = false
    progress[] = 0.0
    model = initialise() #TODO configure
    landcovermap = load(@param(world.landcovermap))
    date[] = model.date
    println("Model initialised.")
end

#TODO loadsimulation()
#TODO saveoutput()

function nextstep()
    global model
    if date[] >= @param(core.enddate)
        running[] = false
        runbuttontext[] = ">>"
        runbuttontip[] = "Run"
        return
    elseif date[] < model.date
        # If we've "scrolled back" in time, step forward again without
        # having to resimulate (see `previousstep()`)
        date[] += Day(1)
    else
        # If we're already displaying the newest model update, we need to
        # keep simulating
        stepsimulation!(model)
        date[] = model.date
    end
    progress[] = (date[]-@param(core.startdate)) /
        (@param(core.enddate)-@param(core.startdate))
    @emit updateMakie()
    println("Updated model: $(date[]-Day(1))")
end

function previousstep()
    # Since we store all model output in a dataframe, we can simply
    # "scroll back" in time and visualise the state of the model at
    # a previous date
    global model
    date[] <= @param(core.startdate) && return
    date[] -= Day(1)
    progress[] = (date[]-@param(core.startdate)) /
        (@param(core.enddate)-@param(core.startdate))
    @emit updateMakie()
    println("Backtracked model: $(date[]-Day(1))")
end

@resumable function createrunfunction()
    while running[]
        nextstep()
        #XXX to reduce interface lag, always only sleep for 0.1s?
        sleep(delay[])
        @yield 1
    end
end

function togglerunning()
    global runsimulation, timer
    if running[]
        running[] = false
        QML.stop(timer)
        runbuttontext[] = ">>"
        runbuttontip[] = "Run"
    else
        running[] = true
        QML.start(timer)
        runbuttontext[] = "||"
        runbuttontip[] = "Pause"
        runsimulation = createrunfunction()
    end
end

function render_map(screen)
    global model, landcovermap
    println("Updating map")
    figure = visualisemap(model, date[], landcovermap)
    display(screen, figure.scene)
end

function render_plot(screen)
    global model
    println("Updating plot")
    figure = populationtrends(model)
    display(screen, figure.scene)
end

on(delay) do d
    println("Delay is now $(round(d, digits=1)) seconds.") #XXX DEBUG
end

on(running) do r
    ticks = 0 #prevent overflows
    r ? println("Simulation started.") : println("Simulation stopped.") #XXX DEBUG
end

on(ticks) do t
    running[] && runsimulation()
end

function splashscreen()
    qmlfile = joinpath(dirname(@__FILE__), "splash.qml")
    quick_view = init_qquickview()
    set_source(quick_view, QUrlFromLocalFile(qmlfile))
    QML.show(quick_view)
    #XXX exec_async() is currently still broken, but should be fixed soon
    # (https://github.com/JuliaGraphics/QML.jl/issues/174)
    @async exec()
end

"""
    launch()

The main function that creates the application.
"""
function launch()
    global model, timer
    #splashscreen()
    newsimulation()
    
    @qmlfunction newsimulation
    @qmlfunction nextstep
    @qmlfunction previousstep
    @qmlfunction togglerunning

    timer = QTimer()

    qmlfile = joinpath(dirname(@__FILE__), "main.qml")
    loadqml(qmlfile,
            timer = timer,
            vars = JuliaPropertyMap("running" => running,
                                    "ticks" => ticks,
                                    "date" => date,
                                    "delay" => delay,
                                    "progress" => progress,
                                    "runbuttontext" => runbuttontext,
                                    "runbuttontip" => runbuttontip),
            render_map_callback = @safe_cfunction(render_map, Cvoid, (Any,)),
            render_plot_callback = @safe_cfunction(render_plot, Cvoid, (Any,)))
    @emit updateMakie()
    println("Launched Persefone Desktop.")
    exec()
end