mirror of
https://github.com/britzl/monarch.git
synced 2025-11-26 19:00:53 +01:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf75bef8da | ||
|
|
66b1f7ca2e | ||
|
|
0fdfb6fd11 | ||
|
|
6fbec4ab8f | ||
|
|
ffc148b4bf | ||
|
|
724713f9e4 |
59
README.md
59
README.md
@@ -6,6 +6,7 @@
|
|||||||
# Monarch
|
# Monarch
|
||||||
Monarch is a screen manager for the [Defold](https://www.defold.com) game engine.
|
Monarch is a screen manager for the [Defold](https://www.defold.com) game engine.
|
||||||
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
You can use Monarch in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add:
|
You can use Monarch in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add:
|
||||||
|
|
||||||
@@ -13,9 +14,11 @@ https://github.com/britzl/monarch/archive/master.zip
|
|||||||
|
|
||||||
Or point to the ZIP file of a [specific release](https://github.com/britzl/monarch/releases).
|
Or point to the ZIP file of a [specific release](https://github.com/britzl/monarch/releases).
|
||||||
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
Using Monarch requires that screens are created in a certain way. Once you have one or more screens created you can start navigating between the screens.
|
Using Monarch requires that screens are created in a certain way. Once you have one or more screens created you can start navigating between the screens.
|
||||||
|
|
||||||
|
|
||||||
## Creating screens
|
## Creating screens
|
||||||
Monarch screens are created in individual collections and loaded through collection proxies. The recommended setup is to create one game object per screen and per game object attach a collection proxy component and an instance of the ```screen.script``` provided by Monarch. The ```screen.script``` will take care of the setup of the screen. All you need to do is to make sure that the script properties on the ```screen.script``` are correct:
|
Monarch screens are created in individual collections and loaded through collection proxies. The recommended setup is to create one game object per screen and per game object attach a collection proxy component and an instance of the ```screen.script``` provided by Monarch. The ```screen.script``` will take care of the setup of the screen. All you need to do is to make sure that the script properties on the ```screen.script``` are correct:
|
||||||
|
|
||||||
@@ -28,6 +31,7 @@ Monarch screens are created in individual collections and loaded through collect
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Navigating between screens
|
## Navigating between screens
|
||||||
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:
|
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:
|
||||||
|
|
||||||
@@ -123,6 +127,7 @@ When a transition is completed it is up to the developer to send a ```transition
|
|||||||
Monarch comes with a system for setting up transitions easily in a gui_script using the ```monarch.transitions.gui``` module. Example:
|
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 transitions = require "monarch.transitions.gui"
|
||||||
|
local monarch = require "monarch.monarch"
|
||||||
|
|
||||||
function init(self)
|
function init(self)
|
||||||
-- create transitions for the node 'root'
|
-- create transitions for the node 'root'
|
||||||
@@ -137,6 +142,10 @@ Monarch comes with a system for setting up transitions easily in a gui_script us
|
|||||||
|
|
||||||
function on_message(self, message_id, message, sender)
|
function on_message(self, message_id, message, sender)
|
||||||
self.transition.handle(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
|
end
|
||||||
|
|
||||||
### Predefined transitions
|
### Predefined transitions
|
||||||
@@ -182,6 +191,26 @@ When using dynamic screen orientation together with gui layouts or using transit
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
## Screen focus gain/loss
|
## Screen focus gain/loss
|
||||||
Monarch will send focus gain and focus loss messages if a Focus Url was provided when the screen was created. The focus gained message will contain the id of the previous screen and the focus loss message will contain the id of the next screen. Example:
|
Monarch will send focus gain and focus loss messages if a Focus Url was provided when the screen was created. The focus gained message will contain the id of the previous screen and the focus loss message will contain the id of the next screen. Example:
|
||||||
|
|
||||||
@@ -195,9 +224,11 @@ Monarch will send focus gain and focus loss messages if a Focus Url was provided
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
## Callbacks
|
## 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
|
## Monarch API
|
||||||
|
|
||||||
### monarch.show(screen_id, [options], [data], [callback])
|
### monarch.show(screen_id, [options], [data], [callback])
|
||||||
@@ -222,6 +253,34 @@ Go back to a previous Monarch screen
|
|||||||
* ```callback``` (function) - Optional function to call when the previous screen is visible.
|
* ```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.
|
||||||
|
|
||||||
|
**PARAMETERS**
|
||||||
|
* ```screen_id``` (hash) - Id of the screen to preload.
|
||||||
|
* ```callback``` (function) - Optional function to call when the new screen is preloaded.
|
||||||
|
|
||||||
|
|
||||||
|
### 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``` (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``` (hash) - Id of the requested screen
|
||||||
|
|
||||||
|
|
||||||
### monarch.data(screen_id)
|
### monarch.data(screen_id)
|
||||||
Get the data associated with a screen (from a call to ```monarch.show()``` or ```monarch.back()```).
|
Get the data associated with a screen (from a call to ```monarch.show()``` or ```monarch.back()```).
|
||||||
|
|
||||||
|
|||||||
@@ -171,10 +171,10 @@ local function async_load(screen)
|
|||||||
screen.wait_for = nil
|
screen.wait_for = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function transition(screen, message_id)
|
local function transition(screen, message_id, message)
|
||||||
log("transition()", screen.id)
|
log("transition()", screen.id)
|
||||||
screen.wait_for = M.TRANSITION.DONE
|
screen.wait_for = M.TRANSITION.DONE
|
||||||
msg.post(screen.transition_url, message_id)
|
msg.post(screen.transition_url, message_id, message)
|
||||||
coroutine.yield()
|
coroutine.yield()
|
||||||
screen.wait_for = nil
|
screen.wait_for = nil
|
||||||
end
|
end
|
||||||
@@ -234,7 +234,7 @@ local function show_out(screen, next_screen, cb)
|
|||||||
local next_is_popup = next_screen and not next_screen.popup
|
local next_is_popup = next_screen and not next_screen.popup
|
||||||
local current_is_popup = screen.popup
|
local current_is_popup = screen.popup
|
||||||
if (next_is_popup and not current_is_popup) or (current_is_popup) then
|
if (next_is_popup and not current_is_popup) or (current_is_popup) then
|
||||||
transition(screen, M.TRANSITION.SHOW_OUT)
|
transition(screen, M.TRANSITION.SHOW_OUT, { next_screen = next_screen.id })
|
||||||
unload(screen)
|
unload(screen)
|
||||||
end
|
end
|
||||||
screen.co = nil
|
screen.co = nil
|
||||||
@@ -253,14 +253,21 @@ local function show_in(screen, previous_screen, reload, cb)
|
|||||||
log("show_in() reloading", screen.id)
|
log("show_in() reloading", screen.id)
|
||||||
unload(screen)
|
unload(screen)
|
||||||
end
|
end
|
||||||
|
-- if the screen has been preloaded we need to enable it
|
||||||
|
if screen.preloaded then
|
||||||
|
log("show_in() screen was preloaded", screen.id)
|
||||||
|
msg.post(screen.proxy, ENABLE)
|
||||||
|
screen.loaded = true
|
||||||
|
screen.preloaded = false
|
||||||
-- the screen could be loaded if the previous screen was a popup
|
-- the screen could be loaded if the previous screen was a popup
|
||||||
-- and the popup asked to show this screen again
|
-- and the popup asked to show this screen again
|
||||||
-- in that case we shouldn't attempt to load it again
|
-- in that case we shouldn't attempt to load it again
|
||||||
if not screen.loaded then
|
elseif not screen.loaded then
|
||||||
|
log("show_in() loading screen", screen.id)
|
||||||
async_load(screen)
|
async_load(screen)
|
||||||
end
|
end
|
||||||
stack[#stack + 1] = screen
|
stack[#stack + 1] = screen
|
||||||
transition(screen, M.TRANSITION.SHOW_IN)
|
transition(screen, M.TRANSITION.SHOW_IN, { previous_screen = previous_screen and previous_screen.id })
|
||||||
acquire_input(screen)
|
acquire_input(screen)
|
||||||
focus_gained(screen, previous_screen)
|
focus_gained(screen, previous_screen)
|
||||||
screen.co = nil
|
screen.co = nil
|
||||||
@@ -275,11 +282,17 @@ local function back_in(screen, previous_screen, cb)
|
|||||||
co = coroutine.create(function()
|
co = coroutine.create(function()
|
||||||
screen.co = co
|
screen.co = co
|
||||||
change_context(screen)
|
change_context(screen)
|
||||||
if not screen.loaded then
|
if screen.preloaded then
|
||||||
|
log("back_in() screen was preloaded", screen.id)
|
||||||
|
msg.post(screen.proxy, ENABLE)
|
||||||
|
screen.preloaded = false
|
||||||
|
screen.loaded = true
|
||||||
|
elseif not screen.loaded then
|
||||||
|
log("back_in() loading screen", screen.id)
|
||||||
async_load(screen)
|
async_load(screen)
|
||||||
end
|
end
|
||||||
if previous_screen and not previous_screen.popup then
|
if previous_screen and not previous_screen.popup then
|
||||||
transition(screen, M.TRANSITION.BACK_IN)
|
transition(screen, M.TRANSITION.BACK_IN, { previous_screen = previous_screen.id })
|
||||||
end
|
end
|
||||||
acquire_input(screen)
|
acquire_input(screen)
|
||||||
focus_gained(screen, previous_screen)
|
focus_gained(screen, previous_screen)
|
||||||
@@ -297,7 +310,7 @@ local function back_out(screen, next_screen, cb)
|
|||||||
change_context(screen)
|
change_context(screen)
|
||||||
release_input(screen)
|
release_input(screen)
|
||||||
focus_lost(screen, next_screen)
|
focus_lost(screen, next_screen)
|
||||||
transition(screen, M.TRANSITION.BACK_OUT)
|
transition(screen, M.TRANSITION.BACK_OUT, { next_screen = next_screen and next_screen.id })
|
||||||
unload(screen)
|
unload(screen)
|
||||||
screen.co = nil
|
screen.co = nil
|
||||||
if cb then cb() end
|
if cb then cb() end
|
||||||
@@ -330,7 +343,7 @@ end
|
|||||||
-- @param options (table) - Table with options when showing the screen (can be nil). Valid values:
|
-- @param options (table) - Table with options when showing the screen (can be nil). Valid values:
|
||||||
-- * clear - Set to true if the stack should be cleared down to an existing instance of the screen
|
-- * clear - Set to true if the stack should be cleared down to an existing instance of the screen
|
||||||
-- * reload - Set to true if screen should be reloaded if it already exists in the stack and is loaded.
|
-- * reload - Set to true if screen should be reloaded if it already exists in the stack and is loaded.
|
||||||
-- This would be the case if doing a show() from a popup on the screen just below the popup.
|
-- This would be the case if doing a show() from a popup on the screen just below the popup.
|
||||||
-- @param data (*) - Optional data to set on the screen. Can be retrieved by the data() function
|
-- @param data (*) - Optional data to set on the screen. Can be retrieved by the data() function
|
||||||
-- @param cb (function) - Optional callback to invoke when screen is shown
|
-- @param cb (function) - Optional callback to invoke when screen is shown
|
||||||
function M.show(id, options, data, cb)
|
function M.show(id, options, data, cb)
|
||||||
@@ -413,6 +426,34 @@ function M.back(data, cb)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Preload a screen. This will load but not enable and show a screen. Useful for "heavier" screens
|
||||||
|
-- that you wish to show without any delay.
|
||||||
|
-- @param id (string|hash) - Id of the screen to preload
|
||||||
|
-- @param cb (function) - Optional callback to invoke when screen is loaded
|
||||||
|
function M.preload(id, cb)
|
||||||
|
assert(id, "You must provide a screen id")
|
||||||
|
id = tohash(id)
|
||||||
|
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
|
||||||
|
|
||||||
|
local screen = screens[id]
|
||||||
|
if screen.preloaded then
|
||||||
|
if cb then cb() end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local co
|
||||||
|
co = coroutine.create(function()
|
||||||
|
screen.co = co
|
||||||
|
change_context(screen)
|
||||||
|
screen.wait_for = PROXY_LOADED
|
||||||
|
msg.post(screen.proxy, ASYNC_LOAD)
|
||||||
|
coroutine.yield()
|
||||||
|
screen.preloaded = true
|
||||||
|
screen.wait_for = nil
|
||||||
|
if cb then cb() end
|
||||||
|
end)
|
||||||
|
coroutine.resume(co)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function M.on_message(message_id, message, sender)
|
function M.on_message(message_id, message, sender)
|
||||||
if message_id == PROXY_LOADED then
|
if message_id == PROXY_LOADED then
|
||||||
@@ -454,6 +495,25 @@ function M.get_stack()
|
|||||||
return s
|
return s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- Get the screen on top of the stack
|
||||||
|
-- @param offset Optional offset from the top of the stack, (eg -1 for the previous screen)
|
||||||
|
-- @return Id of the requested screen
|
||||||
|
function M.top(offset)
|
||||||
|
local screen = stack[#stack + (offset or 0)]
|
||||||
|
return screen and screen.id
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- Get the screen at the bottom of the stack
|
||||||
|
-- @param offset Optional offset from the bottom of the stack
|
||||||
|
-- @return Id of the requested screen
|
||||||
|
function M.bottom(offset)
|
||||||
|
local screen = stack[1 + (offset or 0)]
|
||||||
|
return screen and screen.id
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function M.dump_stack()
|
function M.dump_stack()
|
||||||
local s = ""
|
local s = ""
|
||||||
for i, screen in ipairs(stack) do
|
for i, screen in ipairs(stack) do
|
||||||
|
|||||||
@@ -113,16 +113,18 @@ function M.create(node)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function start_transition(transition, url)
|
local function start_transition(transition, transition_id, url)
|
||||||
table.insert(transition.urls, url)
|
table.insert(transition.urls, url)
|
||||||
if not transition.in_progress then
|
if not transition.in_progress then
|
||||||
|
table.insert(transition.urls, msg.url())
|
||||||
current_transition = transition
|
current_transition = transition
|
||||||
transition.in_progress = true
|
transition.in_progress = true
|
||||||
transition.fn(node, initial_data, transition.easing, transition.duration, transition.delay or 0, function()
|
transition.fn(node, initial_data, transition.easing, transition.duration, transition.delay or 0, function()
|
||||||
transition.in_progress = false
|
transition.in_progress = false
|
||||||
|
local message = { transition = transition_id }
|
||||||
while #transition.urls > 0 do
|
while #transition.urls > 0 do
|
||||||
local url = table.remove(transition.urls)
|
local url = table.remove(transition.urls)
|
||||||
msg.post(url, monarch.TRANSITION.DONE)
|
msg.post(url, monarch.TRANSITION.DONE, message)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -141,7 +143,7 @@ function M.create(node)
|
|||||||
else
|
else
|
||||||
local transition = transitions[message_id]
|
local transition = transitions[message_id]
|
||||||
if transition then
|
if transition then
|
||||||
start_transition(transition, sender)
|
start_transition(transition, message_id, sender)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -175,6 +175,31 @@ return function()
|
|||||||
assert(wait_until_shown(SCREEN2), "Popup2 was never shown")
|
assert(wait_until_shown(SCREEN2), "Popup2 was never shown")
|
||||||
assert_stack({ SCREEN1, SCREEN2 })
|
assert_stack({ SCREEN1, SCREEN2 })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
it("should be able to get the id of the screen at the top and bottom of the stack", function()
|
||||||
|
assert(monarch.top() == nil)
|
||||||
|
assert(monarch.bottom() == nil)
|
||||||
|
assert(monarch.top(1) == nil)
|
||||||
|
assert(monarch.bottom(-1) == nil)
|
||||||
|
|
||||||
|
monarch.show(SCREEN1)
|
||||||
|
assert(wait_until_shown(SCREEN1), "Screen1 was never shown")
|
||||||
|
assert(monarch.top() == SCREEN1)
|
||||||
|
assert(monarch.top(0) == SCREEN1)
|
||||||
|
assert(monarch.top(1) == nil)
|
||||||
|
assert(monarch.bottom() == SCREEN1)
|
||||||
|
assert(monarch.bottom(0) == SCREEN1)
|
||||||
|
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(monarch.top(0) == SCREEN2)
|
||||||
|
assert(monarch.top(-1) == SCREEN1)
|
||||||
|
assert(monarch.bottom(0) == SCREEN1)
|
||||||
|
assert(monarch.bottom(1) == SCREEN2)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user