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

Compare commits

..

24 Commits
3.2.0 ... 3.6.0

Author SHA1 Message Date
Björn Ritzl
c0d45c3d5c Added keep_loaded option to preload()
Fixes #88
2022-07-04 22:43:25 +02:00
Björn Ritzl
bcc6264cd4 Remove screen from stack when unregistered
Fixes #87
2022-05-29 14:47:34 +02:00
Björn Ritzl
1dec704047 Delay reset of timestep when going back from popup
Fixes #84
2022-02-23 12:46:04 +01:00
Björn Ritzl
9764c68475 Update callback_tracker.lua
Fixes #83
2022-01-27 12:16:43 +01:00
Björn Ritzl
d57d550859 Fixed issues with missing variables 2022-01-27 12:15:52 +01:00
Björn Ritzl
17df189089 Replace existing transition
Fixes #82
2022-01-13 19:40:24 +01:00
Björn Ritzl
387a1805eb Added option to change timestep for screen when below popup
Fixes #73
2021-09-19 23:08:31 +02:00
Björn Ritzl
e26da2c6c5 Removed old change log file 2021-09-19 23:05:43 +02:00
Björn Ritzl
c75b1b0044 A screen added with no_stack should not manipulate the stack when hidden 2021-08-19 14:53:05 +02:00
Björn Ritzl
d3799a93ff Make sure that callbacks aren't invoked more than once 2021-08-19 14:35:05 +02:00
Björn Ritzl
76d4ca2927 Added monarch.clear() 2021-07-20 00:27:34 +02:00
Björn Ritzl
9e81b3a327 Removed one-frame delay during show/back-in
If the screen had a transition the delay caused the screen to render in its initial position for one frame before the transition was applied
2021-07-01 15:04:52 +02:00
Björn Ritzl
4927d6e766 Update stack earlier when showing new screen
Fixes #76
2021-06-27 00:34:32 +02:00
Björn Ritzl
20cf731fdb Wait with screen unload until next screen is loaded and ready to be shown (#74)
* Removed screen flicker when transitioning between screens

* Moved advanced example to subfolder. Added basic example.

* Remove flicker on back navigation too

* Fix issue with no_stack screens added at any time
2021-06-24 07:45:09 +02:00
Björn Ritzl
466b558e73 Update game.project 2021-04-26 22:49:08 +02:00
Björn Ritzl
398e78670f Moved advanced example to subfolder. Added basic example. 2021-04-26 22:48:44 +02:00
Björn Ritzl
a19bdd78e8 Added monarch.is_popup() (#70) 2021-02-14 11:53:52 +01:00
Björn Ritzl
5771a40e68 Update README.md 2021-01-10 14:23:54 +01:00
Björn Ritzl
1099390cce Merge branch 'master' of https://github.com/britzl/monarch 2021-01-10 14:20:35 +01:00
Björn Ritzl
36b92b5294 Split up the docs and added FAQ 2021-01-10 14:20:33 +01:00
Brian
03baa3eeb3 Only allow editor script to target .gui (#67)
* Only allow editor script to target .gui

Disable .collection and .gui_script

* whitespace
2020-11-23 08:32:12 +01:00
Marius Petcu
00808c0d56 Implement better behaviour for replace() on popups (#64) 2020-05-28 17:18:51 +02:00
Björn Ritzl
4989939817 Always print errors. Do not rely on monarch debugging 2020-05-28 08:20:43 +02:00
Björn Ritzl
bf880b80fd Print errors if caught in the callback tracker 2020-05-28 08:08:35 +02:00
45 changed files with 1413 additions and 669 deletions

View File

@@ -1,60 +0,0 @@
## Monarch 2.8.0 [britzl released 2018-06-10]
NEW: Prevent show/hide operations while busy showing/hiding another screen
FIX: Make sure to properly finish active transitions when layout changes
## Monarch 2.7.0 [britzl released 2018-06-04]
NEW: Added monarch.top([offset]) and monarch.bottom([offset]) to get screen id of top and bottom screens (w. optional offset)
NEW: Transition messages now contain `next_screen` or `previous_screen`
## Monarch 2.6.1 [britzl released 2018-06-04]
FIX: Check if screen has already been preloaded before trying to preload it again (the callback will still be invoked).
## Monarch 2.6.0 [britzl released 2018-06-03]
NEW: monarch.preload() to load but not show a screen. Useful for content heavy screens that you wish to show without delay.
## Monarch 2.5.0 [britzl released 2018-06-01]
NEW: Transitions will send a `transition_done` message to the creator of the transition to notify that the transition has finished. The `message` will contain which transition that was finished.
## Monarch 2.4.0 [britzl released 2018-05-26]
NEW: Screen transitions are remembered so that they can be replayed when the screen layout changes.
## Monarch 2.3.0 [britzl released 2018-03-24]
CHANGE: The functions in monarch.lua that previously only accepted a hash as screen id now also accepts strings (and does the conversion internally)
## Monarch 2.2.0 [britzl released 2018-03-19]
NEW: Transitions now handle layout changes (via `layout_changed` message)
NEW: Transitions can now be notified of changes in window size using transition.window_resize(width, height)
## Monarch 2.1 [britzl released 2017-12-27]
NEW: Added Popup on Popup flag that allows a popup to be shown on top of another popup
## Monarch 2.0 [britzl released 2017-12-08]
BREAKING CHANGE: If you are using custom screen transitions (ie your own transition functions) you need to make a change to the function. The previous function signature was ```(node, to, easing, duration, delay, url)``` where ```url``` was the URL to where the ```transition_done``` message was supposed to be posted. The new function signature for a transition function is: ```(node, to, easing, duration, delay, cb)``` where ```cb``` is a function that should be invoked when the transition is completed.
FIX: Fixed issues related to screen transitions.
FIX: Code cleanup to reduce code duplication.
FIX: Improved documentation regarding transitions.
## Monarch 1.4 [britzl released 2017-12-06]
FIX: Several bugfixes for specific corner cases.
## Monarch 1.3 [britzl released 2017-12-01]
FIX: monarch.back(data, cb) set the data on the previous screen not the new current screen.
NEW: monarch.is_top(id)
NEW: monarch.get_stack()
NEW: monarch.in_stack(id)
## Monarch 1.2 [britzl released 2017-11-28]
NEW: Message id constants exposed from the Monarch module
NEW: Focus lost/gained contains id of next/previous screen
## Monarch 1.1 [britzl released 2017-11-22]
FIX: Bugfixes for transitions and state under certain circumstances
NEW: Added 'reload' option to show() command.
## Monarch 1.0 [britzl released 2017-09-28]
First public stable release
## Monarch 0.9 [britzl released 2017-09-17]

395
README.md
View File

@@ -28,9 +28,9 @@ Right click in on a`.gui` file in the outline and selected the menu item, it cre
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:
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 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.
@@ -45,9 +45,9 @@ For proxies the recommended setup is to create one game object per screen and pe
![](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:
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 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.
@@ -68,21 +68,21 @@ Sometimes it might be desirable to have a screen that contains one or more sub-s
The navigation in Monarch is based around a stack of screens. When a screen is shown it is pushed to the top of the stack. When going back to a previous screen the topmost screen on the stack is removed. Example:
* Showing screen A
* Stack is ```[A]```
* Stack is `[A]`
* Showing screen B
* Stack is ```[A, B]``` - (B is on top)
* Stack is `[A, B]` - (B is on top)
* Going back
* Stack is ```[A]```
* Stack is `[A]`
### Showing a new screen
You show a screen in one of two ways:
1. Post a ```show``` message to the screen script (either `screen_proxy.script` or `screen_factory.script`)
2. Call ```monarch.show()``` (see below)
1. Post a `show` message to the screen script (either `screen_proxy.script` or `screen_factory.script`)
2. Call `monarch.show()` (see below)
Showing a screen will push it to the top of the stack and trigger an optional transition. The previous screen will be hidden (with an optional transition) unless the screen to be shown is a [popup](#popups).
NOTE: You must ensure that the ```init()``` function of the screen script (either `screen_proxy.script` or `screen_factory.script`) has run. The ```init()``` function is responsible for registering the screen and it's not possible to show it until this has happened. A good practice is to delay the first call by posting a message to a controller script or similar before calling ```monarch.show()``` the first time:
NOTE: You must ensure that the `init()` function of the screen script (either `screen_proxy.script` or `screen_factory.script`) has run. The `init()` function is responsible for registering the screen and it's not possible to show it until this has happened. A good practice is to delay the first call by posting a message to a controller script or similar before calling `monarch.show()` the first time:
function init(self)
msg.post("#", "show_first_screen")
@@ -93,17 +93,17 @@ NOTE: You must ensure that the ```init()``` function of the screen script (eithe
end
#### Preventing duplicates in the stack
You can pass an optional ```clear``` flag when showing a screen (either as a key value pair in the options table when calling ```monarch.show()``` or in the message). If the clear flag is set Monarch will search the stack for the screen in question. If the screen already exists in the stack and the ```clear``` flag is set Monarch will remove all screens between the current top and the screen in question. Example:
You can pass an optional `clear` flag when showing a screen (either as a key value pair in the options table when calling `monarch.show()` or in the message). If the clear flag is set Monarch will search the stack for the screen in question. If the screen already exists in the stack and the `clear` flag is set Monarch will remove all screens between the current top and the screen in question. Example:
* Stack is ```[A, B, C, D]``` - (D is on top)
* A call to ```monarch.show(B, { clear = true })``` is made
* Stack is ```[A, B]```
* Stack is `[A, B, C, D]` - (D is on top)
* A call to `monarch.show(B, { clear = true })` is made
* Stack is `[A, B]`
As opposed to if the ```clear``` flag was not set:
As opposed to if the `clear` flag was not set:
* Stack is ```[A, B, C, D]``` - (D is on top)
* A call to ```monarch.show(B, { clear = false })``` is made
* Stack is ```[A, B, C, D, B]``` - (B is on top)
* Stack is `[A, B, C, D]` - (D is on top)
* A call to `monarch.show(B, { clear = false })` is made
* Stack is `[A, B, C, D, B]` - (B is on top)
#### Showing a screen without adding it to the stack
Monarch can also show a screen without adding it to the stack. This can be used to for instance load a collection containing a background that you want to have visible at all times. You show and hide such a screen like this:
@@ -117,8 +117,8 @@ Monarch can also show a screen without adding it to the stack. This can be used
### Going back to a previous screen
You navigate back in the screen hierarchy in one of two ways:
1. Post a ```back``` message to the screen script (either `screen_proxy.script` or `screen_factory.script`)
2. Call ```monarch.back()``` (see below)
1. Post a `back` message to the screen script (either `screen_proxy.script` or `screen_factory.script`)
2. Call `monarch.back()` (see below)
## Input focus
@@ -131,157 +131,33 @@ A screen that is flagged as a popup (see [list of screen properties](#creating-s
### Popup on normal screen
If a popup is shown on top of a non-popup the current top screen will not be unloaded and instead remain visible in the background:
* Stack is ```[A, B]```
* A call to ```monarch.show(C)``` is made and C is a popup
* Stack is ```[A, B, C]``` and B will still be visible
* Stack is `[A, B]`
* A call to `monarch.show(C)` is made and C is a popup
* Stack is `[A, B, C]` and B will still be visible
### Popup on popup
If a popup is at the top of the stack and another popup is shown the behavior will depend on if the new popup has the Popup on Popup flag set or not. If the Popup on Popup flag is set the underlying popup will remain visible.
* Stack is ```[A, B, C]``` and C is a popup
* A call to ```monarch.show(D)``` is made and D is a popup with the popup on popup flag set
* Stack is ```[A, B, C, D]```
* Stack is `[A, B, C]` and C is a popup
* A call to `monarch.show(D)` is made and D is a popup with the popup on popup flag set
* Stack is `[A, B, C, D]`
If the Popup on Popup flag is not set then the underlying popup will be closed, just as when showing a normal screen on top of a popup (see above).
* Stack is ```[A, B, C]``` and C is a popup
* A call to ```monarch.show(D)``` is made and D is a popup without the popup on popup flag set
* Stack is ```[A, B, D]```
* Stack is `[A, B, C]` and C is a popup
* A call to `monarch.show(D)` is made and D is a popup without the popup on popup flag set
* Stack is `[A, B, D]`
### Screen on popup
If a screen is shown on top of one or more popups they will all be removed from the stack:
* Stack is ```[A, B, C, D]``` and C and D are popups
* A call to ```monarch.show(E)``` is made and E is not a popup
* Stack is ```[A, B, E]```
* Stack is `[A, B, C, D]` and C and D are popups
* A call to `monarch.show(E)` is made and E is not a popup
* Stack is `[A, B, E]`
## Transitions
You can add optional transitions when navigating between screens. The default behavior is that screen navigation is instant but if you have defined a transition for a screen Monarch will wait until the transition is completed before proceeding. The `Transition Url` (proxy) or `Transition Id` (collectionfactory) property described above should be the URL/Id to a script with an ```on_message``` handlers for the following messages:
* ```transition_show_in``` (constant defined as ```monarch.TRANSITION.SHOW_IN```)
* ```transition_show_out``` (constant defined as ```monarch.TRANSITION.SHOW_OUT```)
* ```transition_back_in``` (constant defined as ```monarch.TRANSITION.BACK_IN```)
* ```transition_back_out``` (constant defined as ```monarch.TRANSITION.BACK_OUT```)
When a transition is completed it is up to the developer to send a ```transition_done``` (constant ```monarch.TRANSITION.DONE```) message back to the sender to indicate that the transition is completed and that Monarch can continue the navigation sequence.
### Predefined transitions
Monarch comes with a system for setting up transitions easily in a gui_script using the ```monarch.transitions.gui``` module. Example:
local transitions = require "monarch.transitions.gui"
local monarch = require "monarch.monarch"
function init(self)
-- create transitions for the node 'root'
-- the node will slide in/out from left and right with
-- a specific easing, duration and delay
self.transition = transitions.create(gui.get_node("root"))
.show_in(transitions.slide_in_right, gui.EASING_OUTQUAD, 0.6, 0)
.show_out(transitions.slide_out_left, gui.EASING_INQUAD, 0.6, 0)
.back_in(transitions.slide_in_left, gui.EASING_OUTQUAD, 0.6, 0)
.back_out(transitions.slide_out_right, gui.EASING_INQUAD, 0.6, 0)
end
function on_message(self, message_id, message, sender)
self.transition.handle(message_id, message, sender)
-- you can also check when a transition has completed:
if message_id == monarch.TRANSITION.DONE and message.transition == monarch.TRANSITION.SHOW_IN then
print("Show in done!")
end
end
It is also possible to assign transitions to multiple nodes:
function init(self)
self.transition = transitions.create() -- note that no node is passed to transition.create()!
.show_in(gui.get_node("node1"), transitions.slide_in_right, gui.EASING_OUTQUAD, 0.6, 0)
.show_in(gui.get_node("node2"), transitions.slide_in_right, gui.EASING_OUTQUAD, 0.6, 0)
end
The predefined transitions provided by ```monarch.transitions.gui``` are:
* ```slide_in_right```
* ```slide_in_left```
* ```slide_in_top```
* ```slide_in_bottom```
* ```slide_out_right```
* ```slide_out_left```
* ```slide_out_top```
* ```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:
* ```transitions.in_right_out_left(node, duration, [delay], [easing])```
* ```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.
* ```duration``` (number) - Transition duration in seconds.
* ```delay``` (number) - Transition delay in seconds.
* ```easing``` (table) - Easing table, created from a function provided by ```monarch.transitions.easings```
**RETURN**
* ```instance``` - The created transition instance
### Custom transitions
You can create and use your own transition as long as the provided transition function has the following function signature:
custom_transition(node, to, easing, duration, delay, cb)
**PARAMETERS**
* ```node``` (node) - Gui node to animate.
* ```to``` (vector3) - Target position.
* ```easing``` (number) - One of gui.EASING_* constants.
* ```duration``` (number) - Transition duration in seconds.
* ```delay``` (number) - Transition delay in seconds.
* ```cb``` (function) - This function must be called when the transition is completed.
### Dynamic orientation and resized windows
When using dynamic screen orientation together with gui layouts or using transitions on a platform where the window can be resized it's important to make sure that the created transitions adapt to the change in orientation or window size. The transition system takes care of layout changes automatically, but when it comes to changes in window size you need to notify the transition manually:
local transitions = require "monarch.transitions.gui"
function init(self)
self.transition = transitions.create(gui.get_node("root"))
end
function on_message(self, message_id, message, sender)
if message_id == hash("my_resize_message") then
self.transition.window_resized(message.width, message.height)
end
end
### Screen stack info and transitions
The transition message sent to the Transition Url specified in the screen configuration contains additional information about the transition. For the ```transition_show_in``` and ```transition_back_out``` messages the message contains the previous screen id:
function on_message(self, message_id, message, sender)
if message_id == hash("transition_show_in") or message_id == hash("transition_back_out") then
print(message.previous_screen)
end
end
For the ```transition_show_out``` and ```transition_back_in``` messages the message contains the next screen id:
function on_message(self, message_id, message, sender)
if message_id == hash("transition_show_out") or message_id == hash("transition_back_in") then
print(message.next_screen)
end
end
This information can be used to create dynamic transitions where the direction of the transition depends on the previous/next screen
You can add optional transitions when navigating between screens. This is [described in detail here](/README_TRANSITIONS.md).
## Screen focus gain/loss
@@ -299,210 +175,15 @@ Monarch will send focus gain and focus loss messages if a `Focus Url` (proxy) or
## Callbacks
Both the ```monarch.show()``` and ```monarch.back()``` functions take an optional callback function that will be invoked when the ```transition_show_in``` (or the ```transition_back_in``` in the case of a ```monarch.back()``` call) transition is completed. The transition is considered completed when a ```transition_done``` message has been received (see section on [transitions](#transitions) above).
Both the `monarch.show()` and `monarch.back()` functions take an optional callback function that will be invoked when the `transition_show_in` (or the `transition_back_in` in the case of a `monarch.back()` call) transition is completed. The transition is considered completed when a `transition_done` message has been received (see section on [transitions](#transitions) above).
## Monarch API
The full [Monarch API is documented here](/README_API.md).
### monarch.show(screen_id, [options], [data], [callback])
Show a Monarch screen. Note that the screen must be registered before it can be shown. The ```init()``` function of the screen script (either `screen_proxy.script` or `screen_factory.script`) takes care of registration. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to show.
* ```options``` (table) - Options when showing the new screen (see below).
* ```data``` (table) - Optional data to associate with the screen. Retrieve using ```monarch.data()```.
* ```callback``` (function) - Optional function to call when the new screen is visible.
## Monarch FAQ
The options table can contain the following fields:
**Q**: Why am I getting `ERROR GAMEOBJECT: The collection 'default' could not be created since there is already a socket with the same name`?
* ```clear``` (boolean) - If the `clear` flag is set Monarch will search the stack for the screen that is to be shown. If the screen already exists in the stack and the clear flag is set Monarch will remove all screens between the current top and the screen in question.
* ```reload``` (boolean) - If the `reload` flag is set Monarch will reload the collection proxy if it's already loaded (this can happen if the previous screen was a popup).
* ```no_stack``` (boolean) - If the `no_stack` flag is set Monarch will load the screen without adding it to the screen stack.
* ```sequential``` (boolean) - If the `sequential` flag is set Monarch will start loading the screen only after the previous screen finished transitioning out.
* ```pop``` (number) - If `pop` is set to a number, Monarch will pop that number of screens from the stack before adding the new one.
### monarch.replace(screen_id, [options], [data], [callback])
Replace the top of the stack with a new screen. Equivalent to calling `monarch.show()` with `pop = 1`. It takes the same parameters as `monarch.show()`.
### monarch.hide(screen_id, [callback])
Hide a screen that has been shown using the `no_stack` option. If used on a screen that was shown without the `no_stack` option it will only hide it if the screen is on top of the stack and the behavior will be exactly like if `monarch.back()` had been called. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to hide.
* ```callback``` (function) - Optional function to call when the screen has been hidden.
**RETURN**
* ```success``` (boolean) - True if the process of hiding the screen was started successfully.
### monarch.back([data], [callback])
Go back to a previous Monarch screen. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* ```data``` (table) - Optional data to associate with the screen you are going back to. Retrieve using ```monarch.data()```.
* ```callback``` (function) - Optional function to call when the previous screen is visible.
### monarch.preload(screen_id, [callback])
Preload a Monarch screen. This will load but not enable the screen. This is useful for content heavy screens that you wish to be able to show without having to wait for it load. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to preload.
* ```callback``` (function) - Optional function to call when the screen is preloaded.
### monarch.is_preloading(screen_id)
Check if a Monarch screen is preloading (via monarch.preload() or the Preload screen setting).
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to check
**RETURN**
* ```preloading``` (boolean) - True if the screen is preloading.
### monarch.when_preloaded(screen_id, callback)
Invoke a callback when a screen has been preloaded.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to check
* ```callback``` (function) - Function to call when the screen has been preloaded.
### monarch.unload(screen_id, [callback])
Unload a preloaded Monarch screen. A preloaded screen will automatically get unloaded when hidden, but this function can be useful if a screen has been preloaded and it needs to be unloaded again without actually hiding it. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to unload.
* ```callback``` (function) - Optional function to call when the screen is unloaded.
### monarch.top([offset])
Get the id of the screen at the top of the stack.
**PARAMETERS**
* ```offset``` (number) - Optional offset from the top of the stack, ie -1 to get the previous screen
**RETURN**
* ```screen_id``` (string|hash) - Id of the requested screen
### monarch.bottom([offset])
Get the id of the screen at the bottom of the stack.
**PARAMETERS**
* ```offset``` (number) - Optional offset from the bottom of the stack
**RETURN**
* ```screen_id``` (string|hash) - Id of the requested screen
### monarch.data(screen_id)
Get the data associated with a screen (from a call to ```monarch.show()``` or ```monarch.back()```).
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to get data for
**RETURN**
* ```data``` (table) - Data associated with the screen.
### monarch.screen_exists(screen_id)
Check if a Monarch screen exists.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to get data for
**RETURN**
* ```exists``` (boolean) - True if the screen exists.
### monarch.is_busy()
Check if Monarch is busy showing and/or hiding a screen.
**RETURN**
* ```busy``` (boolean) - True if busy hiding and/or showing a screen.
### monarch.is_top(screen_id)
Check if a Monarch screen is at the top of the view stack.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to check
**RETURN**
* ```exists``` (boolean) - True if the screen is at the top of the stack.
### monarch.is_visible(screen_id)
Check if a Monarch screen is visible.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to check
**RETURN**
* ```exists``` (boolean) - True if the screen is visible.
### 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.post(screen_id, message_id, [message])
Post a message to a visible screen. If the screen is created through a collection proxy it must have specified a receiver url. If the screen is created through a collection factory the function will post the message to all game objects within the collection.
**PARAMETERS**
* ```screen_id``` (string|hash) - Id of the screen to post message to
* ```message_id``` (string|hash) - Id of the message to send
* ```message``` (table|nil) - Optional message data to send
**RETURN**
* ```result``` (boolean) - True if the message was sent
* ```error``` (string|nil) - Error message if unable to send message
### monarch.debug()
Enable verbose logging of the internals of Monarch.
### monarch.SCREEN_TRANSITION_IN_STARTED
Message sent to listeners added using `monarch.add_listener()` 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 added using `monarch.add_listener()` 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 added using `monarch.add_listener()` 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 added using `monarch.add_listener()` 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)
**A**: Each collection that you use must be given a unique id. In this case you have more than one collection loaded with the id `default`. Select the root of each collection in the Outline panel and change the Name field in the properties panel from the default value of `default`.

225
README_API.md Normal file
View File

@@ -0,0 +1,225 @@
# Monarch API
## monarch.show(screen_id, [options], [data], [callback])
Show a Monarch screen. Note that the screen must be registered before it can be shown. The `init()` function of the screen script (either `screen_proxy.script` or `screen_factory.script`) takes care of registration. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to show.
* `options` (table) - Options when showing the new screen (see below).
* `data` (table) - Optional data to associate with the screen. Retrieve using `monarch.data()`.
* `callback` (function) - Optional function to call when the new screen is visible.
The options table can contain the following fields:
* `clear` (boolean) - If the `clear` flag is set Monarch will search the stack for the screen that is to be shown. If the screen already exists in the stack and the clear flag is set Monarch will remove all screens between the current top and the screen in question.
* `reload` (boolean) - If the `reload` flag is set Monarch will reload the collection proxy if it's already loaded (this can happen if the previous screen was a popup).
* `no_stack` (boolean) - If the `no_stack` flag is set Monarch will load the screen without adding it to the screen stack.
* `sequential` (boolean) - If the `sequential` flag is set Monarch will start loading the screen only after the previous screen finished transitioning out.
* `pop` (number) - If `pop` is set to a number, Monarch will pop that number of screens from the stack before adding the new one.
## monarch.replace(screen_id, [options], [data], [callback])
Replace the top of the stack with a new screen. Equivalent to calling `monarch.show()` with `pop = 1`. It takes the same parameters as `monarch.show()`.
## monarch.hide(screen_id, [callback])
Hide a screen that has been shown using the `no_stack` option. If used on a screen that was shown without the `no_stack` option it will only hide it if the screen is on top of the stack and the behavior will be exactly like if `monarch.back()` had been called. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to hide.
* `callback` (function) - Optional function to call when the screen has been hidden.
**RETURN**
* `success` (boolean) - True if the process of hiding the screen was started successfully.
## monarch.clear([callback])
Clear the stack of screens completely. Any visible screen will be hidden by navigating back out from them. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* `callback` (function) - Optional function to call when the stack has been cleared.
## monarch.back([data], [callback])
Go back to a previous Monarch screen. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* `data` (table) - Optional data to associate with the screen you are going back to. Retrieve using `monarch.data()`.
* `callback` (function) - Optional function to call when the previous screen is visible.
## monarch.preload(screen_id, [options], [callback])
Preload a Monarch screen. This will load but not enable the screen. This is useful for content heavy screens that you wish to be able to show without having to wait for it load. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to preload.
* `options` (table)
* `callback` (function) - Optional function to call when the screen is preloaded.
The options table can contain the following fields:
* `keep_loaded` (boolean) - If the `keep_loaded` flag is set Monarch will keep the screen preloaded even after a `hide()` or `back()` navigation event that normally would unload the screen.
## monarch.is_preloading(screen_id)
Check if a Monarch screen is preloading (via monarch.preload() or the Preload screen setting).
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to check
**RETURN**
* `preloading` (boolean) - True if the screen is preloading.
## monarch.when_preloaded(screen_id, callback)
Invoke a callback when a screen has been preloaded.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to check
* `callback` (function) - Function to call when the screen has been preloaded.
## monarch.unload(screen_id, [callback])
Unload a preloaded Monarch screen. A preloaded screen will automatically get unloaded when hidden, but this function can be useful if a screen has been preloaded and it needs to be unloaded again without actually hiding it. This operation will be added to the queue if Monarch is busy.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to unload.
* `callback` (function) - Optional function to call when the screen is unloaded.
## monarch.top([offset])
Get the id of the screen at the top of the stack.
**PARAMETERS**
* `offset` (number) - Optional offset from the top of the stack, ie -1 to get the previous screen
**RETURN**
* `screen_id` (string|hash) - Id of the requested screen
## monarch.bottom([offset])
Get the id of the screen at the bottom of the stack.
**PARAMETERS**
* `offset` (number) - Optional offset from the bottom of the stack
**RETURN**
* `screen_id` (string|hash) - Id of the requested screen
## monarch.data(screen_id)
Get the data associated with a screen (from a call to `monarch.show()` or `monarch.back()`).
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to get data for
**RETURN**
* `data` (table) - Data associated with the screen.
## monarch.screen_exists(screen_id)
Check if a Monarch screen exists.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to get data for
**RETURN**
* `exists` (boolean) - True if the screen exists.
## monarch.is_busy()
Check if Monarch is busy showing and/or hiding a screen.
**RETURN**
* `busy` (boolean) - True if busy hiding and/or showing a screen.
## monarch.is_top(screen_id)
Check if a Monarch screen is at the top of the view stack.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to check
**RETURN**
* `exists` (boolean) - True if the screen is at the top of the stack.
## monarch.is_visible(screen_id)
Check if a Monarch screen is visible.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to check
**RETURN**
* `exists` (boolean) - True if the screen is visible.
## monarch.set_timestep_below_popup(screen_id, timestep)
Set the timestep to apply for a screen when below a popup.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to change timestep setting for
* `timestep` (number) - Timestep to apply
## 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.post(screen_id, message_id, [message])
Post a message to a visible screen. If the screen is created through a collection proxy it must have specified a receiver url. If the screen is created through a collection factory the function will post the message to all game objects within the collection.
**PARAMETERS**
* `screen_id` (string|hash) - Id of the screen to post message to
* `message_id` (string|hash) - Id of the message to send
* `message` (table|nil) - Optional message data to send
**RETURN**
* `result` (boolean) - True if the message was sent
* `error` (string|nil) - Error message if unable to send message
## monarch.debug()
Enable verbose logging of the internals of Monarch.
## monarch.SCREEN_TRANSITION_IN_STARTED
Message sent to listeners added using `monarch.add_listener()` 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 added using `monarch.add_listener()` 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 added using `monarch.add_listener()` 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 added using `monarch.add_listener()` 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)

134
README_TRANSITIONS.md Normal file
View File

@@ -0,0 +1,134 @@
# Transitions
You can add optional transitions when navigating between screens. The default behavior is that screen navigation is instant but if you have defined a transition for a screen Monarch will wait until the transition is completed before proceeding. The `Transition Url` (proxy) or `Transition Id` (collectionfactory) property described above should be the URL/Id to a script with an `on_message` handlers for the following messages:
* `transition_show_in` (constant defined as `monarch.TRANSITION.SHOW_IN`)
* `transition_show_out` (constant defined as `monarch.TRANSITION.SHOW_OUT`)
* `transition_back_in` (constant defined as `monarch.TRANSITION.BACK_IN`)
* `transition_back_out` (constant defined as `monarch.TRANSITION.BACK_OUT`)
When a transition is completed it is up to the developer to send a `transition_done` (constant `monarch.TRANSITION.DONE`) message back to the sender to indicate that the transition is completed and that Monarch can continue the navigation sequence.
## Predefined transitions
Monarch comes with a system for setting up transitions easily in a gui_script using the `monarch.transitions.gui` module. Example:
```lua
local transitions = require "monarch.transitions.gui"
local monarch = require "monarch.monarch"
function init(self)
-- create transitions for the node 'root'
-- the node will slide in/out from left and right with
-- a specific easing, duration and delay
self.transition = transitions.create(gui.get_node("root"))
.show_in(transitions.slide_in_right, gui.EASING_OUTQUAD, 0.6, 0)
.show_out(transitions.slide_out_left, gui.EASING_INQUAD, 0.6, 0)
.back_in(transitions.slide_in_left, gui.EASING_OUTQUAD, 0.6, 0)
.back_out(transitions.slide_out_right, gui.EASING_INQUAD, 0.6, 0)
end
function on_message(self, message_id, message, sender)
self.transition.handle(message_id, message, sender)
-- you can also check when a transition has completed:
if message_id == monarch.TRANSITION.DONE and message.transition == monarch.TRANSITION.SHOW_IN then
print("Show in done!")
end
end
```
It is also possible to assign transitions to multiple nodes:
```lua
function init(self)
self.transition = transitions.create() -- note that no node is passed to transition.create()!
.show_in(gui.get_node("node1"), transitions.slide_in_right, gui.EASING_OUTQUAD, 0.6, 0)
.show_in(gui.get_node("node2"), transitions.slide_in_right, gui.EASING_OUTQUAD, 0.6, 0)
end
```
The predefined transitions provided by `monarch.transitions.gui` are:
* `slide_in_right`
* `slide_in_left`
* `slide_in_top`
* `slide_in_bottom`
* `slide_out_right`
* `slide_out_left`
* `slide_out_top`
* `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:
* `transitions.in_right_out_left(node, duration, [delay], [easing])`
* `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.
* `duration` (number) - Transition duration in seconds.
* `delay` (number) - Transition delay in seconds.
* `easing` (table) - Easing table, created from a function provided by `monarch.transitions.easings`
**RETURN**
* `instance` - The created transition instance
## Custom transitions
You can create and use your own transition as long as the provided transition function has the following function signature:
custom_transition(node, to, easing, duration, delay, cb)
**PARAMETERS**
* `node` (node) - Gui node to animate.
* `to` (vector3) - Target position.
* `easing` (number) - One of gui.EASING_* constants.
* `duration` (number) - Transition duration in seconds.
* `delay` (number) - Transition delay in seconds.
* `cb` (function) - This function must be called when the transition is completed.
## Dynamic orientation and resized windows
When using dynamic screen orientation together with gui layouts or using transitions on a platform where the window can be resized it's important to make sure that the created transitions adapt to the change in orientation or window size. The transition system takes care of layout changes automatically, but when it comes to changes in window size you need to notify the transition manually:
```lua
local transitions = require "monarch.transitions.gui"
function init(self)
self.transition = transitions.create(gui.get_node("root"))
end
function on_message(self, message_id, message, sender)
if message_id == hash("my_resize_message") then
self.transition.window_resized(message.width, message.height)
end
end
```
## Screen stack info and transitions
The transition message sent to the Transition Url specified in the screen configuration contains additional information about the transition. For the `transition_show_in` and `transition_back_out` messages the message contains the previous screen id:
```lua
function on_message(self, message_id, message, sender)
if message_id == hash("transition_show_in") or message_id == hash("transition_back_out") then
print(message.previous_screen)
end
end
```
For the `transition_show_out` and `transition_back_in` messages the message contains the next screen id:
```lua
function on_message(self, message_id, message, sender)
if message_id == hash("transition_show_out") or message_id == hash("transition_back_in") then
print(message.next_screen)
end
end
```
This information can be used to create dynamic transitions where the direction of the transition depends on the previous/next screen.

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "go"
data: "components {\n"
" id: \"about\"\n"
" component: \"/example/about.gui\"\n"
" component: \"/example/advanced/about.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"

View File

@@ -1,4 +1,4 @@
script: "/example/about.gui_script"
script: "/example/advanced/about.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -1,4 +1,4 @@
name: "example"
name: "advanced"
scale_along_z: 0
embedded_instances {
id: "menu"
@@ -35,7 +35,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionfactory\"\n"
" type: \"collectionfactory\"\n"
" data: \"prototype: \\\"/example/menu.collection\\\"\\n"
" data: \"prototype: \\\"/example/advanced/menu.collection\\\"\\n"
"load_dynamically: true\\n"
"\"\n"
" position {\n"
@@ -72,7 +72,7 @@ embedded_instances {
id: "main"
data: "components {\n"
" id: \"main\"\n"
" component: \"/example/main.script\"\n"
" component: \"/example/advanced/advanced.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -87,7 +87,7 @@ embedded_instances {
"}\n"
"components {\n"
" id: \"gui\"\n"
" component: \"/example/debug.gui\"\n"
" component: \"/example/advanced/debug.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -148,7 +148,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/pregame.collection\\\"\\n"
" data: \"collection: \\\"/example/advanced/pregame.collection\\\"\\n"
"exclude: false\\n"
"\"\n"
" position {\n"
@@ -211,7 +211,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/game.collection\\\"\\n"
" data: \"collection: \\\"/example/advanced/game.collection\\\"\\n"
"exclude: false\\n"
"\"\n"
" position {\n"
@@ -294,7 +294,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/about.collection\\\"\\n"
" data: \"collection: \\\"/example/advanced/about.collection\\\"\\n"
"exclude: false\\n"
"\"\n"
" position {\n"
@@ -372,7 +372,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/confirm.collection\\\"\\n"
" data: \"collection: \\\"/example/advanced/confirm.collection\\\"\\n"
"exclude: false\\n"
"\"\n"
" position {\n"
@@ -430,7 +430,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionfactory\"\n"
" type: \"collectionfactory\"\n"
" data: \"prototype: \\\"/example/background.collection\\\"\\n"
" data: \"prototype: \\\"/example/advanced/background.collection\\\"\\n"
"load_dynamically: false\\n"
"\"\n"
" position {\n"

View File

@@ -11,4 +11,4 @@ function on_message(self, message_id, message, sender)
monarch.show(hash("background"), { no_stack = true })
monarch.show(hash("menu"))
end
end
end

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "go"
data: "components {\n"
" id: \"confirm\"\n"
" component: \"/example/confirm.gui\"\n"
" component: \"/example/advanced/confirm.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"

View File

@@ -1,4 +1,4 @@
script: "/example/confirm.gui_script"
script: "/example/advanced/confirm.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -1,4 +1,4 @@
script: "/example/debug.gui_script"
script: "/example/advanced/debug.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "go"
data: "components {\n"
" id: \"game\"\n"
" component: \"/example/game.gui\"\n"
" component: \"/example/advanced/game.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"

View File

@@ -1,4 +1,4 @@
script: "/example/game.gui_script"
script: "/example/advanced/game.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "go"
data: "components {\n"
" id: \"menu\"\n"
" component: \"/example/menu.gui\"\n"
" component: \"/example/advanced/menu.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
@@ -80,7 +80,7 @@ embedded_instances {
"embedded_components {\n"
" id: \"collectionfactory\"\n"
" type: \"collectionfactory\"\n"
" data: \"prototype: \\\"/example/popup.collection\\\"\\n"
" data: \"prototype: \\\"/example/advanced/popup.collection\\\"\\n"
"load_dynamically: false\\n"
"\"\n"
" position {\n"

View File

@@ -1,4 +1,4 @@
script: "/example/menu.gui_script"
script: "/example/advanced/menu.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "go"
data: "components {\n"
" id: \"popup\"\n"
" component: \"/example/popup.gui\"\n"
" component: \"/example/advanced/popup.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"

View File

@@ -1,4 +1,4 @@
script: "/example/popup.gui_script"
script: "/example/advanced/popup.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -4,7 +4,7 @@ embedded_instances {
id: "go"
data: "components {\n"
" id: \"pregame\"\n"
" component: \"/example/pregame.gui\"\n"
" component: \"/example/advanced/pregame.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"

View File

@@ -1,4 +1,4 @@
script: "/example/pregame.gui_script"
script: "/example/advanced/pregame.gui_script"
fonts {
name: "example"
font: "/assets/example.font"

View File

@@ -0,0 +1,153 @@
name: "basic"
scale_along_z: 0
embedded_instances {
id: "screen1"
data: "components {\n"
" id: \"screen_proxy\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
" properties {\n"
" id: \"screen_id\"\n"
" value: \"screen1\"\n"
" type: PROPERTY_TYPE_HASH\n"
" }\n"
"}\n"
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/basic/screen1.collection\\\"\\n"
"exclude: false\\n"
"\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
"}\n"
""
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: "basic"
data: "components {\n"
" id: \"basic\"\n"
" component: \"/example/basic/basic.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
"}\n"
""
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: "screen2"
data: "components {\n"
" id: \"screen_proxy\"\n"
" component: \"/monarch/screen_proxy.script\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
" properties {\n"
" id: \"screen_id\"\n"
" value: \"screen2\"\n"
" type: PROPERTY_TYPE_HASH\n"
" }\n"
"}\n"
"embedded_components {\n"
" id: \"collectionproxy\"\n"
" type: \"collectionproxy\"\n"
" data: \"collection: \\\"/example/basic/screen2.collection\\\"\\n"
"exclude: false\\n"
"\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
"}\n"
""
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
}
}

View File

@@ -0,0 +1,12 @@
local monarch = require "monarch.monarch"
function init(self)
msg.post(".", "acquire_input_focus")
msg.post("#", "show_screen1")
end
function on_message(self, message_id, message, sender)
if message_id == hash("show_screen1") then
monarch.show("screen1")
end
end

View File

@@ -0,0 +1,37 @@
name: "screen1"
scale_along_z: 0
embedded_instances {
id: "go"
data: "components {\n"
" id: \"screen1\"\n"
" component: \"/example/basic/screen1.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
"}\n"
""
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
}
}

131
example/basic/screen1.gui Normal file
View File

@@ -0,0 +1,131 @@
script: "/example/basic/screen1.gui_script"
fonts {
name: "example"
font: "/assets/example.font"
}
background_color {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
nodes {
position {
x: 320.0
y: 568.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: 200.0
y: 100.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: "showscreen2"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
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_AUTO
}
nodes {
position {
x: 0.0
y: 0.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: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "SHOW SCREEN 2"
font: "example"
id: "text"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "showscreen2"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
template_node_child: false
text_leading: 1.0
text_tracking: 0.0
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -0,0 +1,13 @@
local monarch = require "monarch.monarch"
function init(self)
msg.post(".", "acquire_input_focus")
end
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
if gui.pick_node(gui.get_node("showscreen2"), action.x, action.y) then
monarch.show("screen2")
end
end
end

View File

@@ -0,0 +1,37 @@
name: "screen2"
scale_along_z: 0
embedded_instances {
id: "go"
data: "components {\n"
" id: \"screen2\"\n"
" component: \"/example/basic/screen2.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
"}\n"
""
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
}
}

131
example/basic/screen2.gui Normal file
View File

@@ -0,0 +1,131 @@
script: "/example/basic/screen2.gui_script"
fonts {
name: "example"
font: "/assets/example.font"
}
background_color {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
nodes {
position {
x: 320.0
y: 568.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: 200.0
y: 100.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: "backbutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
layer: ""
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_AUTO
}
nodes {
position {
x: 0.0
y: 0.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: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "BACK"
font: "example"
id: "text"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "backbutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
template_node_child: false
text_leading: 1.0
text_tracking: 0.0
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View File

@@ -0,0 +1,13 @@
local monarch = require "monarch.monarch"
function init(self)
msg.post(".", "acquire_input_focus")
end
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
if gui.pick_node(gui.get_node("backbutton"), action.x, action.y) then
monarch.back()
end
end
end

View File

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

View File

@@ -68,7 +68,7 @@ function M.get_commands()
},
active = function(opts)
local path = editor.get(opts.selection, "path")
return ends_with(path, ".gui") or ends_with(path, ".collection") or ends_with(path, ".gui_script")
return ends_with(path, ".gui")
end,
run = function(opts)
create_files(opts.selection)

View File

@@ -1,4 +1,5 @@
local callback_tracker = require "monarch.utils.callback_tracker"
local async = require "monarch.utils.async"
local M = {}
@@ -33,6 +34,8 @@ M.SCREEN_TRANSITION_OUT_STARTED = hash("monarch_screen_transition_out_started")
M.SCREEN_TRANSITION_OUT_FINISHED = hash("monarch_screen_transition_out_finished")
M.SCREEN_TRANSITION_FAILED = hash("monarch_screen_transition_failed")
local WAIT_FOR_TRANSITION = true
local DO_NOT_WAIT_FOR_TRANSITION = false
-- all registered screens
local screens = {}
@@ -47,7 +50,6 @@ local transition_listeners = {}
-- monarch is considered busy while there are active transitions
local active_transition_count = 0
local function log(...) end
function M.debug()
@@ -93,8 +95,8 @@ end
local queue = {}
local function queue_error(message)
log(message)
log("queue() error - clearing queue")
print(message)
while next(queue) do
table.remove(queue)
end
@@ -184,7 +186,18 @@ function M.is_visible(id)
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)))
return screens[id].loaded
return screens[id].visible
end
--- Check if a screen is a popup
-- @param id Screen id
-- @return true if the screen is a popup
function M.is_popup(id)
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)))
return screens[id].popup
end
@@ -285,6 +298,12 @@ function M.unregister(id)
log("unregister()", id)
local screen = screens[id]
screens[id] = nil
-- remove screen from stack
for i = #stack, 1, -1 do
if stack[i].id == id then
table.remove(stack, i)
end
end
end
local function acquire_input(screen)
@@ -326,7 +345,7 @@ end
local function change_context(screen)
log("change_context()", screen.id)
screen.wait_for = CONTEXT
msg.post(screen.script, CONTEXT)
msg.post(screen.script, CONTEXT, { id = screen.id })
coroutine.yield()
screen.wait_for = nil
end
@@ -440,12 +459,14 @@ local function load(screen)
return true
end
local function transition(screen, message_id, message)
local function transition(screen, message_id, message, wait)
log("transition()", screen.id)
if screen.transition_url then
screen.wait_for = M.TRANSITION.DONE
msg.post(screen.transition_url, message_id, message)
coroutine.yield()
if wait then
coroutine.yield()
end
screen.wait_for = nil
else
log("transition() no transition url - ignoring")
@@ -470,6 +491,7 @@ local function focus_lost(screen, next_screen)
-- the focus_url
-- we add a delay to ensure the message queue has time to be processed
cowait(0)
cowait(0)
else
log("focus_lost() no focus url - ignoring")
end
@@ -527,8 +549,21 @@ local function enable(screen, previous_screen)
end)
end
local function show_out(screen, next_screen, cb)
local function show_out(screen, next_screen, wait_for_transition, cb)
log("show_out()", screen.id)
assert(wait_for_transition ~= nil)
-- make sure the screen is loaded. scenario:
-- show A - stack [A]
-- - show_in for A
-- show B with no_stack = true - stack [A]
-- - show_in for B and show_out for A
-- show C
-- - show_in for C and show_out for A again!
if not screen.loaded then
log("show_out() screen was not loaded")
cb()
return
end
run_coroutine(screen, cb, function()
active_transition_count = active_transition_count + 1
notify_transition_listeners(M.SCREEN_TRANSITION_OUT_STARTED, { screen = screen.id, next_screen = next_screen.id })
@@ -541,7 +576,8 @@ local function show_out(screen, next_screen, cb)
local next_is_popup = next_screen and next_screen.popup
local current_is_popup = screen.popup
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 })
transition(screen, M.TRANSITION.SHOW_OUT, { next_screen = next_screen.id }, wait_for_transition)
screen.visible = false
unload(screen)
elseif next_is_popup then
change_timestep(screen)
@@ -551,8 +587,9 @@ local function show_out(screen, next_screen, cb)
end)
end
local function show_in(screen, previous_screen, reload, add_to_stack, cb)
log("show_in()", screen.id)
local function show_in(screen, previous_screen, reload, add_to_stack, wait_for_transition, cb)
log("show_in()", screen.id, wait_for_transition)
assert(wait_for_transition ~= nil)
run_coroutine(screen, cb, function()
active_transition_count = active_transition_count + 1
notify_transition_listeners(M.SCREEN_TRANSITION_IN_STARTED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
@@ -561,18 +598,22 @@ local function show_in(screen, previous_screen, reload, add_to_stack, cb)
log("show_in() reloading", screen.id)
unload(screen, reload)
end
if add_to_stack then
stack[#stack + 1] = screen
end
local ok, err = load(screen)
if not ok then
log("show_in()", err)
if add_to_stack then
stack[#stack] = nil
end
active_transition_count = active_transition_count - 1
notify_transition_listeners(M.SCREEN_TRANSITION_FAILED, { screen = screen.id })
return
end
if add_to_stack then
stack[#stack + 1] = screen
end
reset_timestep(screen)
transition(screen, M.TRANSITION.SHOW_IN, { previous_screen = previous_screen and previous_screen.id })
transition(screen, M.TRANSITION.SHOW_IN, { previous_screen = previous_screen and previous_screen.id }, wait_for_transition)
screen.visible = true
acquire_input(screen)
focus_gained(screen, previous_screen)
active_transition_count = active_transition_count - 1
@@ -580,8 +621,9 @@ local function show_in(screen, previous_screen, reload, add_to_stack, cb)
end)
end
local function back_in(screen, previous_screen, cb)
local function back_in(screen, previous_screen, wait_for_transition, cb)
log("back_in()", screen.id)
assert(wait_for_transition ~= nil)
run_coroutine(screen, cb, function()
active_transition_count = active_transition_count + 1
notify_transition_listeners(M.SCREEN_TRANSITION_IN_STARTED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
@@ -595,8 +637,9 @@ local function back_in(screen, previous_screen, cb)
end
reset_timestep(screen)
if previous_screen and not previous_screen.popup then
transition(screen, M.TRANSITION.BACK_IN, { previous_screen = previous_screen.id })
transition(screen, M.TRANSITION.BACK_IN, { previous_screen = previous_screen.id }, wait_for_transition)
end
screen.visible = true
acquire_input(screen)
focus_gained(screen, previous_screen)
active_transition_count = active_transition_count - 1
@@ -604,18 +647,20 @@ local function back_in(screen, previous_screen, cb)
end)
end
local function back_out(screen, next_screen, cb)
local function back_out(screen, next_screen, wait_for_transition, cb)
log("back_out()", screen.id)
assert(wait_for_transition ~= nil)
run_coroutine(screen, cb, function()
notify_transition_listeners(M.SCREEN_TRANSITION_OUT_STARTED, { screen = screen.id, next_screen = next_screen and next_screen.id })
active_transition_count = active_transition_count + 1
change_context(screen)
release_input(screen, next_screen)
focus_lost(screen, next_screen)
transition(screen, M.TRANSITION.BACK_OUT, { next_screen = next_screen and next_screen.id }, wait_for_transition)
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 })
screen.visible = false
unload(screen)
active_transition_count = active_transition_count - 1
notify_transition_listeners(M.SCREEN_TRANSITION_OUT_FINISHED, { screen = screen.id, next_screen = next_screen and next_screen.id })
@@ -668,9 +713,7 @@ function M.show(id, options, data, cb)
id = tohash(id)
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
log("show() queuing action", id)
queue_action(function(action_done, action_error)
log("show()", id)
local screen = screens[id]
if not screen then
action_error(("show() there is no longer a screen with id %s"):format(tostring(id)))
@@ -686,54 +729,57 @@ function M.show(id, options, data, cb)
local top = stack[#stack]
-- a screen can ignore the stack by setting the no_stack to true
local add_to_stack = not options or not options.no_stack
if add_to_stack then
if add_to_stack and top then
-- manipulate the current top
-- close popup(s) if needed
-- transition out
if top then
-- keep top popup visible if new screen can be shown on top of a popup
if top.popup and screen.popup_on_popup then
disable(top, screen)
else
-- close all popups, one by one
while top.popup do
stack[#stack] = nil
show_out(top, screen, callbacks.track())
callbacks.yield_until_done()
top = stack[#stack]
end
-- unload and transition out from top
-- wait until we are done if showing the same screen as is already visible
local same_screen = top and top.id == screen.id
show_out(top, screen, callbacks.track())
if same_screen or (options and options.sequential) then
callbacks.yield_until_done()
end
local pop = options and options.pop or 0
local is_not_popup = not screen.popup
local pop_all_popups = is_not_popup -- pop all popups when transitioning screens
-- keep top popup visible if new screen can be shown on top of a popup
if top.popup and screen.popup and screen.popup_on_popup then
disable(top, screen)
else
pop_all_popups = true
end
-- close popups, one by one, either all of them or the number specified by options.pop
while top and top.popup do
if not pop_all_popups then
if pop <= 0 then break end
pop = pop - 1
end
stack[#stack] = nil
async(function(await, resume)
await(show_out, top, screen, WAIT_FOR_TRANSITION, resume)
end)
top = stack[#stack]
end
-- if the screen we want to show is in the stack
-- already and the clear flag is set then we need
-- to remove every screen on the stack up until and
-- including the screen itself
if options and options.clear then
log("show() clearing")
while M.in_stack(id) do
table.remove(stack)
end
end
-- pop screens off the stack
if is_not_popup then
for i = 1, pop do
local stack_top = #stack
if stack_top < 1 then break end
stack[stack_top] = nil
end
end
end
-- if the screen we want to show is in the stack
-- already and the clear flag is set then we need
-- to remove every screen on the stack up until and
-- including the screen itself
if options and options.clear then
log("show() clearing")
while M.in_stack(id) do
table.remove(stack)
end
end
if options and options.pop then
for i = 1, options.pop do
local stack_top = #stack
if stack_top < 1 then break end
stack[stack_top] = nil
end
end
-- show screen, wait until preloaded if it is already preloading
-- this can typpically happen if you do a show() on app start for a
-- wait until preloaded if it is already preloading
-- this can typically happen if you do a show() on app start for a
-- screen that has Preload set to true
if M.is_preloading(id) then
M.when_preloaded(id, function()
@@ -741,7 +787,26 @@ function M.show(id, options, data, cb)
end)
coroutine.yield()
end
show_in(screen, top, options and options.reload, add_to_stack, callbacks.track())
-- showing and hiding the same screen?
local same_screen = top and top.id == screen.id
if same_screen or (options and options.sequential) then
if top then
async(function(await, resume)
await(show_out, top, screen, WAIT_FOR_TRANSITION, resume)
end)
end
show_in(screen, top, options and options.reload, add_to_stack, WAIT_FOR_TRANSITION, callbacks.track())
else
-- show screen
local cb = callbacks.track()
show_in(screen, top, options and options.reload, add_to_stack, DO_NOT_WAIT_FOR_TRANSITION, function()
if add_to_stack and top and not top.popup then
show_out(top, screen, WAIT_FOR_TRANSITION, callbacks.track())
end
cb()
end)
end
callbacks.when_done(function()
pcallfn(cb)
@@ -770,7 +835,7 @@ end
-- Hide a screen. The screen must either be at the top of the stack or
-- visible but not added to the stack (through the no_stack option)
-- @param id (string|hash) - Id of the screen to show
-- @param id (string|hash) - Id of the screen to .hide
-- @param cb (function) - Optional callback to invoke when the screen is hidden
-- @return true if successfully hiding, false if busy or for some other reason unable to hide the screen
function M.hide(id, cb)
@@ -795,7 +860,7 @@ function M.hide(id, cb)
action_error(("hide() there is no longer a screen with id %s"):format(tostring(id)))
return
end
back_out(screen, nil, callbacks.track())
back_out(screen, nil, WAIT_FOR_TRANSITION, callbacks.track())
end
callbacks.when_done(function()
pcallfn(cb)
@@ -807,6 +872,34 @@ function M.hide(id, cb)
end
-- Clear stack completely. Any visible screens will be hidden by navigating back out
-- from them.
-- @param cb (function) - Optional callback to invoke when the stack has been cleared
function M.clear(cb)
log("clear() queuing action")
queue_action(function(action_done, action_error)
async(function(await, resume)
local top = stack[#stack]
while top and top.visible do
stack[#stack] = nil
await(back_out, top, stack[#stack - 1], WAIT_FOR_TRANSITION, resume)
top = stack[#stack]
end
while stack[#stack] do
table.remove(stack)
end
pcallfn(cb)
pcallfn(action_done)
end)
end)
end
-- Go back to the previous screen in the stack.
-- @param data (*) - Optional data to set for the previous screen
-- @param cb (function) - Optional callback to invoke when the previous screen is visible again
@@ -815,6 +908,7 @@ function M.back(data, cb)
queue_action(function(action_done)
local callbacks = callback_tracker()
local back_cb = callbacks.track()
local screen = table.remove(stack)
if screen then
log("back()", screen.id)
@@ -822,19 +916,33 @@ function M.back(data, cb)
-- if we go back to the same screen we need to first hide it
-- and wait until it is hidden before we show it again
if top and screen.id == top.id then
back_out(screen, top, function()
back_out(screen, top, WAIT_FOR_TRANSITION, function()
if data then
top.data = data
end
back_in(top, screen, callbacks.track())
back_in(top, screen, WAIT_FOR_TRANSITION, back_cb)
end)
else
back_out(screen, top, callbacks.track())
if top then
if data then
top.data = data
end
back_in(top, screen, callbacks.track())
-- if the screen we are backing out from is a popup and the screen we go
-- back to is not a popup we need to let the popup completely hide before
-- we start working on the screen we go back to
-- we do this to ensure that we do not reset the times step of the screen
-- we go back to until it is no longer obscured by the popup
if screen.popup and not top.popup then
back_out(screen, top, WAIT_FOR_TRANSITION, function()
back_in(top, screen, WAIT_FOR_TRANSITION, back_cb)
end)
else
back_in(top, screen, DO_NOT_WAIT_FOR_TRANSITION, function()
back_out(screen, top, WAIT_FOR_TRANSITION, back_cb)
end)
end
else
back_out(screen, top, WAIT_FOR_TRANSITION, back_cb)
end
end
end
@@ -888,12 +996,19 @@ 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 options (table)
-- @param cb (function) - Optional callback to invoke when screen is loaded
function M.preload(id, cb)
function M.preload(id, options, cb)
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)))
-- support old function signature (id, cb)
if type(options) == "function" and not cb then
cb = options
options = nil
end
log("preload() queuing action", id)
queue_action(function(action_done, action_error)
log("preload()", id)
@@ -904,6 +1019,10 @@ function M.preload(id, cb)
return
end
-- keep_loaded is an option for monarch.preload()
-- use it to get the same behavior as the auto preload checkbox
screen.auto_preload = screen.auto_preload or options and options.keep_loaded
if screen.preloaded or screen.loaded then
pcallfn(cb)
pcallfn(action_done)
@@ -998,13 +1117,9 @@ function M.post(id, message_id, message)
return false, "Unable to post message since screen has no receiver url specified"
end
else
run_coroutine(screen, nil, function()
change_context(screen)
log("post() sending message to", screen.receiver_url)
for id,instance in pairs(screen.factory_ids) do
msg.post(instance, message_id, message)
end
end)
for id,instance in pairs(screen.factory_ids) do
msg.post(instance, message_id, message)
end
end
return true
end
@@ -1068,6 +1183,19 @@ function M.bottom(offset)
return screen and screen.id
end
--- Set the timestep to apply for a screen when below a popup
-- @param id (string|hash) Id of the screen to change timestep setting for
-- @param timestep (number) Timestep to apply
function M.set_timestep_below_popup(id, timestep)
assert(id, "You must provide a screen id")
assert(timestep, "You must provide a timestep")
id = tohash(id)
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
screens[id].timestep_below_popup = timestep
end
local function url_to_key(url)
return (url.socket or hash("")) .. (url.path or hash("")) .. (url.fragment or hash(""))
end
@@ -1097,4 +1225,9 @@ function M.dump_stack()
return s
end
function M.queue_size()
return #queue
end
return M

View File

@@ -121,11 +121,15 @@ local function create()
local current_transition = nil
local function create_transition(transition_id, node, fn, easing, duration, delay)
assert(transition_id, "You must provide a valid transition id")
assert(node, "You must provide a node")
assert(fn, "You must provide a transition function")
local t = transitions[transition_id]
-- find if there's already a transition for the node in
-- question and if so update it instead of creating a new
-- transition
for _,transition in ipairs(t) do
for _,transition in ipairs(t.transitions) do
if transition.node == node then
transition.fn = fn
transition.easing = easing

47
monarch/utils/async.lua Normal file
View File

@@ -0,0 +1,47 @@
local M = {}
local NOT_STARTED = "not_started"
local YIELDED = "yielded"
local RESUMED = "resumed"
local RUNNING = "running"
function M.async(fn)
local co = coroutine.running()
local state = NOT_STARTED
local function await(fn, ...)
state = RUNNING
fn(...)
if state ~= RUNNING then
return
end
state = YIELDED
local r = { coroutine.yield() }
return unpack(r)
end
local function resume(...)
if state ~= RUNNING then
state = RUNNING
local ok, err = coroutine.resume(co, ...)
if not ok then
print(err)
print(debug.traceback())
end
end
end
if co then
return fn(await, resume)
else
co = coroutine.create(fn)
return resume(await, resume)
end
end
return setmetatable(M, {
__call = function(t, ...)
return M.async(...)
end
})

View File

@@ -6,13 +6,18 @@ function M.create()
local callback = nil
local callback_count = 0
local all_callbacks_done = false
local function is_done()
return callback_count == 0
end
local function invoke_if_done()
if all_callbacks_done then
print("Warning: The same callback will be invoked twice from the callback tracker!")
end
if callback_count == 0 and callback then
all_callbacks_done = true
local ok, err = pcall(callback)
if not ok then print(err) end
end
@@ -41,23 +46,12 @@ function M.create()
invoke_if_done()
end
function instance.yield_until_done()
local co = coroutine.running()
callback = function()
coroutine.resume(co)
end
invoke_if_done()
if not is_done() then
coroutine.yield()
end
end
return instance
end
return setmetatable(M, {
__call = function(_, ...)
return M.create(...)
return M.create()
end
})

View File

@@ -4,7 +4,10 @@ function M.seconds(amount)
local co = coroutine.running()
assert(co, "You must run this from within a coroutine")
timer.delay(amount, false, function()
coroutine.resume(co)
local ok, err = coroutine.resume(co)
if not ok then
print(err)
end
end)
coroutine.yield()
end
@@ -13,10 +16,13 @@ function M.eval(fn, timeout)
local co = coroutine.running()
assert(co, "You must run this from within a coroutine")
local start = socket.gettime()
timer.delay(0.01, true, function(self, handle, time_elapsed)
timer.delay(0.02, true, function(self, handle, time_elapsed)
if fn() or (timeout and socket.gettime() > (start + timeout)) then
timer.cancel(handle)
coroutine.resume(co)
local ok, err = coroutine.resume(co)
if not ok then
print(err)
end
end
end)
coroutine.yield()

View File

@@ -1,5 +1,5 @@
function init(self)
local monarch = require "monarch.monarch"
local data = monarch.data(hash("screen_preload"))
data.count = data.count + 1
if data then data.count = data.count + 1 end
end

View File

@@ -39,7 +39,7 @@ return function()
local screens_instances = {}
local function is_shown(screen_id)
local function is_visible(screen_id)
return monarch.is_visible(screen_id)
end
@@ -65,9 +65,6 @@ return function()
local function wait_until_visible(screen_id)
return wait_timeout(is_visible, screen_id)
end
local function wait_until_shown(screen_id)
return wait_timeout(is_shown, screen_id)
end
local function wait_until_hidden(screen_id)
return wait_timeout(is_hidden, screen_id)
end
@@ -77,6 +74,9 @@ return function()
local function wait_until_not_busy()
return wait_timeout(function() return not monarch.is_busy() end)
end
local function wait_until_stack(expected_screens)
return wait_timeout(function() return check_stack(expected_screens) end)
end
local function wait_until_loaded(screen_id)
wait_until_done(function(done)
monarch.when_preloaded(screen_id, done)
@@ -115,114 +115,113 @@ return function()
it("should be able to show screens and go back to previous screens", function()
monarch.show(SCREEN1_STR)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(SCREEN2)
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
monarch.back()
assert(wait_until_hidden(SCREEN2), "Screen2 was never hidden")
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.back()
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert_stack({ })
assert(wait_until_stack({ }))
end)
it("should be able to replace screens at the top of the stack", function()
monarch.show(SCREEN1_STR)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }), "Screen1 was never shown")
monarch.show(SCREEN2)
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
monarch.replace(SCREEN1)
assert(wait_until_hidden(SCREEN2), "Screen2 was never hidden")
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1, SCREEN1 })
assert(wait_until_stack({ SCREEN1, SCREEN1 }))
end)
it("should be able to tell if a screen is visible or not", function()
assert(not monarch.is_visible(SCREEN1))
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(monarch.is_visible(SCREEN1))
assert(wait_until_stack({ SCREEN1 }))
assert(wait_until_visible(SCREEN1))
monarch.show(SCREEN2)
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(not monarch.is_visible(SCREEN1))
assert(monarch.is_visible(SCREEN2))
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
assert(wait_until_hidden(SCREEN1))
assert(wait_until_visible(SCREEN2))
monarch.show(POPUP1)
assert(wait_until_shown(POPUP1), "Popup1 was never shown")
assert_stack({ SCREEN1, SCREEN2, POPUP1 })
assert(not monarch.is_visible(SCREEN1))
assert(monarch.is_visible(SCREEN2))
assert(monarch.is_visible(POPUP1))
assert(wait_until_stack({ SCREEN1, SCREEN2, POPUP1 }))
assert(wait_until_hidden(SCREEN1))
assert(wait_until_visible(SCREEN2))
assert(wait_until_visible(POPUP1))
end)
it("should be able to show a screen without adding it to the stack", function()
monarch.show(BACKGROUND, { no_stack = true })
assert(wait_until_shown(BACKGROUND), "Background was never shown")
assert_stack({ })
assert(wait_until_visible(BACKGROUND), "Background was never shown")
assert(wait_until_stack({ }))
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
end)
it("should be able to show a screen without adding it to the stack at any time", function()
monarch.show(SCREEN1)
assert(wait_until_not_busy())
assert(wait_until_stack({ SCREEN1 }))
monarch.show(BACKGROUND, { no_stack = true })
assert(wait_until_not_busy())
assert(wait_until_visible(BACKGROUND))
assert(wait_until_stack({ SCREEN1 }))
monarch.show(SCREEN2)
assert(wait_until_not_busy())
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
monarch.back()
assert(wait_until_not_busy())
assert(wait_until_visible(SCREEN1))
assert(wait_until_stack({ SCREEN1 }))
end)
it("should be able to hide a screen not added to the stack", function()
monarch.show(BACKGROUND, { no_stack = true })
assert(wait_until_shown(BACKGROUND), "Background was never shown")
assert(wait_until_visible(BACKGROUND), "Background was never shown")
assert_stack({ })
monarch.hide(BACKGROUND)
assert(wait_until_hidden(BACKGROUND), "Background was never hidden")
assert_stack({ })
end)
it("should be able to hide the top screen", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(SCREEN2)
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
assert(monarch.hide(SCREEN1) == false)
assert(monarch.hide(SCREEN2) == true)
assert(wait_until_hidden(SCREEN2), "Screen2 was never hidden")
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
end)
it("should be able to pass data to a screen when showing it or going back to it", function()
local data1 = { foo = "bar" }
monarch.show(SCREEN1, nil, data1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert(wait_until_visible(SCREEN1), "Screen1 was never shown")
local data2 = { boo = "car" }
monarch.show(SCREEN2, nil, data2)
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert(wait_until_visible(SCREEN2), "Screen2 was never shown")
assert(monarch.data(SCREEN1) == data1, "Expected data on screen1 doesn't match actual data")
assert(monarch.data(SCREEN2) == data2, "Expected data on screen2 doesn't match actual data")
local data_back = { going = "back" }
monarch.back(data_back)
assert(wait_until_shown(SCREEN1))
assert(wait_until_visible(SCREEN1))
assert(monarch.data(SCREEN1) == data_back, "Expected data on screen1 doesn't match actual data")
end)
@@ -230,51 +229,40 @@ return function()
it("should be able to show the same screen twice", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1, SCREEN1 })
assert(wait_until_stack({ SCREEN1, SCREEN1 }))
end)
it("should be able to clear the stack if trying to show the same screen twice", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(SCREEN2)
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
monarch.show(SCREEN1, { clear = true })
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
end)
it("should be able to show one popup on top of another if the Popup On Popup flag is set", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP1)
assert(wait_until_shown(POPUP1), "Popup1 was never shown")
assert_stack({ SCREEN1, POPUP1 })
assert(wait_until_stack({ SCREEN1, POPUP1 }))
monarch.show(POPUP2)
assert(wait_until_shown(POPUP2), "Popup2 was never shown")
assert_stack({ SCREEN1, POPUP1, POPUP2 })
assert(wait_until_stack({ SCREEN1, POPUP1, POPUP2 }))
end)
it("should prevent further operations while hiding/showing a screen", function()
it("should be able to queue multiple display commands", function()
monarch.show(SCREEN1)
monarch.show(SCREEN2)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
assert(monarch.back())
assert(monarch.back())
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert(wait_until_hidden(SCREEN2), "Screen2 was never hidden")
assert(wait_until_stack({}), "Stack never became empty")
end)
@@ -292,45 +280,63 @@ return function()
it("should close any open popups when showing a popup without the Popup On Popup flag", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP2)
assert(wait_until_shown(POPUP2), "Popup2 was never shown")
assert_stack({ SCREEN1, POPUP2 })
assert(wait_until_stack({ SCREEN1, POPUP2 }))
monarch.show(POPUP1)
assert(wait_until_shown(POPUP1), "Popup1 was never shown")
assert_stack({ SCREEN1, POPUP1 })
assert(wait_until_stack({ SCREEN1, POPUP1 }))
end)
it("should close any open popups when showing a non-popup", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP1)
assert(wait_until_shown(POPUP1), "Popup1 was never shown")
assert_stack({ SCREEN1, POPUP1 })
assert(wait_until_stack({ SCREEN1, POPUP1 }))
monarch.show(POPUP2)
assert(wait_until_shown(POPUP2), "Popup2 was never shown")
assert_stack({ SCREEN1, POPUP1, POPUP2 })
assert(wait_until_stack({ SCREEN1, POPUP1, POPUP2 }))
monarch.show(SCREEN2)
assert(wait_until_shown(SCREEN2), "Popup2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
end)
it("should close any open popups when replacing a non-popup", function()
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert_stack({ SCREEN1 })
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP1)
assert(wait_until_shown(POPUP1), "Popup1 was never shown")
assert_stack({ SCREEN1, POPUP1 })
assert(wait_until_stack({ SCREEN1, POPUP1 }))
monarch.show(POPUP2)
assert(wait_until_shown(POPUP2), "Popup2 was never shown")
assert_stack({ SCREEN1, POPUP1, POPUP2 })
assert(wait_until_stack({ SCREEN1, POPUP1, POPUP2 }))
monarch.replace(SCREEN2)
assert(wait_until_shown(SCREEN2), "Popup2 was never shown")
assert_stack({ SCREEN2 })
assert(wait_until_stack({ SCREEN2 }))
end)
it("should replace a popup", function()
monarch.show(SCREEN1)
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP1)
assert(wait_until_stack({ SCREEN1, POPUP1 }))
monarch.replace(POPUP2)
assert(wait_until_stack({ SCREEN1, POPUP2 }))
end)
it("should replace a pop-up two levels down", function()
monarch.show(SCREEN1)
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP1)
assert(wait_until_stack({ SCREEN1, POPUP1 }))
monarch.show(POPUP2)
assert(wait_until_stack({ SCREEN1, POPUP1, POPUP2 }))
monarch.show(POPUP2, { pop = 2 })
assert(wait_until_stack({ SCREEN1, POPUP2 }))
end)
it("shouldn't over-pop popups", function()
monarch.show(SCREEN1)
assert(wait_until_stack({ SCREEN1 }))
monarch.show(POPUP1)
assert(wait_until_stack({ SCREEN1, POPUP1 }))
monarch.show(POPUP2, { pop = 10 })
assert(wait_until_stack({ SCREEN1, POPUP2 }))
end)
it("should be able to get the id of the screen at the top and bottom of the stack", function()
@@ -340,7 +346,7 @@ return function()
assert(monarch.bottom(-1) == nil)
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert(wait_until_stack({ SCREEN1 }))
assert(monarch.top() == SCREEN1)
assert(monarch.top(0) == SCREEN1)
assert(monarch.top(1) == nil)
@@ -349,9 +355,7 @@ return function()
assert(monarch.bottom(-1) == nil)
monarch.show(SCREEN2)
assert(wait_until_hidden(SCREEN1), "Screen1 was never hidden")
assert(wait_until_shown(SCREEN2), "Screen2 was never shown")
assert_stack({ SCREEN1, SCREEN2 })
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
assert(monarch.top(0) == SCREEN2)
assert(monarch.top(-1) == SCREEN1)
assert(monarch.bottom(0) == SCREEN1)
@@ -360,8 +364,8 @@ return function()
it("should be busy while transition is running", function()
monarch.show(TRANSITION1)
assert(wait_until_shown(TRANSITION1), "Transition1 was never shown")
assert(monarch.is_busy())
assert(wait_until_visible(TRANSITION1), "Transition1 was never shown")
assert(wait_until_not_busy())
end)
@@ -373,13 +377,27 @@ return function()
end)
assert(not monarch.is_preloading(TRANSITION1))
end)
it("should be able to preload a screen and keep it loaded", function()
assert(not monarch.is_preloading(TRANSITION1))
monarch.preload(TRANSITION1, { keep_loaded = true })
wait_until_done(function(done)
monarch.when_preloaded(TRANSITION1, done)
end)
monarch.show(TRANSITION1)
assert(wait_until_visible(TRANSITION1), "Transition1 was never shown")
monarch.back()
assert(wait_until_hidden(TRANSITION1), "Transition1 was never hidden")
assert(monarch.is_preloaded(TRANSITION1))
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")
assert(wait_until_visible(TRANSITION1), "Transition1 was never shown")
end)
it("should be able to notify listeners of navigation events", function()
@@ -389,11 +407,11 @@ return function()
monarch.add_listener(URL2)
monarch.show(SCREEN1)
assert(wait_until_not_busy())
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)
@@ -405,38 +423,38 @@ return function()
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)[3].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[3].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[4].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
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)[5].message_id == monarch.SCREEN_TRANSITION_OUT_STARTED)
assert(mock_msg.messages(URL1)[5].message.screen == SCREEN1)
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)[7].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[7].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[8].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[8].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[9].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[9].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[9].message_id == monarch.SCREEN_TRANSITION_OUT_STARTED)
assert(mock_msg.messages(URL1)[9].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[10].message_id == monarch.SCREEN_TRANSITION_OUT_FINISHED)
assert(mock_msg.messages(URL1)[10].message.screen == SCREEN2)
end)
it("should be able to show a screen even while it is preloading", function()
monarch.show(SCREEN_PRELOAD, nil, { count = 1 })
assert(wait_until_shown(SCREEN_PRELOAD), "Screen_preload was never shown")
assert(wait_until_visible(SCREEN_PRELOAD), "Screen_preload was never shown")
end)
it("should be able to preload a screen and always keep it loaded", function()
monarch.show(SCREEN_PRELOAD)
assert(wait_until_shown(SCREEN_PRELOAD), "Screen_preload was never shown")
assert(wait_until_visible(SCREEN_PRELOAD), "Screen_preload was never shown")
monarch.back()
assert(wait_until_hidden(SCREEN_PRELOAD), "Screen_preload was never hidden")
assert(monarch.is_preloaded(SCREEN_PRELOAD))
@@ -444,12 +462,12 @@ return function()
it("should be able to reload a preloaded screen", function()
monarch.show(SCREEN_PRELOAD, nil, { count = 1 })
assert(wait_until_shown(SCREEN_PRELOAD), "Screen_preload was never shown")
assert(wait_until_visible(SCREEN_PRELOAD), "Screen_preload was never shown")
-- first time the screen gets loaded it will increment the count
assert(monarch.data(SCREEN_PRELOAD).count == 2)
monarch.show(SCREEN_PRELOAD, { clear = true, reload = true }, { count = 1 })
assert(wait_until_shown(SCREEN_PRELOAD), "Screen_preload was never shown")
assert(wait_until_visible(SCREEN_PRELOAD), "Screen_preload was never shown")
-- second time the screen gets shown it will be reloaded and increment the count
assert(monarch.data(SCREEN_PRELOAD).count == 2)
end)
@@ -460,13 +478,14 @@ return function()
_G.focus1_lost = nil
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert(wait_until_stack({ SCREEN1 }))
monarch.show(FOCUS1)
assert(wait_until_shown(FOCUS1), "Screen1 was never shown")
assert(wait_until_stack({ SCREEN1, FOCUS1 }))
assert(wait_until_visible(FOCUS1))
assert(_G.focus1_gained)
monarch.show(SCREEN1)
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
assert(wait_until_hidden(FOCUS1), "Focus1 was never hidden")
assert(wait_until_stack({ SCREEN1, FOCUS1, SCREEN1 }))
assert(wait_until_hidden(FOCUS1))
assert(_G.focus1_lost)
end)
@@ -477,14 +496,14 @@ return function()
-- proxy screen
monarch.show(SCREEN1)
wait_until_shown(SCREEN1)
wait_until_visible(SCREEN1)
assert(monarch.post(SCREEN1, "foobar"), "Expected monarch.post() to return true")
cowait(0.1)
assert(_G.screen1_foobar, "Screen1 never received a message")
-- factory screen
monarch.show(SCREEN2)
wait_until_shown(SCREEN2)
wait_until_visible(SCREEN2)
assert(monarch.post(SCREEN2, "foobar"), "Expected monarch.post() to return true")
cowait(0.1)
assert(_G.screen2_foobar, "Screen2 never received a message")
@@ -497,7 +516,7 @@ return function()
-- proxy screen
monarch.show(SCREEN1)
wait_until_shown(SCREEN1)
wait_until_visible(SCREEN1)
assert(monarch.post(SCREEN1, "foobar", { foo = "bar" }), "Expected monarch.post() to return true")
cowait(0.1)
assert(_G.screen1_foobar, "Screen1 never received a message")
@@ -505,7 +524,7 @@ return function()
-- factory screen
monarch.show(SCREEN2)
wait_until_shown(SCREEN2)
wait_until_visible(SCREEN2)
assert(monarch.post(SCREEN2, "foobar", { foo = "bar" }), "Expected monarch.post() to return true")
cowait(0.1)
assert(_G.screen2_foobar, "Screen2 never received a message")
@@ -517,9 +536,9 @@ return function()
_G.screen1_foobar = nil
monarch.show(SCREEN1)
wait_until_shown(SCREEN1)
monarch.show(SCREEN2)
wait_until_shown(SCREEN2)
assert(wait_until_stack({ SCREEN1, SCREEN2 }))
assert(wait_until_hidden(SCREEN1))
local ok, err = monarch.post(SCREEN1, "foobar")
assert(not ok and err, "Expected monarch.post() to return false plus an error message")
cowait(0.1)
@@ -529,9 +548,15 @@ return function()
it("should not be able to post messages to proxy screens without a receiver url", function()
monarch.show(POPUP1)
wait_until_shown(POPUP1)
wait_until_visible(POPUP1)
local ok, err = monarch.post(POPUP1, "foobar")
assert(not ok and err, "Expected monarch.post() to return false plus an error message")
end)
it("should be able to check if a screen is is a popup", function()
assert(not monarch.is_popup(SCREEN1))
assert(monarch.is_popup(POPUP1))
end)
end)
end

View File

@@ -8,6 +8,12 @@ local easing = require "monarch.transitions.easings"
return function()
local function wait_timeout(fn, ...)
local args = { ... }
cowait(function() return fn(unpack(args)) end, 5)
return fn(...)
end
describe("transitions", function()
before(function()
mock_msg.mock()
@@ -22,6 +28,28 @@ describe("transitions", function()
end)
it("should replace an existing transition with a new one", function()
local one = false
function dummy_transition1(node, to, easing, duration, delay, cb)
one = true
end
local two = false
function dummy_transition2(node, to, easing, duration, delay, cb)
two = true
end
local node = gui.new_box_node(vmath.vector3(), vmath.vector3(100, 100, 0))
local duration = 2
local t = transitions.create(node)
t.show_in(dummy_transition1, easing.OUT, duration, delay or 0)
t.show_in(dummy_transition2, easing.OUT, duration, delay or 0)
t.handle(monarch.TRANSITION.SHOW_IN)
wait_timeout(function() return one or two end)
assert(two)
assert(not one)
end)
it("should replay and immediately finish on layout change", function()
function dummy_transition(node, to, easing, duration, delay, cb)
print("dummy transition")