diff --git a/src/GUI.jl b/src/GUI.jl index f216a8f5f5de9c9e9c684bc206f56b1cb4132ac9..60356902b0f016c4b95403b5a73f25406b0c657d 100644 --- a/src/GUI.jl +++ b/src/GUI.jl @@ -3,42 +3,6 @@ ### This file links the QML UI designs to the underlying model. ### -global model = nothing -global landcovermap = nothing -global runsimulation = nothing -global timer = nothing - -const configfile = Observable("guiparams.toml") -const launching = Observable(false) -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") -const mapimage = Observable(Figure().scene) - -global guiproperties = - ["running" => running, - "ticks" => ticks, - "date" => date, - "delay" => delay, - "progress" => progress, - "runbuttontext" => runbuttontext, - "runbuttontip" => runbuttontip] - -function newsimulation() - global model, landcovermap - launching[] = true # trigger the launch screen - running[] = false - progress[] = 0.0 - model = initialise(configfile[]) - landcovermap = rotr90(load(@param(world.landcovermap))) - date[] = model.date - launching[] = false - println("Model initialised.") -end function loadsimulation(filename) global model @@ -73,59 +37,6 @@ end #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 - -#XXX This could also be done with Channels rather than ResumableFunctions, -# which would save us one dependency (Channels are inbuilt). However, the -# ResumableFunctions version seemed to work more smoothly. -@resumable function createrunfunction() - i = round(delay[]*10) - while running[] - i -= 1 - if i <= 0 - nextstep() - i = round(delay[]*10) - end - # to reduce interface lag, always only sleep for 0.1s - sleep(0.1) - @yield 1 - end -end - function togglerunning() global runsimulation, timer if running[] @@ -137,7 +48,6 @@ function togglerunning() running[] = true runbuttontext[] = "||" runbuttontip[] = "Pause" - runsimulation = createrunfunction() QML.start(timer) end end @@ -195,16 +105,6 @@ 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 - #launching[] && #FIXME - running[] && runsimulation() -end - on(launching) do l if l @emit showSplash() @@ -246,7 +146,7 @@ function launch() qmlfile = joinpath(dirname(@__FILE__), "main.qml") loadqml(qmlfile, timer = timer, - vars = JuliaPropertyMap(guiproperties...,configproperties...), + vars = JuliaPropertyMap(guiproperties...), render_map_callback = @safe_cfunction(render_map, Cvoid, (Any,)), render_plot_callback = @safe_cfunction(render_plot, Cvoid, (Any,))) launching[] = true diff --git a/src/PersefoneDesktop.jl b/src/PersefoneDesktop.jl index f8c58990f49c225fb90e19096bbc097affe917fc..75d8489deb5a027d78aa244c85aa25c8fda9fe97 100644 --- a/src/PersefoneDesktop.jl +++ b/src/PersefoneDesktop.jl @@ -26,6 +26,8 @@ using DataFramesMeta, ResumableFunctions +include("variables.jl") +include("logic.jl") include("config.jl") include("GUI.jl") @@ -33,7 +35,6 @@ precompile(launch, ()) precompile(render_map, (Any,)) #what's the input type? export - configwindow, launch end diff --git a/src/config.jl b/src/config.jl index 89c5b21e7cfa49a2ae963832d7e1bb4f627a93a1..33c17a73aed9c967526308cf47cebceb27521cdc 100644 --- a/src/config.jl +++ b/src/config.jl @@ -3,41 +3,6 @@ ### This file manages the backend of the setup window. ### -const userconfigfile = Observable("userconfig.toml") - -const outdir = Observable("persefone_output") -const csvoutput = Observable(false) -const loglevel = Observable("warn") -const processors = Observable(1) -const seed = Observable(2) -const startdateday = Observable(1) -const startdatemonth = Observable(1) -const startdateyear = Observable(2022) -const enddateday = Observable(31) -const enddatemonth = Observable(12) -const enddateyear = Observable(2022) -const region = Observable("Jena") #XXX alternatives not yet implemented -#const farmmodel = Observable("FieldManager") #XXX not yet implemented -#const targetspecies = Observable() #TODO not sure how to configure a list? -#const insectmodel = Observable() #TODO not sure how to configure a list? -const cropmodel = Observable("ALMaSS") #XXX alternatives not yet implemented - -global configproperties = - ["userconfigfile" => userconfigfile, - "outdir" => outdir, - "csvoutput" => csvoutput, - "loglevel" => loglevel, - "processors" => processors, - "seed" => seed, - "startdateday" => startdateday, - "startdatemonth" => startdatemonth, - "startdateyear" => startdateyear, - "enddateday" => enddateday, - "enddatemonth" => enddatemonth, - "enddateyear" => enddateyear, - "region" => region, - "cropmodel" => cropmodel] - function writeconfig() println("Wrote config file.") diff --git a/src/logic.jl b/src/logic.jl new file mode 100644 index 0000000000000000000000000000000000000000..3ae716528f7e7e9019ecb1953cb79ddf73ba11bf --- /dev/null +++ b/src/logic.jl @@ -0,0 +1,82 @@ +### PersefoneDesktop - a GUI to the Persefone model of agriculture and ecosystems +### +### This file manages the model initialisation and run functions. +### + + +function newsimulation() + global model, landcovermap + launching[] = true # trigger the launch screen + running[] = false + progress[] = 0.0 + model = initialise(configfile[]) + landcovermap = rotr90(load(@param(world.landcovermap))) + date[] = model.date + launching[] = false + println("Model initialised.") +end + +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 + +#XXX This could also be done with Channels rather than ResumableFunctions, +# which would save us one dependency (Channels are inbuilt). However, the +# ResumableFunctions version seemed to work more smoothly. +@resumable function createrunfunction() + i = round(delay[]*10) + while running[] + i -= 1 + if i <= 0 + nextstep() + i = round(delay[]*10) + end + # to reduce interface lag, always only sleep for 0.1s + sleep(0.1) + @yield 1 + end +end + + +on(running) do r + ticks = 0 #prevent overflows + runsimulation = createrunfunction() + r ? println("Simulation started.") : println("Simulation stopped.") #XXX DEBUG +end + +on(ticks) do t + #launching[] && #FIXME + running[] && runsimulation() +end diff --git a/src/mockup.ui b/src/mockup.ui deleted file mode 100644 index 19c710a6771b047d65cba91a8ed56f49252e3404..0000000000000000000000000000000000000000 --- a/src/mockup.ui +++ /dev/null @@ -1,189 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>1067</width> - <height>614</height> - </rect> - </property> - <property name="windowTitle"> - <string>Persefone Desktop</string> - </property> - <property name="windowIcon"> - <iconset> - <normaloff>../eacf50ed/favicon.png</normaloff>../eacf50ed/favicon.png</iconset> - </property> - <property name="toolTipDuration"> - <number>-1</number> - </property> - <widget class="QWidget" name="centralwidget"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QGraphicsView" name="graphicsView"/> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <property name="leftMargin"> - <number>5</number> - </property> - <item> - <widget class="QCalendarWidget" name="calendarWidget"/> - </item> - <item> - <widget class="QGraphicsView" name="graphicsView_2"/> - </item> - </layout> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QPushButton" name="pushButton_3"> - <property name="toolTip"> - <string>Pause</string> - </property> - <property name="text"> - <string>||</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pushButton"> - <property name="toolTip"> - <string>Step</string> - </property> - <property name="text"> - <string>></string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pushButton_2"> - <property name="toolTip"> - <string>Run</string> - </property> - <property name="text"> - <string>>></string> - </property> - </widget> - </item> - <item> - <widget class="QProgressBar" name="progressBar"> - <property name="value"> - <number>24</number> - </property> - </widget> - </item> - <item> - <widget class="QSlider" name="horizontalSlider"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>200</width> - <height>0</height> - </size> - </property> - <property name="toolTip"> - <string>Delay between updates</string> - </property> - <property name="maximum"> - <number>10</number> - </property> - <property name="value"> - <number>5</number> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="tickPosition"> - <enum>QSlider::TicksBelow</enum> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menubar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>1067</width> - <height>25</height> - </rect> - </property> - <widget class="QMenu" name="menu_Menu"> - <property name="title"> - <string>&Menu</string> - </property> - <addaction name="separator"/> - <addaction name="action_New_run"/> - <addaction name="actionLoad_saved_state"/> - <addaction name="actionSave_current_state"/> - <addaction name="separator"/> - <addaction name="action_Quit"/> - </widget> - <widget class="QMenu" name="menu_Help"> - <property name="title"> - <string>&Help</string> - </property> - <addaction name="actionHelp"/> - <addaction name="actionWebsite"/> - <addaction name="actionAbout"/> - </widget> - <addaction name="menu_Menu"/> - <addaction name="menu_Help"/> - </widget> - <widget class="QStatusBar" name="statusbar"/> - <action name="actionHelp"> - <property name="text"> - <string>Documentation</string> - </property> - </action> - <action name="actionAbout"> - <property name="text"> - <string>About</string> - </property> - </action> - <action name="action_New_run"> - <property name="text"> - <string>&New run</string> - </property> - </action> - <action name="action_Quit"> - <property name="text"> - <string>&Quit</string> - </property> - </action> - <action name="actionSave_current_state"> - <property name="text"> - <string>&Save current state</string> - </property> - </action> - <action name="actionLoad_saved_state"> - <property name="text"> - <string>&Load saved state</string> - </property> - </action> - <action name="actionWebsite"> - <property name="text"> - <string>Website</string> - </property> - </action> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/splash.qml b/src/splash.qml deleted file mode 100644 index 4108ee5e74f6eacbdab6ed27904696e44f65e483..0000000000000000000000000000000000000000 --- a/src/splash.qml +++ /dev/null @@ -1,34 +0,0 @@ -/// PersefoneDesktop - a GUI to the Persefone model of agriculture and ecosystems -/// -/// This file creates the splash screen. Load it using a QQuickView. -/// - -import QtQuick -import QtQuick.Controls -import org.julialang - -Popup { - id: splashPopupExt - parent: Overlay.overlay - - width: 600 - height: 250 - - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height - height) / 2) - - Rectangle { - anchors.fill: parent - - Image { - source: "persefonejl_logo_v3_splash.png" - fillMode: Image.Stretch - } - Timer { - // Show the splash screen while the model object is initialised - //XXX is 15s a good time here? - interval: 15000; running: true; repeat: false - onTriggered: parent.Window.window.close() - } - } -} diff --git a/src/variables.jl b/src/variables.jl new file mode 100644 index 0000000000000000000000000000000000000000..b2a6b85de9cb40e50a9964d743c2853ee3c50d55 --- /dev/null +++ b/src/variables.jl @@ -0,0 +1,70 @@ +### PersefoneDesktop - a GUI to the Persefone model of agriculture and ecosystems +### +### This file contains all globals and Observables. +### + + +## GUI parameters + +global model = nothing +global landcovermap = nothing +global runsimulation = nothing +global timer = nothing + +const configfile = Observable("guiparams.toml") +const launching = Observable(false) +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") +const mapimage = Observable(Figure().scene) + + +## configuration parameters + +const userconfigfile = Observable("userconfig.toml") +const outdir = Observable("persefone_output") +const csvoutput = Observable(false) +const loglevel = Observable("warn") +const processors = Observable(1) +const seed = Observable(2) +const startdateday = Observable(1) +const startdatemonth = Observable(1) +const startdateyear = Observable(2022) +const enddateday = Observable(31) +const enddatemonth = Observable(12) +const enddateyear = Observable(2022) +const region = Observable("Jena") #XXX alternatives not yet implemented +#const farmmodel = Observable("FieldManager") #XXX not yet implemented +#const targetspecies = Observable() #TODO not sure how to configure a list? +#const insectmodel = Observable() #TODO not sure how to configure a list? +const cropmodel = Observable("ALMaSS") #XXX alternatives not yet implemented + + +## the property list passed to QML + +global guiproperties = + ["running" => running, + "ticks" => ticks, + "date" => date, + "delay" => delay, + "progress" => progress, + "runbuttontext" => runbuttontext, + "runbuttontip" => runbuttontip, + "userconfigfile" => userconfigfile, + "outdir" => outdir, + "csvoutput" => csvoutput, + "loglevel" => loglevel, + "processors" => processors, + "seed" => seed, + "startdateday" => startdateday, + "startdatemonth" => startdatemonth, + "startdateyear" => startdateyear, + "enddateday" => enddateday, + "enddatemonth" => enddatemonth, + "enddateyear" => enddateyear, + "region" => region, + "cropmodel" => cropmodel]