3
0
mirror of https://github.com/britzl/monarch.git synced 2025-11-26 19:00:53 +01:00

Compare commits

..

19 Commits

Author SHA1 Message Date
Björn Ritzl
753d003861 Preload fix (#34)
* Don't try to preload if monarch is busy
* Added test for #32
2018-12-10 12:27:11 +01:00
Björn Ritzl
8a0a36a2d5 Added new screenshots and linked to them from the docs (#30) 2018-10-03 13:41:58 +02:00
Björn Ritzl
c98a8ef44a Updated to deftest-2.4.3 2018-08-01 08:10:25 +02:00
Björn Ritzl
588398e23e Updated code coverage collection 2018-08-01 07:49:27 +02:00
Björn Ritzl
75e3ac1ce9 Added code coverage badge 2018-07-31 19:07:13 +02:00
Björn Ritzl
5ec208d10d Collect code coverage 2018-07-31 19:01:22 +02:00
Björn Ritzl
3443484cce Added support for collection factories 2018-07-27 13:28:36 +02:00
Björn Ritzl
3a7187b844 Added layers to all guis in the example 2018-07-27 12:30:33 +02:00
Björn Ritzl
b73ed95315 Removed code duplication when loading 2018-07-27 09:48:59 +02:00
Björn Ritzl
8d1051f0fd Added Timestep below Popup
Fixes #25
2018-07-26 10:52:03 +02:00
Björn Ritzl
bc4260d72a Added fade in/out transition 2018-07-13 06:54:26 +02:00
Björn Ritzl
07eacc7a5f Track when callbacks are invoked so that we can ensure that all screens are shown/hidden before invoking callbacks 2018-06-21 11:33:20 +02:00
Björn Ritzl
5ee6ea5982 Log when monarch is busy 2018-06-21 11:30:17 +02:00
Björn Ritzl
f590c75b9e Removed debug prints that was accidentally left behind 2018-06-21 11:29:59 +02:00
Björn Ritzl
d528947998 Updated docs with new notification messages 2018-06-20 13:49:02 +02:00
Björn Ritzl
22f0e6cddf Changed from single notification to begin and end notification for every navigation 2018-06-20 13:46:45 +02:00
Björn Ritzl
b13adcfb62 Added listener documentation 2018-06-20 07:36:16 +02:00
Björn Ritzl
3947e86169 Added navigation listeners 2018-06-20 07:30:38 +02:00
Björn Ritzl
e570eac40b Don't preload an already loaded screen 2018-06-18 07:19:37 +02:00
26 changed files with 962 additions and 172 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ build
.cproject
builtins
.internal
luacov.report.out

74
.luacov Normal file
View File

@@ -0,0 +1,74 @@
local reporter = require "luacov.reporter.defold"
--- Default values for configuration options.
-- For project specific configuration create '.luacov' file in your project
-- folder. It should be a Lua script setting various options as globals
-- or returning table of options.
-- @class module
-- @name deftest.coverage.configuration
return {
--- Reporter class to use when creating a report. Default: DefaultReporter from reporter.lua
reporter = reporter,
--- Filename to store collected stats. Default: "luacov.stats.out".
statsfile = "luacov.stats.out",
--- Filename to store report. Default: "luacov.report.out".
reportfile = "luacov.report.out",
--- Enable saving coverage data after every `savestepsize` lines?
-- Setting this flag to `true` in config is equivalent to running LuaCov
-- using `luacov.tick` module. Default: false.
tick = false,
--- Stats file updating frequency for `luacov.tick`.
-- The lower this value - the more frequently results will be written out to the stats file.
-- You may want to reduce this value (to, for example, 2) to avoid losing coverage data in
-- case your program may terminate without triggering luacov exit hooks that are supposed
-- to save the data. Default: 100.
savestepsize = 100,
--- Run reporter on completion? Default: true.
runreport = true,
--- Delete stats file after reporting? Default: false.
deletestats = true,
--- Process Lua code loaded from raw strings?
-- That is, when the 'source' field in the debug info
-- does not start with '@'. Default: true.
codefromstrings = true,
--- Lua patterns for files to include when reporting.
-- All will be included if nothing is listed.
-- Do not include the '.lua' extension. Path separator is always '/'.
-- Overruled by `exclude`.
-- @usage
-- include = {
-- "mymodule$", -- the main module
-- "mymodule%/.+$", -- and everything namespaced underneath it
-- }
include = {},
--- Lua patterns for files to exclude when reporting.
-- Nothing will be excluded if nothing is listed.
-- Do not include the '.lua' extension. Path separator is always '/'.
-- Overrules `include`.
exclude = { "^test%/.+$", "^deftest%/.+$" },
--- Table mapping names of modules to be included to their filenames.
-- Has no effect if empty.
-- Real filenames mentioned here will be used for reporting
-- even if the modules have been installed elsewhere.
-- Module name can contain '*' wildcard to match groups of modules,
-- in this case corresponding path will be used as a prefix directory
-- where modules from the group are located.
-- @usage
-- modules = {
-- ["some_rock"] = "src/some_rock.lua",
-- ["some_rock.*"] = "src"
-- }
modules = {},
}

View File

@@ -26,3 +26,6 @@ env:
script:
- "./.travis/run.sh"
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -1,6 +1,7 @@
![](docs/logo.jpg)
[![Build Status](https://travis-ci.org/britzl/monarch.svg?branch=master)](https://travis-ci.org/britzl/monarch)
[![Code Coverage](https://codecov.io/gh/britzl/monarch/branch/master/graph/badge.svg)](https://codecov.io/gh/britzl/monarch)
[![Latest Release](https://img.shields.io/github/release/britzl/monarch.svg)](https://github.com/britzl/monarch/releases)
# Monarch
@@ -20,16 +21,32 @@ Using Monarch requires that screens are created in a certain way. Once you have
## Creating screens
Monarch screens are created in individual collections and loaded through collection proxies. The recommended setup is to create one game object per screen and per game object attach a collection proxy component and an instance of the ```screen.script``` provided by Monarch. The ```screen.script``` will take care of the setup of the screen. All you need to do is to make sure that the script properties on the ```screen.script``` are correct:
Monarch screens are created in individual collections and either loaded through collection proxies or created through collection factories.
### Collection proxies
For proxies the recommended setup is to create one game object per screen and per game object attach a collection proxy component and an instance of the ```screen_proxy.script``` provided by Monarch. The ```screen_proxy.script``` will take care of the setup of the screen. All you need to do is to make sure that the script properties on the script are correct:
* **Screen Proxy (url)** - The URL to the collection proxy component containing the actual screen. Defaults to ```#collectionproxy```.
* **Screen Id (hash)** - A unique id that can be used to reference the screen when navigating your app.
* **Popup (boolean)** - Check this if the screen should be treated as a [popup](#popups).
* **Popup on Popup (boolean)** - Check this if the screen is a [popup](#popups) and it can be shown on top of other popups.
* **Timestep below Popup (number)** - Timestep to set on screen proxy when it is below a popup. This is useful when pausing animations and gameplay while a popup is open.
* **Transition Url (url)** - Optional URL to call when the screen is about to be shown/hidden. Use this to trigger a transition (see the section on [transitions](#transitions)).
* **Focus Url (url)** - Optional URL to call when the screen gains or loses focus (see the section on [screen focus](#screen-focus-gainloss)).
![](docs/setup.png)
![](docs/setup_proxy.png)
### Collection factories
For factories the recommended setup is to create one game object per screen and per game object attach a collection factory component and an instance of the ```screen_factory.script``` provided by Monarch. The ```screen_factory.script``` will take care of the setup of the screen. All you need to do is to make sure that the script properties on the script are correct:
* **Screen Factory (url)** - The URL to the collection factory component containing the actual screen. Defaults to ```#collectionfactory```.
* **Screen Id (hash)** - A unique id that can be used to reference the screen when navigating your app.
* **Popup (boolean)** - Check this if the screen should be treated as a [popup](#popups).
* **Popup on Popup (boolean)** - Check this if the screen is a [popup](#popups) and it can be shown on top of other popups.
* **Transition Id (url)** - Optional id of the game object to send a message to when the screen is about to be shown/hidden. Use this to trigger a transition (see the section on [transitions](#transitions)).
* **Focus Id (url)** - Optional id of the game object to send a message to when the screen gains or loses focus (see the section on [screen focus](#screen-focus-gainloss)).
![](docs/setup_factory.png)
## Navigating between screens
@@ -162,6 +179,8 @@ The predefined transitions provided by ```monarch.transitions.gui``` are:
* ```slide_out_bottom```
* ```scale_in```
* ```scale_out```
* ```fade_in``` - Set node alpha to fully transparent (i.e. 0.0) and fade to fully opaque (i.e. 1.0)
* ```fade_out``` - Set node alpha to fully opaque (i.e. 1.0) and fade to fully transparent (i.e. 0.0)
Additionally there's functionality to create a full set of transitions for common transition styles:
@@ -169,6 +188,7 @@ Additionally there's functionality to create a full set of transitions for commo
* ```transitions.in_left_out_right(node, duration, [delay], [easing])```
* ```transitions.in_left_out_left(node, duration, [delay], [easing])```
* ```transitions.in_right_out_right(node, duration, [delay], [easing])```
* ```transitions.fade_in_out(node, duration, [delay], [easing])```
**PARAMETERS**
* ```node``` (node) - Gui node to animate.
@@ -334,5 +354,51 @@ Check if Monarch is busy showing and/or hiding a screen.
* ```busy``` (boolean) - True if busy hiding and/or showing a screen.
### monarch.add_listener([url])
Add a URL that will be notified of navigation events.
**PARAMETERS**
* ```url``` (url) - URL to send navigation events to. Will use current URL if omitted.
### monarch.remove_listener([url])
Remove a previously added listener.
**PARAMETERS**
* ```url``` (url) - URL to remove. Will use current URL if omitted.
### monarch.debug()
Enable verbose logging of the internals of Monarch.
### monarch.SCREEN_TRANSITION_IN_STARTED
Message sent to listeners when a screen has started to transition in.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```previous_screen``` (hash) - Id of the previous screen (if any)
### monarch.SCREEN_TRANSITION_IN_FINISHED
Message sent to listeners when a screen has finished to transition in.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```previous_screen``` (hash) - Id of the previous screen (if any)
### monarch.SCREEN_TRANSITION_OUT_STARTED
Message sent to listeners when a screen has started to transition out.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```next_screen``` (hash) - Id of the next screen (if any)
### monarch.SCREEN_TRANSITION_OUT_FINISHED
Message sent to listeners when a screen has finished to transition out.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```next_screen``` (hash) - Id of the next screen (if any)

BIN
docs/setup_factory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/setup_proxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -48,7 +48,7 @@ nodes {
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -103,7 +103,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -172,7 +172,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "ok_reload_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -235,7 +235,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: true
parent: "root"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -284,7 +284,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -353,7 +353,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "ok_clear_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -402,7 +402,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -471,7 +471,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: true
parent: "ok_clearreload_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -520,7 +520,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -589,7 +589,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "ok_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -598,6 +598,12 @@ nodes {
text_leading: 1.0
text_tracking: 0.0
}
layers {
name: "below"
}
layers {
name: "text"
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -48,7 +48,7 @@ nodes {
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -103,7 +103,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -172,7 +172,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "yes_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -221,7 +221,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -290,7 +290,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "no_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -362,6 +362,12 @@ nodes {
text_leading: 1.0
text_tracking: 0.0
}
layers {
name: "below"
}
layers {
name: "text"
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "menu"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -22,6 +22,11 @@ embedded_instances {
" type: PROPERTY_TYPE_HASH\n"
" }\n"
" properties {\n"
" id: \"timestep_below_popup\"\n"
" value: \"0.0\"\n"
" type: PROPERTY_TYPE_NUMBER\n"
" }\n"
" properties {\n"
" id: \"transition_url\"\n"
" value: \"menu:/go#menu\"\n"
" type: PROPERTY_TYPE_URL\n"
@@ -122,7 +127,7 @@ embedded_instances {
id: "pregame"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -185,7 +190,7 @@ embedded_instances {
id: "game"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -248,7 +253,7 @@ embedded_instances {
id: "popup"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_factory.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -271,16 +276,26 @@ embedded_instances {
" type: PROPERTY_TYPE_BOOLEAN\n"
" }\n"
" properties {\n"
" id: \"transition_url\"\n"
" value: \"popup:/go#popup\"\n"
" type: PROPERTY_TYPE_URL\n"
" id: \"popup_on_popup\"\n"
" value: \"true\"\n"
" type: PROPERTY_TYPE_BOOLEAN\n"
" }\n"
" properties {\n"
" id: \"transition_id\"\n"
" value: \"/go\"\n"
" type: PROPERTY_TYPE_HASH\n"
" }\n"
" properties {\n"
" id: \"focus_id\"\n"
" value: \"/go\"\n"
" type: PROPERTY_TYPE_HASH\n"
" }\n"
"}\n"
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/popup.collection\\\"\\n"
"exclude: false\\n"
" id: \"collectionfactory\"\n"
" type: \"collectionfactory\"\n"
" data: \"prototype: \\\"/example/popup.collection\\\"\\n"
"load_dynamically: false\\n"
"\"\n"
" position {\n"
" x: 0.0\n"
@@ -316,7 +331,7 @@ embedded_instances {
id: "about"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -339,6 +354,11 @@ embedded_instances {
" type: PROPERTY_TYPE_BOOLEAN\n"
" }\n"
" properties {\n"
" id: \"popup_on_popup\"\n"
" value: \"true\"\n"
" type: PROPERTY_TYPE_BOOLEAN\n"
" }\n"
" properties {\n"
" id: \"transition_url\"\n"
" value: \"about:/go#about\"\n"
" type: PROPERTY_TYPE_URL\n"
@@ -384,7 +404,7 @@ embedded_instances {
id: "confirm"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"

View File

@@ -48,7 +48,7 @@ nodes {
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -103,7 +103,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -172,7 +172,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "win_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -235,7 +235,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "root"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -244,6 +244,12 @@ nodes {
text_leading: 1.0
text_tracking: 0.0
}
layers {
name: "below"
}
layers {
name: "text"
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -48,7 +48,7 @@ nodes {
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -103,7 +103,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -172,7 +172,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "startgame_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -221,7 +221,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -290,7 +290,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "about_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -339,7 +339,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -408,7 +408,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "back_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -471,7 +471,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "root"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -480,6 +480,67 @@ nodes {
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 320.0
y: 272.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 40.0
y: 40.0
z: 0.0
w: 1.0
}
color {
x: 0.6
y: 0.0
z: 0.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: ""
id: "spinner"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: false
size_mode: SIZE_MODE_MANUAL
}
layers {
name: "below"
}
layers {
name: "text"
}
material: "/builtins/materials/gui.material"
layouts {
name: "Landscape"
@@ -523,7 +584,7 @@ layouts {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -579,7 +640,7 @@ layouts {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -635,7 +696,7 @@ layouts {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -705,7 +766,7 @@ layouts {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "root"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0

View File

@@ -6,7 +6,9 @@ function init(self)
gui.set_text(gui.get_node("timestamp"), os.date())
self.transition = transitions.in_right_out_left(gui.get_node("root"), 0.6, 0)
gui.animate(gui.get_node("spinner"), gui.PROP_ROTATION, vmath.vector3(0, 0, -360), gui.EASING_INOUTQUAD, 2, 0, nil, gui.PLAYBACK_LOOP_FORWARD)
self.transition = transitions.fade_in_out(gui.get_node("root"), 0.6, 0)
end
function on_input(self, action_id, action)

View File

@@ -48,7 +48,7 @@ nodes {
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -103,7 +103,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -172,7 +172,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "ok_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -221,7 +221,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -290,7 +290,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "cancel_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -339,7 +339,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -408,7 +408,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "about_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -417,6 +417,67 @@ nodes {
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 184.0
y: 136.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 10.0
y: 10.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: ""
id: "spinner"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: false
size_mode: SIZE_MODE_MANUAL
}
layers {
name: "below"
}
layers {
name: "text"
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -8,6 +8,8 @@ function init(self)
self.about = gui.get_node("about_button")
gui.set_render_order(14)
gui.animate(gui.get_node("spinner"), gui.PROP_ROTATION, vmath.vector3(0, 0, -360), gui.EASING_INOUTQUAD, 2, 0, nil, gui.PLAYBACK_LOOP_FORWARD)
self.transition = transitions.create(gui.get_node("root"))
.show_in(transitions.scale_in, gui.EASING_OUTBACK, 0.3, 0)
.show_out(transitions.scale_out, gui.EASING_INBACK, 0.3, 0)

View File

@@ -48,7 +48,7 @@ nodes {
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -103,7 +103,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -172,7 +172,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "play_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -221,7 +221,7 @@ nodes {
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
layer: "below"
inherit_alpha: true
slice9 {
x: 0.0
@@ -290,7 +290,7 @@ nodes {
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "back_button"
layer: ""
layer: "text"
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
@@ -299,6 +299,12 @@ nodes {
text_leading: 1.0
text_tracking: 0.0
}
layers {
name: "below"
}
layers {
name: "text"
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -1,10 +1,10 @@
[project]
title = Monarch
version = 0.9
dependencies = https://github.com/britzl/deftest/archive/1.2.1.zip
dependencies = https://github.com/britzl/deftest/archive/2.4.3.zip
[bootstrap]
main_collection = /example/example.collectionc
main_collection = /test/test.collectionc
[input]
game_binding = /input/game.input_bindingc

View File

@@ -1,3 +1,5 @@
local callback_tracker = require "monarch.utils.callback_tracker"
local M = {}
local CONTEXT = hash("monarch_context")
@@ -10,6 +12,7 @@ local ASYNC_LOAD = hash("async_load")
local UNLOAD = hash("unload")
local ENABLE = hash("enable")
-- transition messages
M.TRANSITION = {}
M.TRANSITION.DONE = hash("transition_done")
M.TRANSITION.SHOW_IN = hash("transition_show_in")
@@ -17,16 +20,27 @@ M.TRANSITION.SHOW_OUT = hash("transition_show_out")
M.TRANSITION.BACK_IN = hash("transition_back_in")
M.TRANSITION.BACK_OUT = hash("transition_back_out")
-- focus messages
M.FOCUS = {}
M.FOCUS.GAINED = hash("monarch_focus_gained")
M.FOCUS.LOST = hash("monarch_focus_lost")
-- listener messages
M.SCREEN_TRANSITION_IN_STARTED = hash("monarch_screen_transition_in_started")
M.SCREEN_TRANSITION_IN_FINISHED = hash("monarch_screen_transition_in_finished")
M.SCREEN_TRANSITION_OUT_STARTED = hash("monarch_screen_transition_out_started")
M.SCREEN_TRANSITION_OUT_FINISHED = hash("monarch_screen_transition_out_finished")
-- all registered screens
local screens = {}
-- the current stack of screens
local stack = {}
-- navigation listeners
local listeners = {}
-- the number of active transitions
-- monarch is considered busy while there are active transitions
local active_transition_count = 0
@@ -46,9 +60,15 @@ local function tohash(s)
return hash_lookup[s]
end
local function notify_listeners(message_id, message)
log("notify_listeners()", message_id)
for _,url in pairs(listeners) do
msg.post(url, message_id, message or {})
end
end
local function screen_from_proxy(proxy)
for _, screen in pairs(screens) do
for _,screen in pairs(screens) do
if screen.proxy == proxy then
return screen
end
@@ -57,7 +77,7 @@ end
local function screen_from_script()
local url = msg.url()
for _, screen in pairs(screens) do
for _,screen in pairs(screens) do
if screen.script == url then
return screen
end
@@ -92,9 +112,23 @@ function M.is_top(id)
end
--- Register a new screen
-- This is done automatically by the screen.script. It is expected that the
-- caller of this function is a script component attached to the same game
local function register(id, settings)
assert(id, "You must provide a screen id")
id = tohash(id)
assert(not screens[id], ("There is already a screen registered with id %s"):format(tostring(id)))
screens[id] = {
id = id,
script = msg.url(),
popup = settings and settings.popup,
popup_on_popup = settings and settings.popup_on_popup,
timestep_below_popup = settings and settings.timestep_below_popup or 1,
}
return screens[id]
end
--- Register a new screen contained in a collection proxy
-- This is done automatically by the screen_proxy.script. It is expected that
-- the caller of this function is a script component attached to the same game
-- object as the proxy. This is required since monarch will acquire and
-- release input focus of the game object where the proxy is attached.
-- @param id Unique id of the screen
@@ -107,21 +141,38 @@ end
-- screen transitions
-- * focus_url - URL to a script that is to be notified of focus
-- lost/gained events
function M.register(id, proxy, settings)
assert(id, "You must provide a screen id")
id = tohash(id)
assert(not screens[id], ("There is already a screen registered with id %s"):format(tostring(id)))
-- * timestep_below_popup - Timestep to set on proxy when below a popup
function M.register_proxy(id, proxy, settings)
assert(proxy, "You must provide a collection proxy URL")
local url = msg.url(proxy)
screens[id] = {
id = id,
proxy = proxy,
script = msg.url(),
popup = settings and settings.popup,
popup_on_popup = settings and settings.popup_on_popup,
transition_url = settings and settings.transition_url,
focus_url = settings and settings.focus_url,
}
local screen = register(id, settings)
screen.proxy = proxy
screen.transition_url = settings and settings.transition_url
screen.focus_url = settings and settings.focus_url
end
M.register = M.register_proxy
--- Register a new screen contained in a collection factory
-- This is done automatically by the screen_factory.script. It is expected that
-- the caller of this function is a script component attached to the same game
-- object as the factory. This is required since monarch will acquire and
-- release input focus of the game object where the factory is attached.
-- @param id Unique id of the screen
-- @param factory URL to the collection factory containing the screen
-- @param settings Settings table for screen. Accepted values:
-- * popup - true the screen is a popup
-- * popup_on_popup - true if this popup can be shown on top of
-- another popup or false if an underlying popup should be closed
-- * transition_id - Id of the game object in the collection that is responsible
-- for the screen transitions
-- * focus_id - Id of the game object in the collection that is to be notified
-- of focus lost/gained events
function M.register_factory(id, factory, settings)
assert(factory, "You must provide a collection factory URL")
local screen = register(id, settings)
screen.factory = factory
screen.transition_id = settings and settings.transition_id
screen.focus_id = settings and settings.focus_id
end
--- Unregister a screen
@@ -137,7 +188,13 @@ end
local function acquire_input(screen)
log("change_context()", screen.id)
if not screen.input then
msg.post(screen.script, ACQUIRE_INPUT_FOCUS)
if screen.proxy then
msg.post(screen.script, ACQUIRE_INPUT_FOCUS)
elseif screen.factory then
for id,instance in pairs(screen.factory_ids) do
msg.post(instance, ACQUIRE_INPUT_FOCUS)
end
end
screen.input = true
end
end
@@ -145,7 +202,13 @@ end
local function release_input(screen)
log("change_context()", screen.id)
if screen.input then
msg.post(screen.script, RELEASE_INPUT_FOCUS)
if screen.proxy then
msg.post(screen.script, RELEASE_INPUT_FOCUS)
elseif screen.factory then
for id,instance in pairs(screen.factory_ids) do
msg.post(instance, RELEASE_INPUT_FOCUS)
end
end
screen.input = false
end
end
@@ -160,42 +223,121 @@ end
local function unload(screen)
log("unload()", screen.id)
screen.wait_for = PROXY_UNLOADED
msg.post(screen.proxy, UNLOAD)
coroutine.yield()
screen.loaded = false
screen.wait_for = nil
if screen.proxy then
screen.wait_for = PROXY_UNLOADED
msg.post(screen.proxy, UNLOAD)
coroutine.yield()
screen.loaded = false
screen.wait_for = nil
elseif screen.factory then
for id, instance in pairs(screen.factory_ids) do
go.delete(instance)
end
screen.factory_ids = nil
collectionfactory.unload(screen.factory)
screen.loaded = false
end
end
local function async_load(screen)
log("async_load()", screen.id)
screen.wait_for = PROXY_LOADED
msg.post(screen.proxy, ASYNC_LOAD)
coroutine.yield()
msg.post(screen.proxy, ENABLE)
local function preload(screen)
log("preload() preloading screen", screen.id)
assert(screen.co, "You must assign a coroutine to the screen")
if screen.preloaded then
log("preload() screen already preloaded", screen.id)
return
end
if screen.proxy then
screen.wait_for = PROXY_LOADED
msg.post(screen.proxy, ASYNC_LOAD)
coroutine.yield()
elseif screen.factory then
if collectionfactory.get_status(screen.factory) == collectionfactory.STATUS_UNLOADED then
collectionfactory.load(screen.factory, function(self, url, result)
assert(coroutine.resume(screen.co))
end)
coroutine.yield()
end
if collectionfactory.get_status(screen.factory) ~= collectionfactory.STATUS_LOADED then
log("preload() error loading factory resources")
return
end
end
screen.preloaded = true
end
local function load(screen)
log("load()", screen.id)
assert(screen.co, "You must assign a coroutine to the screen")
if screen.loaded then
log("load() screen already loaded", screen.id)
return
end
preload(screen)
if not screen.preloaded then
log("load() screen wasn't preloaded", screen.id)
return
end
if screen.proxy then
msg.post(screen.proxy, ENABLE)
elseif screen.factory then
screen.factory_ids = collectionfactory.create(screen.factory)
screen.transition_url = screen.factory_ids[screen.transition_id]
screen.focus_url = screen.factory_ids[screen.focus_id]
end
screen.loaded = true
screen.wait_for = nil
screen.preloaded = false
end
local function transition(screen, message_id, message)
log("transition()", screen.id)
screen.wait_for = M.TRANSITION.DONE
msg.post(screen.transition_url, message_id, message)
coroutine.yield()
screen.wait_for = nil
if screen.transition_url then
screen.wait_for = M.TRANSITION.DONE
msg.post(screen.transition_url, message_id, message)
coroutine.yield()
screen.wait_for = nil
else
log("transition() no transition url - ignoring")
end
end
local function focus_gained(screen, previous_screen)
log("focus_gained()", screen.id)
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.GAINED, {id = previous_screen and previous_screen.id})
msg.post(screen.focus_url, M.FOCUS.GAINED, { id = previous_screen and previous_screen.id })
else
log("focus_gained() no focus url - ignoring")
end
end
local function focus_lost(screen, next_screen)
log("focus_lost()", screen.id)
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.LOST, {id = next_screen and next_screen.id})
msg.post(screen.focus_url, M.FOCUS.LOST, { id = next_screen and next_screen.id })
else
log("focus_lost() no focus url - ignoring")
end
end
local function change_timestep(screen)
if screen.proxy then
screen.changed_timestep = true
msg.post(screen.proxy, "set_time_step", { mode = 0, factor = screen.timestep_below_popup })
end
end
local function reset_timestep(screen)
if screen.proxy and screen.changed_timestep then
msg.post(screen.proxy, "set_time_step", { mode = 0, factor = 1 })
screen.changed_timestep = false
end
end
@@ -207,10 +349,15 @@ local function disable(screen, next_screen)
change_context(screen)
release_input(screen)
focus_lost(screen, next_screen)
if next_screen and next_screen.popup then
change_timestep(screen)
else
reset_timestep(screen)
end
screen.co = nil
if cb then cb() end
end)
coroutine.resume(co)
assert(coroutine.resume(co))
end
local function enable(screen, previous_screen)
@@ -221,10 +368,11 @@ local function enable(screen, previous_screen)
change_context(screen)
acquire_input(screen)
focus_gained(screen, previous_screen)
reset_timestep(screen)
screen.co = nil
if cb then cb() end
end)
coroutine.resume(co)
assert(coroutine.resume(co))
end
local function show_out(screen, next_screen, cb)
@@ -232,21 +380,26 @@ local function show_out(screen, next_screen, cb)
local co
co = coroutine.create(function()
active_transition_count = active_transition_count + 1
notify_listeners(M.SCREEN_TRANSITION_OUT_STARTED, { screen = screen.id, next_screen = next_screen.id })
screen.co = co
change_context(screen)
release_input(screen)
focus_lost(screen, next_screen)
reset_timestep(screen)
-- if the next screen is a popup we want the current screen to stay visible below the popup
-- if the next screen isn't a popup the current one should be unloaded and transitioned out
local next_is_popup = next_screen and not next_screen.popup
local next_is_popup = next_screen and next_screen.popup
local current_is_popup = screen.popup
if (next_is_popup and not current_is_popup) or (current_is_popup) then
if (not next_is_popup and not current_is_popup) or (current_is_popup) then
transition(screen, M.TRANSITION.SHOW_OUT, { next_screen = next_screen.id })
unload(screen)
elseif next_is_popup then
change_timestep(screen)
end
screen.co = nil
active_transition_count = active_transition_count - 1
if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_OUT_FINISHED, { screen = screen.id, next_screen = next_screen.id })
end)
coroutine.resume(co)
end
@@ -256,32 +409,23 @@ local function show_in(screen, previous_screen, reload, cb)
local co
co = coroutine.create(function()
active_transition_count = active_transition_count + 1
notify_listeners(M.SCREEN_TRANSITION_IN_STARTED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
screen.co = co
change_context(screen)
if reload and screen.loaded then
log("show_in() reloading", screen.id)
unload(screen)
end
-- if the screen has been preloaded we need to enable it
if screen.preloaded then
log("show_in() screen was preloaded", screen.id)
msg.post(screen.proxy, ENABLE)
screen.loaded = true
screen.preloaded = false
-- the screen could be loaded if the previous screen was a popup
-- and the popup asked to show this screen again
-- in that case we shouldn't attempt to load it again
elseif not screen.loaded then
log("show_in() loading screen", screen.id)
async_load(screen)
end
load(screen)
stack[#stack + 1] = screen
reset_timestep(screen)
transition(screen, M.TRANSITION.SHOW_IN, { previous_screen = previous_screen and previous_screen.id })
acquire_input(screen)
focus_gained(screen, previous_screen)
screen.co = nil
active_transition_count = active_transition_count - 1
if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_IN_FINISHED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
end)
coroutine.resume(co)
end
@@ -291,17 +435,11 @@ local function back_in(screen, previous_screen, cb)
local co
co = coroutine.create(function()
active_transition_count = active_transition_count + 1
notify_listeners(M.SCREEN_TRANSITION_IN_STARTED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
screen.co = co
change_context(screen)
if screen.preloaded then
log("back_in() screen was preloaded", screen.id)
msg.post(screen.proxy, ENABLE)
screen.preloaded = false
screen.loaded = true
elseif not screen.loaded then
log("back_in() loading screen", screen.id)
async_load(screen)
end
load(screen)
reset_timestep(screen)
if previous_screen and not previous_screen.popup then
transition(screen, M.TRANSITION.BACK_IN, { previous_screen = previous_screen.id })
end
@@ -310,6 +448,7 @@ local function back_in(screen, previous_screen, cb)
screen.co = nil
active_transition_count = active_transition_count - 1
if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_IN_FINISHED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
end)
coroutine.resume(co)
end
@@ -318,16 +457,21 @@ local function back_out(screen, next_screen, cb)
log("back_out()", screen.id)
local co
co = coroutine.create(function()
notify_listeners(M.SCREEN_TRANSITION_OUT_STARTED, { screen = screen.id, next_screen = next_screen and next_screen.id })
active_transition_count = active_transition_count + 1
screen.co = co
change_context(screen)
release_input(screen)
focus_lost(screen, next_screen)
if next_screen and screen.popup then
reset_timestep(next_screen)
end
transition(screen, M.TRANSITION.BACK_OUT, { next_screen = next_screen and next_screen.id })
unload(screen)
screen.co = nil
active_transition_count = active_transition_count - 1
if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_OUT_FINISHED, { screen = screen.id, next_screen = next_screen and next_screen.id })
end)
coroutine.resume(co)
end
@@ -373,9 +517,12 @@ end
function M.show(id, options, data, cb)
assert(id, "You must provide a screen id")
if M.is_busy() then
log("show() monarch is busy, ignoring request")
return false
end
local callbacks = callback_tracker()
id = tohash(id)
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
@@ -396,13 +543,13 @@ function M.show(id, options, data, cb)
-- close all popups
while top.popup do
stack[#stack] = nil
show_out(top, screen)
show_out(top, screen, callbacks.track())
top = stack[#stack]
end
-- unload and transition out from top
-- unless we're showing the same screen as is already visible
if top and top.id ~= screen.id then
show_out(top, screen)
show_out(top, screen, callbacks.track())
end
end
end
@@ -419,7 +566,9 @@ function M.show(id, options, data, cb)
end
-- show screen
show_in(screen, top, options and options.reload, cb)
show_in(screen, top, options and options.reload, callbacks.track())
if cb then callbacks.when_done(cb) end
return true
end
@@ -431,9 +580,12 @@ end
-- @return true if successfully going back, false if busy performing another operation
function M.back(data, cb)
if M.is_busy() then
log("back() monarch is busy, ignoring request")
return false
end
local callbacks = callback_tracker()
local screen = table.remove(stack)
if screen then
log("back()", screen.id)
@@ -445,7 +597,7 @@ function M.back(data, cb)
if data then
top.data = data
end
back_in(top, screen, cb)
back_in(top, screen, callbacks.track())
end)
else
back_out(screen, top)
@@ -453,26 +605,34 @@ function M.back(data, cb)
if data then
top.data = data
end
back_in(top, screen, cb)
back_in(top, screen, callbacks.track())
end
end
elseif cb then
cb()
end
if cb then callbacks.when_done(cb) end
return true
end
--- Preload a screen. This will load but not enable and show a screen. Useful for "heavier" screens
-- that you wish to show without any delay.
-- @param id (string|hash) - Id of the screen to preload
-- @param cb (function) - Optional callback to invoke when screen is loaded
function M.preload(id, cb)
if M.is_busy() then
log("preload() monarch is busy, ignoring request")
return false
end
assert(id, "You must provide a screen id")
id = tohash(id)
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
local screen = screens[id]
if screen.preloaded then
log("preload()", screen.id)
if screen.preloaded or screen.loaded then
if cb then cb() end
return
end
@@ -480,14 +640,10 @@ function M.preload(id, cb)
co = coroutine.create(function()
screen.co = co
change_context(screen)
screen.wait_for = PROXY_LOADED
msg.post(screen.proxy, ASYNC_LOAD)
coroutine.yield()
screen.preloaded = true
screen.wait_for = nil
preload(screen)
if cb then cb() end
end)
coroutine.resume(co)
assert(coroutine.resume(co))
end
@@ -549,6 +705,26 @@ function M.bottom(offset)
return screen and screen.id
end
local function url_to_key(url)
return (url.socket or hash("")) .. (url.path or hash("")) .. (url.fragment or hash(""))
end
--- Add a listener to be notified of when screens are shown or hidden
-- @param url The url to notify, nil for current url
function M.add_listener(url)
url = url or msg.url()
listeners[url_to_key(url)] = url
end
--- Remove a previously added listener
-- @param url The url to remove, nil for current url
function M.remove_listener(url)
url = url or msg.url()
listeners[url_to_key(url)] = nil
end
function M.dump_stack()
local s = ""

View File

@@ -1,26 +1,31 @@
local monarch
go.property("screen_proxy", msg.url("#collectionproxy"))
go.property("screen_id", hash(""))
go.property("screen_id", hash("UNIQUE ID HERE"))
go.property("popup", false)
go.property("popup_on_popup", false)
go.property("timestep_below_popup", 1)
go.property("transition_url", msg.url())
go.property("focus_url", msg.url())
function init(self)
print("WARNING - screen.script is deprecated. Please use screen_proxy.script")
monarch = require "monarch.monarch"
local url = msg.url()
assert(not self.popup_on_popup or (self.popup_on_popup and self.popup), "Popup on Popups can only be set if the Popup flag is set")
monarch.register(
self.screen_id,
self.screen_proxy,
{
popup = self.popup,
popup_on_popup = self.popup_on_popup,
transition_url = self.transition_url,
focus_url = self.focus_url
}
)
assert(self.screen_proxy ~= url, "You must specify either a proxy URL")
assert(self.timestep_below_popup >= 0, "Timestep must be positive")
local settings = {
popup = self.popup,
popup_on_popup = self.popup_on_popup,
transition_url = self.transition_url ~= url and self.transition_url or nil,
focus_url = self.focus_url ~= url and self.focus_url or nil,
timestep_below_popup = self.timestep_below_popup,
}
monarch.register_proxy(self.screen_id, self.screen_proxy, settings)
end
function final(self)
@@ -34,11 +39,6 @@ function on_message(self, message_id, message, sender)
monarch.back()
elseif message_id == hash("back") then
monarch.back()
elseif message_id == monarch.TRANSITION.SHOW_IN
or message_id == monarch.TRANSITION.SHOW_OUT
or message_id == monarch.TRANSITION.BACK_IN
or message_id == monarch.TRANSITION.BACK_OUT then
msg.post(sender, monarch.TRANSITION.DONE)
else
monarch.on_message(message_id, message, sender)
end

View File

@@ -0,0 +1,39 @@
local monarch
go.property("screen_factory", msg.url("#collectionfactory"))
go.property("screen_id", hash("UNIQUE ID HERE"))
go.property("popup", false)
go.property("popup_on_popup", false)
go.property("transition_id", hash(""))
go.property("focus_id", hash(""))
function init(self)
monarch = require "monarch.monarch"
assert(not self.popup_on_popup or (self.popup_on_popup and self.popup), "Popup on Popups can only be set if the Popup flag is set")
assert(self.screen_factory ~= msg.url(), "You must specify either a factory URL")
local settings = {
popup = self.popup,
popup_on_popup = self.popup_on_popup,
transition_id = self.transition_id,
focus_id = self.focus_id,
}
monarch.register_factory(self.screen_id, self.screen_factory, settings)
end
function final(self)
monarch.unregister(self.screen_id)
end
function on_message(self, message_id, message, sender)
if message_id == hash("show") then
monarch.show(self.screen_id, { clear = message.clear })
elseif message_id == hash("hide") then
monarch.back()
elseif message_id == hash("back") then
monarch.back()
else
monarch.on_message(message_id, message, sender)
end
end

View File

@@ -0,0 +1,44 @@
local monarch
go.property("screen_proxy", msg.url("#collectionproxy"))
go.property("screen_id", hash("UNIQUE ID HERE"))
go.property("popup", false)
go.property("popup_on_popup", false)
go.property("timestep_below_popup", 1)
go.property("transition_url", msg.url())
go.property("focus_url", msg.url())
function init(self)
monarch = require "monarch.monarch"
local url = msg.url()
assert(not self.popup_on_popup or (self.popup_on_popup and self.popup), "Popup on Popups can only be set if the Popup flag is set")
assert(self.screen_proxy ~= url, "You must specify either a proxy URL")
assert(self.timestep_below_popup >= 0, "Timestep must be positive")
local settings = {
popup = self.popup,
popup_on_popup = self.popup_on_popup,
transition_url = self.transition_url ~= url and self.transition_url or nil,
focus_url = self.focus_url ~= url and self.focus_url or nil,
timestep_below_popup = self.timestep_below_popup,
}
monarch.register_proxy(self.screen_id, self.screen_proxy, settings)
end
function final(self)
monarch.unregister(self.screen_id)
end
function on_message(self, message_id, message, sender)
if message_id == hash("show") then
monarch.show(self.screen_id, { clear = message.clear })
elseif message_id == hash("hide") then
monarch.back()
elseif message_id == hash("back") then
monarch.back()
else
monarch.on_message(message_id, message, sender)
end
end

View File

@@ -88,6 +88,22 @@ function M.scale_out(node, from, easing, duration, delay, cb)
gui.animate(node, gui.PROP_SCALE, ZERO_SCALE, easing, duration, delay, cb)
end
function M.fade_out(node, from, easing, duration, delay, cb)
local to = gui.get_color(node)
to.w = 1
gui.set_color(node, to)
to.w = 0
gui.animate(node, gui.PROP_COLOR, to, easing, duration, delay, cb)
end
function M.fade_in(node, from, easing, duration, delay, cb)
local to = gui.get_color(node)
to.w = 0
gui.set_color(node, to)
to.w = 1
gui.animate(node, gui.PROP_COLOR, to, easing, duration, delay, cb)
end
--- Create a transition for a node
-- @return Transition instance
function M.create(node)
@@ -255,4 +271,16 @@ function M.in_left_out_left(node, duration, delay, easing)
end
function M.fade_in_out(node, duration, delay, easing)
assert(node, "You must provide a node")
assert(duration, "You must provide a duration")
easing = easing or easings.QUAD()
return M.create(node)
.show_in(M.fade_in, easing.OUT, duration, delay or 0)
.show_out(M.fade_out, easing.IN, duration, delay or 0)
.back_in(M.fade_in, easing.OUT, duration, delay or 0)
.back_out(M.fade_out, easing.IN, duration, delay or 0)
end
return M

View File

@@ -0,0 +1,39 @@
local M = {}
function M.create()
local instance = {}
local callback = nil
local callback_count = 0
--- Create a callback function and track when it is done
-- @return Callback function
function instance.track()
callback_count = callback_count + 1
return function()
callback_count = callback_count - 1
if callback_count == 0 and callback then
callback()
end
end
end
--- Call a function when all callbacks have been triggered
-- @param cb Function to call when all
function instance.when_done(cb)
callback = cb
if callback_count == 0 then
callback()
end
end
return instance
end
return setmetatable(M, {
__call = function(_, ...)
return M.create(...)
end
})

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "screen1"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -62,7 +62,7 @@ embedded_instances {
id: "screen2"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_factory.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -81,10 +81,10 @@ embedded_instances {
" }\n"
"}\n"
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/test/data/screen2.collection\\\"\\n"
"exclude: false\\n"
" id: \"collectionfactory\"\n"
" type: \"collectionfactory\"\n"
" data: \"prototype: \\\"/test/data/screen2.collection\\\"\\n"
"load_dynamically: false\\n"
"\"\n"
" position {\n"
" x: 0.0\n"
@@ -120,7 +120,7 @@ embedded_instances {
id: "popup1"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -142,11 +142,6 @@ embedded_instances {
" value: \"true\"\n"
" type: PROPERTY_TYPE_BOOLEAN\n"
" }\n"
" properties {\n"
" id: \"popup_on_popup\"\n"
" value: \"false\"\n"
" type: PROPERTY_TYPE_BOOLEAN\n"
" }\n"
"}\n"
"embedded_components {\n"
" id: \"collectionproxy\"\n"
@@ -188,7 +183,7 @@ embedded_instances {
id: "popup2"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -256,7 +251,7 @@ embedded_instances {
id: "transition1"
data: "components {\n"
" id: \"screen\"\n"
" component: \"/monarch/screen.script\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -315,3 +310,43 @@ embedded_instances {
z: 1.0
}
}
embedded_instances {
id: "listener1"
data: ""
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale3 {
x: 1.0
y: 1.0
z: 1.0
}
}
embedded_instances {
id: "listener2"
data: ""
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale3 {
x: 1.0
y: 1.0
z: 1.0
}
}

52
test/msg.lua Normal file
View File

@@ -0,0 +1,52 @@
local mock = require "deftest.mock.mock"
local M = {}
local recipients = {}
local history = {}
local function get_recipient(url)
recipients[url] = recipients[url] or {}
return recipients[url]
end
local function post(url, message_id, message)
local data = { url = url, message_id = message_id, message = message }
history[#history + 1] = data
local recipient = get_recipient(url)
recipient[#recipient + 1] = data
msg.post.original(url, message_id, message or {})
end
function M.mock()
recipients = {}
history = {}
mock.mock(msg)
msg.post.replace(post)
end
function M.unmock()
mock.unmock(msg)
end
function M.messages(url)
return url and get_recipient(url) or history
end
function M.first(url)
local messages = url and get_recipient(url) or history
return messages[1]
end
function M.last(url)
local messages = url and get_recipient(url) or history
return messages[#messages]
end
return M

View File

@@ -5,5 +5,5 @@ local test_monarch = require "test.test_monarch"
function init(self)
deftest.add(test_monarch)
deftest.run()
deftest.run({ coverage = { enabled = true }})
end

View File

@@ -1,4 +1,6 @@
local cowait = require "test.cowait"
local mock_msg = require "test.msg"
local unload = require "deftest.util.unload"
local monarch = require "monarch.monarch"
local SCREEN1_STR = hash("screen1")
@@ -55,12 +57,18 @@ return function()
describe("monarch", function()
before(function()
mock_msg.mock()
monarch = require "monarch.monarch"
screens_instances = collectionfactory.create("#screensfactory")
end)
after(function()
package.loaded["monarch.monarch"] = nil
while #monarch.get_stack() > 0 do
monarch.back()
wait_until_not_busy()
end
mock_msg.unmock()
unload.unload("monarch%..*")
for id,instance_id in pairs(screens_instances) do
go.delete(instance_id)
end
@@ -229,5 +237,60 @@ return function()
assert(monarch.is_busy())
assert(wait_until_not_busy())
end)
it("should ignore any preload calls while busy", function()
monarch.show(TRANSITION1)
-- previously a call to preload() while also showing a screen would
-- lock up monarch. See issue #32
monarch.preload(TRANSITION1)
assert(wait_until_shown(TRANSITION1), "Transition1 was never shown")
end)
it("should be able to notify listeners of navigation events", function()
local URL1 = msg.url(screens_instances[hash("/listener1")])
local URL2 = msg.url(screens_instances[hash("/listener2")])
monarch.add_listener(URL1)
monarch.add_listener(URL2)
monarch.show(SCREEN1)
assert(mock_msg.messages(URL1)[1].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[1].message.screen == SCREEN1)
assert(mock_msg.messages(URL2)[1].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL2)[1].message.screen == SCREEN1)
assert(wait_until_not_busy())
assert(mock_msg.messages(URL1)[2].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[2].message.screen == SCREEN1)
assert(mock_msg.messages(URL2)[2].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL2)[2].message.screen == SCREEN1)
monarch.remove_listener(URL2)
monarch.show(SCREEN2)
assert(wait_until_not_busy())
assert(#mock_msg.messages(URL1) == 6)
assert(#mock_msg.messages(URL2) == 2)
assert(mock_msg.messages(URL1)[3].message_id == monarch.SCREEN_TRANSITION_OUT_STARTED)
assert(mock_msg.messages(URL1)[3].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[4].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[4].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[5].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[5].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[6].message_id == monarch.SCREEN_TRANSITION_OUT_FINISHED)
assert(mock_msg.messages(URL1)[6].message.screen == SCREEN1)
monarch.back()
assert(wait_until_not_busy())
assert(#mock_msg.messages(URL1) == 10)
assert(#mock_msg.messages(URL2) == 2)
assert(mock_msg.messages(URL1)[7].message_id == monarch.SCREEN_TRANSITION_OUT_STARTED)
assert(mock_msg.messages(URL1)[7].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[8].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[8].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[9].message_id == monarch.SCREEN_TRANSITION_OUT_FINISHED)
assert(mock_msg.messages(URL1)[9].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[10].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[10].message.screen == SCREEN1)
end)
end)
end