3
0
mirror of https://github.com/britzl/monarch.git synced 2025-11-27 03:10:54 +01:00

Compare commits

..

10 Commits

Author SHA1 Message Date
Björn Ritzl
8d1051f0fd Added Timestep below Popup
Fixes #25
2018-07-26 10:52:03 +02:00
Björn Ritzl
bc4260d72a Added fade in/out transition 2018-07-13 06:54:26 +02:00
Björn Ritzl
07eacc7a5f Track when callbacks are invoked so that we can ensure that all screens are shown/hidden before invoking callbacks 2018-06-21 11:33:20 +02:00
Björn Ritzl
5ee6ea5982 Log when monarch is busy 2018-06-21 11:30:17 +02:00
Björn Ritzl
f590c75b9e Removed debug prints that was accidentally left behind 2018-06-21 11:29:59 +02:00
Björn Ritzl
d528947998 Updated docs with new notification messages 2018-06-20 13:49:02 +02:00
Björn Ritzl
22f0e6cddf Changed from single notification to begin and end notification for every navigation 2018-06-20 13:46:45 +02:00
Björn Ritzl
b13adcfb62 Added listener documentation 2018-06-20 07:36:16 +02:00
Björn Ritzl
3947e86169 Added navigation listeners 2018-06-20 07:30:38 +02:00
Björn Ritzl
e570eac40b Don't preload an already loaded screen 2018-06-18 07:19:37 +02:00
14 changed files with 480 additions and 18 deletions

View File

@@ -26,6 +26,7 @@ Monarch screens are created in individual collections and loaded through collect
* **Screen Id (hash)** - A unique id that can be used to reference the screen when navigating your app. * **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 (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. * **Popup on Popup (boolean)** - Check this if the screen is a [popup](#popups) and it can be shown on top of other popups.
* **Timestep below Popup (number)** - Timestep to set on screen proxy when it is below a popup. This is useful when pausing animations and gameplay while a popup is open.
* **Transition Url (url)** - Optional URL to call when the screen is about to be shown/hidden. Use this to trigger a transition (see the section on [transitions](#transitions)). * **Transition Url (url)** - Optional URL to call when the screen is about to be shown/hidden. Use this to trigger a transition (see the section on [transitions](#transitions)).
* **Focus Url (url)** - Optional URL to call when the screen gains or loses focus (see the section on [screen focus](#screen-focus-gainloss)). * **Focus Url (url)** - Optional URL to call when the screen gains or loses focus (see the section on [screen focus](#screen-focus-gainloss)).
@@ -162,6 +163,8 @@ The predefined transitions provided by ```monarch.transitions.gui``` are:
* ```slide_out_bottom``` * ```slide_out_bottom```
* ```scale_in``` * ```scale_in```
* ```scale_out``` * ```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: Additionally there's functionality to create a full set of transitions for common transition styles:
@@ -169,6 +172,7 @@ Additionally there's functionality to create a full set of transitions for commo
* ```transitions.in_left_out_right(node, duration, [delay], [easing])``` * ```transitions.in_left_out_right(node, duration, [delay], [easing])```
* ```transitions.in_left_out_left(node, duration, [delay], [easing])``` * ```transitions.in_left_out_left(node, duration, [delay], [easing])```
* ```transitions.in_right_out_right(node, duration, [delay], [easing])``` * ```transitions.in_right_out_right(node, duration, [delay], [easing])```
* ```transitions.fade_in_out(node, duration, [delay], [easing])```
**PARAMETERS** **PARAMETERS**
* ```node``` (node) - Gui node to animate. * ```node``` (node) - Gui node to animate.
@@ -334,5 +338,51 @@ Check if Monarch is busy showing and/or hiding a screen.
* ```busy``` (boolean) - True if busy hiding and/or showing a screen. * ```busy``` (boolean) - True if busy hiding and/or showing a screen.
### monarch.add_listener([url])
Add a URL that will be notified of navigation events.
**PARAMETERS**
* ```url``` (url) - URL to send navigation events to. Will use current URL if omitted.
### monarch.remove_listener([url])
Remove a previously added listener.
**PARAMETERS**
* ```url``` (url) - URL to remove. Will use current URL if omitted.
### monarch.debug() ### monarch.debug()
Enable verbose logging of the internals of Monarch. Enable verbose logging of the internals of Monarch.
### monarch.SCREEN_TRANSITION_IN_STARTED
Message sent to listeners when a screen has started to transition in.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```previous_screen``` (hash) - Id of the previous screen (if any)
### monarch.SCREEN_TRANSITION_IN_FINISHED
Message sent to listeners when a screen has finished to transition in.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```previous_screen``` (hash) - Id of the previous screen (if any)
### monarch.SCREEN_TRANSITION_OUT_STARTED
Message sent to listeners when a screen has started to transition out.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```next_screen``` (hash) - Id of the next screen (if any)
### monarch.SCREEN_TRANSITION_OUT_FINISHED
Message sent to listeners when a screen has finished to transition out.
**PARAMETERS**
* ```screen``` (hash) - Id of the screen
* ```next_screen``` (hash) - Id of the next screen (if any)

View File

@@ -22,6 +22,11 @@ embedded_instances {
" type: PROPERTY_TYPE_HASH\n" " type: PROPERTY_TYPE_HASH\n"
" }\n" " }\n"
" properties {\n" " properties {\n"
" id: \"timestep_below_popup\"\n"
" value: \"0.0\"\n"
" type: PROPERTY_TYPE_NUMBER\n"
" }\n"
" properties {\n"
" id: \"transition_url\"\n" " id: \"transition_url\"\n"
" value: \"menu:/go#menu\"\n" " value: \"menu:/go#menu\"\n"
" type: PROPERTY_TYPE_URL\n" " type: PROPERTY_TYPE_URL\n"
@@ -271,6 +276,11 @@ embedded_instances {
" type: PROPERTY_TYPE_BOOLEAN\n" " type: PROPERTY_TYPE_BOOLEAN\n"
" }\n" " }\n"
" properties {\n" " properties {\n"
" id: \"timestep_below_popup\"\n"
" value: \"0.0\"\n"
" type: PROPERTY_TYPE_NUMBER\n"
" }\n"
" properties {\n"
" id: \"transition_url\"\n" " id: \"transition_url\"\n"
" value: \"popup:/go#popup\"\n" " value: \"popup:/go#popup\"\n"
" type: PROPERTY_TYPE_URL\n" " type: PROPERTY_TYPE_URL\n"

View File

@@ -480,6 +480,61 @@ nodes {
text_leading: 1.0 text_leading: 1.0
text_tracking: 0.0 text_tracking: 0.0
} }
nodes {
position {
x: 320.0
y: 272.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 40.0
y: 40.0
z: 0.0
w: 1.0
}
color {
x: 0.6
y: 0.0
z: 0.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: ""
id: "spinner"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
inherit_alpha: true
slice9 {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: false
size_mode: SIZE_MODE_MANUAL
}
material: "/builtins/materials/gui.material" material: "/builtins/materials/gui.material"
layouts { layouts {
name: "Landscape" name: "Landscape"

View File

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

View File

@@ -417,6 +417,61 @@ nodes {
text_leading: 1.0 text_leading: 1.0
text_tracking: 0.0 text_tracking: 0.0
} }
nodes {
position {
x: 184.0
y: 136.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 10.0
y: 10.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: ""
id: "spinner"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "root"
layer: ""
inherit_alpha: true
slice9 {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: false
size_mode: SIZE_MODE_MANUAL
}
material: "/builtins/materials/gui.material" material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512 max_nodes: 512

View File

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

View File

@@ -1,7 +1,7 @@
[project] [project]
title = Monarch title = Monarch
version = 0.9 version = 0.9
dependencies = https://github.com/britzl/deftest/archive/1.2.1.zip dependencies = https://github.com/britzl/deftest/archive/2.3.0.zip
[bootstrap] [bootstrap]
main_collection = /example/example.collectionc main_collection = /example/example.collectionc

View File

@@ -1,3 +1,5 @@
local callback_tracker = require "monarch.utils.callback_tracker"
local M = {} local M = {}
local CONTEXT = hash("monarch_context") local CONTEXT = hash("monarch_context")
@@ -10,6 +12,7 @@ local ASYNC_LOAD = hash("async_load")
local UNLOAD = hash("unload") local UNLOAD = hash("unload")
local ENABLE = hash("enable") local ENABLE = hash("enable")
-- transition messages
M.TRANSITION = {} M.TRANSITION = {}
M.TRANSITION.DONE = hash("transition_done") M.TRANSITION.DONE = hash("transition_done")
M.TRANSITION.SHOW_IN = hash("transition_show_in") M.TRANSITION.SHOW_IN = hash("transition_show_in")
@@ -17,16 +20,27 @@ M.TRANSITION.SHOW_OUT = hash("transition_show_out")
M.TRANSITION.BACK_IN = hash("transition_back_in") M.TRANSITION.BACK_IN = hash("transition_back_in")
M.TRANSITION.BACK_OUT = hash("transition_back_out") M.TRANSITION.BACK_OUT = hash("transition_back_out")
-- focus messages
M.FOCUS = {} M.FOCUS = {}
M.FOCUS.GAINED = hash("monarch_focus_gained") M.FOCUS.GAINED = hash("monarch_focus_gained")
M.FOCUS.LOST = hash("monarch_focus_lost") M.FOCUS.LOST = hash("monarch_focus_lost")
-- listener messages
M.SCREEN_TRANSITION_IN_STARTED = hash("monarch_screen_transition_in_started")
M.SCREEN_TRANSITION_IN_FINISHED = hash("monarch_screen_transition_in_finished")
M.SCREEN_TRANSITION_OUT_STARTED = hash("monarch_screen_transition_out_started")
M.SCREEN_TRANSITION_OUT_FINISHED = hash("monarch_screen_transition_out_finished")
-- all registered screens -- all registered screens
local screens = {} local screens = {}
-- the current stack of screens -- the current stack of screens
local stack = {} local stack = {}
-- navigation listeners
local listeners = {}
-- the number of active transitions -- the number of active transitions
-- monarch is considered busy while there are active transitions -- monarch is considered busy while there are active transitions
local active_transition_count = 0 local active_transition_count = 0
@@ -46,6 +60,12 @@ local function tohash(s)
return hash_lookup[s] return hash_lookup[s]
end end
local function notify_listeners(message_id, message)
log("notify_listeners()", message_id)
for _,url in pairs(listeners) do
msg.post(url, message_id, message or {})
end
end
local function screen_from_proxy(proxy) local function screen_from_proxy(proxy)
for _,screen in pairs(screens) do for _,screen in pairs(screens) do
@@ -107,6 +127,7 @@ end
-- screen transitions -- screen transitions
-- * focus_url - URL to a script that is to be notified of focus -- * focus_url - URL to a script that is to be notified of focus
-- lost/gained events -- lost/gained events
-- * timestep_below_popup - Timestep to set on proxy when below a popup
function M.register(id, proxy, settings) function M.register(id, proxy, settings)
assert(id, "You must provide a screen id") assert(id, "You must provide a screen id")
id = tohash(id) id = tohash(id)
@@ -121,6 +142,7 @@ function M.register(id, proxy, settings)
popup_on_popup = settings and settings.popup_on_popup, popup_on_popup = settings and settings.popup_on_popup,
transition_url = settings and settings.transition_url, transition_url = settings and settings.transition_url,
focus_url = settings and settings.focus_url, focus_url = settings and settings.focus_url,
timestep_below_popup = settings and settings.timestep_below_popup or 1,
} }
end end
@@ -199,6 +221,18 @@ local function focus_lost(screen, next_screen)
end end
end end
local function change_timestep(screen)
screen.changed_timestep = true
msg.post(screen.proxy, "set_time_step", { mode = 0, factor = screen.timestep_below_popup })
end
local function reset_timestep(screen)
if screen.changed_timestep then
msg.post(screen.proxy, "set_time_step", { mode = 0, factor = 1 })
screen.changed_timestep = false
end
end
local function disable(screen, next_screen) local function disable(screen, next_screen)
log("disable()", screen.id) log("disable()", screen.id)
local co local co
@@ -207,6 +241,11 @@ local function disable(screen, next_screen)
change_context(screen) change_context(screen)
release_input(screen) release_input(screen)
focus_lost(screen, next_screen) focus_lost(screen, next_screen)
if next_screen and next_screen.popup then
change_timestep(screen)
else
reset_timestep(screen)
end
screen.co = nil screen.co = nil
if cb then cb() end if cb then cb() end
end) end)
@@ -221,6 +260,7 @@ local function enable(screen, previous_screen)
change_context(screen) change_context(screen)
acquire_input(screen) acquire_input(screen)
focus_gained(screen, previous_screen) focus_gained(screen, previous_screen)
reset_timestep(screen)
screen.co = nil screen.co = nil
if cb then cb() end if cb then cb() end
end) end)
@@ -232,21 +272,26 @@ local function show_out(screen, next_screen, cb)
local co local co
co = coroutine.create(function() co = coroutine.create(function()
active_transition_count = active_transition_count + 1 active_transition_count = active_transition_count + 1
notify_listeners(M.SCREEN_TRANSITION_OUT_STARTED, { screen = screen.id, next_screen = next_screen.id })
screen.co = co screen.co = co
change_context(screen) change_context(screen)
release_input(screen) release_input(screen)
focus_lost(screen, next_screen) focus_lost(screen, next_screen)
reset_timestep(screen)
-- if the next screen is a popup we want the current screen to stay visible below the popup -- if the next screen is a popup we want the current screen to stay visible below the popup
-- if the next screen isn't a popup the current one should be unloaded and transitioned out -- if the next screen isn't a popup the current one should be unloaded and transitioned out
local next_is_popup = next_screen and not next_screen.popup local next_is_popup = next_screen and next_screen.popup
local current_is_popup = screen.popup local current_is_popup = screen.popup
if (next_is_popup and not current_is_popup) or (current_is_popup) then if (not next_is_popup and not current_is_popup) or (current_is_popup) then
transition(screen, M.TRANSITION.SHOW_OUT, { next_screen = next_screen.id }) transition(screen, M.TRANSITION.SHOW_OUT, { next_screen = next_screen.id })
unload(screen) unload(screen)
elseif next_is_popup then
change_timestep(screen)
end end
screen.co = nil screen.co = nil
active_transition_count = active_transition_count - 1 active_transition_count = active_transition_count - 1
if cb then cb() end if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_OUT_FINISHED, { screen = screen.id, next_screen = next_screen.id })
end) end)
coroutine.resume(co) coroutine.resume(co)
end end
@@ -256,6 +301,7 @@ local function show_in(screen, previous_screen, reload, cb)
local co local co
co = coroutine.create(function() co = coroutine.create(function()
active_transition_count = active_transition_count + 1 active_transition_count = active_transition_count + 1
notify_listeners(M.SCREEN_TRANSITION_IN_STARTED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
screen.co = co screen.co = co
change_context(screen) change_context(screen)
if reload and screen.loaded then if reload and screen.loaded then
@@ -276,12 +322,14 @@ local function show_in(screen, previous_screen, reload, cb)
async_load(screen) async_load(screen)
end end
stack[#stack + 1] = screen stack[#stack + 1] = screen
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 })
acquire_input(screen) acquire_input(screen)
focus_gained(screen, previous_screen) focus_gained(screen, previous_screen)
screen.co = nil screen.co = nil
active_transition_count = active_transition_count - 1 active_transition_count = active_transition_count - 1
if cb then cb() end if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_IN_FINISHED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
end) end)
coroutine.resume(co) coroutine.resume(co)
end end
@@ -291,6 +339,7 @@ local function back_in(screen, previous_screen, cb)
local co local co
co = coroutine.create(function() co = coroutine.create(function()
active_transition_count = active_transition_count + 1 active_transition_count = active_transition_count + 1
notify_listeners(M.SCREEN_TRANSITION_IN_STARTED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
screen.co = co screen.co = co
change_context(screen) change_context(screen)
if screen.preloaded then if screen.preloaded then
@@ -302,6 +351,7 @@ local function back_in(screen, previous_screen, cb)
log("back_in() loading screen", screen.id) log("back_in() loading screen", screen.id)
async_load(screen) async_load(screen)
end end
reset_timestep(screen)
if previous_screen and not previous_screen.popup then 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 })
end end
@@ -310,6 +360,7 @@ local function back_in(screen, previous_screen, cb)
screen.co = nil screen.co = nil
active_transition_count = active_transition_count - 1 active_transition_count = active_transition_count - 1
if cb then cb() end if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_IN_FINISHED, { screen = screen.id, previous_screen = previous_screen and previous_screen.id })
end) end)
coroutine.resume(co) coroutine.resume(co)
end end
@@ -318,16 +369,21 @@ local function back_out(screen, next_screen, cb)
log("back_out()", screen.id) log("back_out()", screen.id)
local co local co
co = coroutine.create(function() co = coroutine.create(function()
notify_listeners(M.SCREEN_TRANSITION_OUT_STARTED, { screen = screen.id, next_screen = next_screen.id })
active_transition_count = active_transition_count + 1 active_transition_count = active_transition_count + 1
screen.co = co screen.co = co
change_context(screen) change_context(screen)
release_input(screen) release_input(screen)
focus_lost(screen, next_screen) focus_lost(screen, next_screen)
if next_screen and screen.popup then
reset_timestep(next_screen)
end
transition(screen, M.TRANSITION.BACK_OUT, { next_screen = next_screen and next_screen.id }) transition(screen, M.TRANSITION.BACK_OUT, { next_screen = next_screen and next_screen.id })
unload(screen) unload(screen)
screen.co = nil screen.co = nil
active_transition_count = active_transition_count - 1 active_transition_count = active_transition_count - 1
if cb then cb() end if cb then cb() end
notify_listeners(M.SCREEN_TRANSITION_OUT_FINISHED, { screen = screen.id, next_screen = next_screen.id })
end) end)
coroutine.resume(co) coroutine.resume(co)
end end
@@ -373,9 +429,12 @@ end
function M.show(id, options, data, cb) function M.show(id, options, data, cb)
assert(id, "You must provide a screen id") assert(id, "You must provide a screen id")
if M.is_busy() then if M.is_busy() then
log("show() monarch is busy, ignoring request")
return false return false
end end
local callbacks = callback_tracker()
id = tohash(id) id = tohash(id)
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id))) assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
@@ -396,13 +455,13 @@ function M.show(id, options, data, cb)
-- close all popups -- close all popups
while top.popup do while top.popup do
stack[#stack] = nil stack[#stack] = nil
show_out(top, screen) show_out(top, screen, callbacks.track())
top = stack[#stack] top = stack[#stack]
end end
-- unload and transition out from top -- unload and transition out from top
-- unless we're showing the same screen as is already visible -- unless we're showing the same screen as is already visible
if top and top.id ~= screen.id then if top and top.id ~= screen.id then
show_out(top, screen) show_out(top, screen, callbacks.track())
end end
end end
end end
@@ -419,7 +478,9 @@ function M.show(id, options, data, cb)
end end
-- show screen -- show screen
show_in(screen, top, options and options.reload, cb) show_in(screen, top, options and options.reload, callbacks.track())
if cb then callbacks.when_done(cb) end
return true return true
end end
@@ -431,9 +492,12 @@ end
-- @return true if successfully going back, false if busy performing another operation -- @return true if successfully going back, false if busy performing another operation
function M.back(data, cb) function M.back(data, cb)
if M.is_busy() then if M.is_busy() then
log("back() monarch is busy, ignoring request")
return false return false
end end
local callbacks = callback_tracker()
local screen = table.remove(stack) local screen = table.remove(stack)
if screen then if screen then
log("back()", screen.id) log("back()", screen.id)
@@ -445,7 +509,7 @@ function M.back(data, cb)
if data then if data then
top.data = data top.data = data
end end
back_in(top, screen, cb) back_in(top, screen, callbacks.track())
end) end)
else else
back_out(screen, top) back_out(screen, top)
@@ -453,12 +517,13 @@ function M.back(data, cb)
if data then if data then
top.data = data top.data = data
end end
back_in(top, screen, cb) back_in(top, screen, callbacks.track())
end end
end end
elseif cb then
cb()
end end
if cb then callbacks.when_done(cb) end
return true return true
end end
@@ -472,7 +537,7 @@ function M.preload(id, cb)
assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id))) assert(screens[id], ("There is no screen registered with id %s"):format(tostring(id)))
local screen = screens[id] local screen = screens[id]
if screen.preloaded then if screen.preloaded or screen.loaded then
if cb then cb() end if cb then cb() end
return return
end end
@@ -549,6 +614,26 @@ function M.bottom(offset)
return screen and screen.id return screen and screen.id
end end
local function url_to_key(url)
return (url.socket or hash("")) .. (url.path or hash("")) .. (url.fragment or hash(""))
end
--- Add a listener to be notified of when screens are shown or hidden
-- @param url The url to notify, nil for current url
function M.add_listener(url)
url = url or msg.url()
listeners[url_to_key(url)] = url
end
--- Remove a previously added listener
-- @param url The url to remove, nil for current url
function M.remove_listener(url)
url = url or msg.url()
listeners[url_to_key(url)] = nil
end
function M.dump_stack() function M.dump_stack()
local s = "" local s = ""

View File

@@ -4,6 +4,7 @@ go.property("screen_proxy", msg.url("#collectionproxy"))
go.property("screen_id", hash("")) go.property("screen_id", hash(""))
go.property("popup", false) go.property("popup", false)
go.property("popup_on_popup", false) go.property("popup_on_popup", false)
go.property("timestep_below_popup", 1)
go.property("transition_url", msg.url()) go.property("transition_url", msg.url())
go.property("focus_url", msg.url()) go.property("focus_url", msg.url())
@@ -18,7 +19,8 @@ function init(self)
popup = self.popup, popup = self.popup,
popup_on_popup = self.popup_on_popup, popup_on_popup = self.popup_on_popup,
transition_url = self.transition_url, transition_url = self.transition_url,
focus_url = self.focus_url focus_url = self.focus_url,
timestep_below_popup = self.timestep_below_popup,
} }
) )
end end

View File

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

View File

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

View File

@@ -315,3 +315,43 @@ embedded_instances {
z: 1.0 z: 1.0
} }
} }
embedded_instances {
id: "listener1"
data: ""
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale3 {
x: 1.0
y: 1.0
z: 1.0
}
}
embedded_instances {
id: "listener2"
data: ""
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale3 {
x: 1.0
y: 1.0
z: 1.0
}
}

52
test/msg.lua Normal file
View File

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

View File

@@ -1,4 +1,6 @@
local cowait = require "test.cowait" local cowait = require "test.cowait"
local mock_msg = require "test.msg"
local unload = require "deftest.util.unload"
local monarch = require "monarch.monarch" local monarch = require "monarch.monarch"
local SCREEN1_STR = hash("screen1") local SCREEN1_STR = hash("screen1")
@@ -55,12 +57,14 @@ return function()
describe("monarch", function() describe("monarch", function()
before(function() before(function()
mock_msg.mock()
monarch = require "monarch.monarch" monarch = require "monarch.monarch"
screens_instances = collectionfactory.create("#screensfactory") screens_instances = collectionfactory.create("#screensfactory")
end) end)
after(function() after(function()
package.loaded["monarch.monarch"] = nil mock_msg.unmock()
unload.unload("monarch%..*")
for id,instance_id in pairs(screens_instances) do for id,instance_id in pairs(screens_instances) do
go.delete(instance_id) go.delete(instance_id)
end end
@@ -229,5 +233,43 @@ return function()
assert(monarch.is_busy()) assert(monarch.is_busy())
assert(wait_until_not_busy()) assert(wait_until_not_busy())
end) end)
it("should be able to notify listeners of navigation events", function()
local URL1 = msg.url(screens_instances[hash("/listener1")])
local URL2 = msg.url(screens_instances[hash("/listener2")])
monarch.add_listener(URL1)
monarch.add_listener(URL2)
monarch.show(SCREEN1)
assert(mock_msg.messages(URL1)[1].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[1].message.screen == SCREEN1)
assert(mock_msg.messages(URL2)[1].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL2)[1].message.screen == SCREEN1)
assert(wait_until_not_busy())
assert(mock_msg.messages(URL1)[2].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[2].message.screen == SCREEN1)
assert(mock_msg.messages(URL2)[2].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL2)[2].message.screen == SCREEN1)
monarch.remove_listener(URL2)
monarch.show(SCREEN2)
assert(wait_until_not_busy())
monarch.back()
assert(wait_until_not_busy())
local messages_1 = mock_msg.messages(URL1)
local messages_2 = mock_msg.messages(URL2)
assert(#mock_msg.messages(URL1) == 10)
assert(#mock_msg.messages(URL2) == 2)
assert(mock_msg.messages(URL1)[3].message_id == monarch.SCREEN_TRANSITION_OUT_STARTED)
assert(mock_msg.messages(URL1)[3].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[4].message_id == monarch.SCREEN_TRANSITION_IN_STARTED)
assert(mock_msg.messages(URL1)[4].message.screen == SCREEN2)
assert(mock_msg.messages(URL1)[5].message_id == monarch.SCREEN_TRANSITION_OUT_FINISHED)
assert(mock_msg.messages(URL1)[5].message.screen == SCREEN1)
assert(mock_msg.messages(URL1)[6].message_id == monarch.SCREEN_TRANSITION_IN_FINISHED)
assert(mock_msg.messages(URL1)[7].message.screen == SCREEN2)
end)
end) end)
end end