3
0
mirror of https://github.com/britzl/monarch.git synced 2025-06-27 10:27:49 +02:00

Merge branch 'dev/agulev'

This commit is contained in:
Björn Ritzl 2017-12-08 07:42:07 +01:00
commit 995843ff20
4 changed files with 211 additions and 125 deletions

View File

@ -84,6 +84,8 @@ You can add optional transitions when navigating between screens. The default be
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. Monarch comes with a system for setting up transitions easily in a gui_script. 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"
function init(self)
@ -101,6 +103,34 @@ When a transition is completed it is up to the developer to send a ```transition
self.transition.handle(message_id, message, sender)
end
### Predefined transitions
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```
### 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.
## 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:

View File

@ -1,6 +1,7 @@
local monarch = require "monarch.monarch"
function init(self)
monarch.debug()
msg.post("@render:/", "clear_color", { color = vmath.vector4(0.4, 0.6, 0.8,1.0) })
msg.post("#", "init_monarch") -- wait until init() has been called for all screen.script instances
end

View File

@ -26,6 +26,12 @@ M.FOCUS.GAINED = hash("monarch_focus_gained")
M.FOCUS.LOST = hash("monarch_focus_lost")
local function log(...) end
function M.debug()
log = print
end
local function screen_from_proxy(proxy)
for _, screen in pairs(screens) do
if screen.proxy == proxy then
@ -101,29 +107,86 @@ function M.unregister(id)
screens[id] = nil
end
local function acquire_input(screen)
log("change_context()", screen.id)
if not screen.input then
msg.post(screen.script, ACQUIRE_INPUT_FOCUS)
screen.input = true
end
end
local function release_input(screen)
log("change_context()", screen.id)
if screen.input then
msg.post(screen.script, RELEASE_INPUT_FOCUS)
screen.input = false
end
end
local function change_context(screen)
log("change_context()", screen.id)
screen.wait_for = CONTEXT
msg.post(screen.script, CONTEXT)
coroutine.yield()
screen.wait_for = nil
end
local function unload(screen)
log("unload()", screen.id)
screen.wait_for = PROXY_UNLOADED
msg.post(screen.proxy, UNLOAD)
coroutine.yield()
screen.loaded = false
screen.wait_for = nil
end
local function async_load(screen)
log("async_load()", screen.id)
screen.wait_for = PROXY_LOADED
msg.post(screen.proxy, ASYNC_LOAD)
coroutine.yield()
msg.post(screen.proxy, ENABLE)
screen.loaded = true
screen.wait_for = nil
end
local function transition(screen, message_id)
log("transition()", screen.id)
screen.wait_for = M.TRANSITION.DONE
msg.post(screen.transition_url, message_id)
coroutine.yield()
screen.wait_for = nil
end
local function focus_gained(screen, previous_screen)
log("focus_gained()", screen.id)
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.GAINED, {id = previous_screen and previous_screen.id})
end
end
local function focus_lost(screen, next_screen)
log("focus_lost()", screen.id)
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.LOST, {id = next_screen and next_screen.id})
end
end
local function show_out(screen, next_screen, cb)
log("show_out()", screen.id)
local co
co = coroutine.create(function()
screen.co = co
msg.post(screen.script, RELEASE_INPUT_FOCUS)
screen.input = false
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.LOST, {id = next_screen.id})
end
msg.post(screen.script, CONTEXT)
coroutine.yield()
change_context(screen)
release_input(screen)
focus_lost(screen, next_screen)
-- if the next screen is a popup we want the current screen to stay visible below the popup
-- if the next screen isn't a popup the current one should be unloaded and transitioned out
local next_is_popup = next_screen and not next_screen.popup
local current_is_popup = screen.popup
if (next_is_popup and not current_is_popup) or (current_is_popup) then
msg.post(screen.transition_url, M.TRANSITION.SHOW_OUT)
coroutine.yield()
msg.post(screen.proxy, UNLOAD)
coroutine.yield()
screen.loaded = false
transition(screen, M.TRANSITION.SHOW_OUT)
unload(screen)
end
screen.co = nil
if cb then cb() end
@ -132,39 +195,25 @@ local function show_out(screen, next_screen, cb)
end
local function show_in(screen, previous_screen, reload, cb)
log("show_in()", screen.id)
local co
co = coroutine.create(function()
screen.co = co
msg.post(screen.script, CONTEXT)
coroutine.yield()
change_context(screen)
if reload and screen.loaded then
msg.post(screen.proxy, UNLOAD)
coroutine.yield()
screen.loaded = false
log("show_in() reloading", screen.id)
unload(screen)
end
-- the screen could be loaded if the previous screen was a popup
-- and the popup asked to show this screen again
-- in that case we shouldn't attempt to load it again
if not screen.loaded then
msg.post(screen.proxy, ASYNC_LOAD)
coroutine.yield()
msg.post(screen.proxy, ENABLE)
screen.loaded = true
async_load(screen)
end
stack[#stack + 1] = screen
msg.post(screen.transition_url, M.TRANSITION.SHOW_IN)
coroutine.yield()
if not screen.input then
msg.post(screen.script, ACQUIRE_INPUT_FOCUS)
screen.input = true
end
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.GAINED, {id = previous_screen and previous_screen.id})
end
transition(screen, M.TRANSITION.SHOW_IN)
acquire_input(screen)
focus_gained(screen, previous_screen)
screen.co = nil
if cb then cb() end
end)
@ -172,30 +221,19 @@ local function show_in(screen, previous_screen, reload, cb)
end
local function back_in(screen, previous_screen, cb)
log("back_in()", screen.id)
local co
co = coroutine.create(function()
screen.co = co
msg.post(screen.script, CONTEXT)
coroutine.yield()
change_context(screen)
if not screen.loaded then
msg.post(screen.proxy, ASYNC_LOAD)
coroutine.yield()
msg.post(screen.proxy, ENABLE)
screen.loaded = true
async_load(screen)
end
if previous_screen and not previous_screen.popup then
msg.post(screen.transition_url, M.TRANSITION.BACK_IN)
coroutine.yield()
end
if not screen.input then
msg.post(screen.script, ACQUIRE_INPUT_FOCUS)
screen.input = true
end
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.GAINED, {id = previous_screen.id})
transition(screen, M.TRANSITION.BACK_IN)
end
acquire_input(screen)
focus_gained(screen, previous_screen)
screen.co = nil
if cb then cb() end
end)
@ -203,21 +241,15 @@ local function back_in(screen, previous_screen, cb)
end
local function back_out(screen, next_screen, cb)
log("back_out()", screen.id)
local co
co = coroutine.create(function()
screen.co = co
msg.post(screen.script, RELEASE_INPUT_FOCUS)
screen.input = false
if screen.focus_url then
msg.post(screen.focus_url, M.FOCUS.LOST, {id = next_screen and next_screen.id})
end
msg.post(screen.script, CONTEXT)
coroutine.yield()
msg.post(screen.transition_url, M.TRANSITION.BACK_OUT)
coroutine.yield()
msg.post(screen.proxy, UNLOAD)
coroutine.yield()
screen.loaded = false
change_context(screen)
release_input(screen)
focus_lost(screen, next_screen)
transition(screen, M.TRANSITION.BACK_OUT)
unload(screen)
screen.co = nil
if cb then cb() end
end)
@ -256,6 +288,8 @@ function M.show(id, options, data, cb)
local screen = screens[id]
screen.data = data
log("show()", screen.id)
-- manipulate the current top
-- close popup if needed
-- transition out
@ -279,6 +313,7 @@ function M.show(id, options, data, cb)
-- 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
@ -295,6 +330,7 @@ end
function M.back(data, cb)
local screen = table.remove(stack)
if screen then
log("back()", screen.id)
local top = stack[#stack]
-- 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
@ -324,19 +360,27 @@ function M.on_message(message_id, message, sender)
if message_id == PROXY_LOADED then
local screen = screen_from_proxy(sender)
assert(screen, "Unable to find screen for loaded proxy")
assert(coroutine.resume(screen.co))
if screen.wait_for == PROXY_LOADED then
assert(coroutine.resume(screen.co))
end
elseif message_id == PROXY_UNLOADED then
local screen = screen_from_proxy(sender)
assert(screen, "Unable to find screen for unloaded proxy")
assert(coroutine.resume(screen.co))
if screen.wait_for == PROXY_UNLOADED then
assert(coroutine.resume(screen.co))
end
elseif message_id == CONTEXT then
local screen = screen_from_script()
assert(screen, "Unable to find screen for current script url")
assert(coroutine.resume(screen.co))
if screen.wait_for == CONTEXT then
assert(coroutine.resume(screen.co))
end
elseif message_id == M.TRANSITION.DONE then
local screen = screen_from_script()
assert(screen, "Unable to find screen for current script url")
assert(coroutine.resume(screen.co))
if screen.wait_for == M.TRANSITION.DONE then
assert(coroutine.resume(screen.co))
end
end
end

View File

@ -12,71 +12,63 @@ local BOTTOM = vmath.vector3(0, - HEIGHT * 2, 0)
local ZERO_SCALE = vmath.vector3(0, 0, 1)
function M.instant(node, to, easing, duration, delay, url)
msg.post(url, monarch.TRANSITION.DONE)
function M.instant(node, to, easing, duration, delay, cb)
cb()
end
local function slide_in(direction, node, to, easing, duration, delay, url)
local function slide_in(direction, node, to, easing, duration, delay, cb)
local from = to + direction
gui.set_position(node, from)
gui.animate(node, gui.PROP_POSITION, to, easing, duration, delay, function()
msg.post(url, monarch.TRANSITION.DONE)
end)
gui.animate(node, gui.PROP_POSITION, to, easing, duration, delay, cb)
end
function M.slide_in_left(node, to, easing, duration, delay, url)
return slide_in(LEFT, node, to.pos, easing, duration, delay, url)
function M.slide_in_left(node, to, easing, duration, delay, cb)
return slide_in(LEFT, node, to.pos, easing, duration, delay, cb)
end
function M.slide_in_right(node, to, easing, duration, delay, url)
slide_in(RIGHT, node, to.pos, easing, duration, delay, url)
function M.slide_in_right(node, to, easing, duration, delay, cb)
slide_in(RIGHT, node, to.pos, easing, duration, delay, cb)
end
function M.slide_in_top(node, to, easing, duration, delay, url)
slide_in(TOP, node, to.pos, easing, duration, delay, url)
function M.slide_in_top(node, to, easing, duration, delay, cb)
slide_in(TOP, node, to.pos, easing, duration, delay, cb)
end
function M.slide_in_bottom(node, to, easing, duration, delay, url)
slide_in(BOTTOM, node, to.pos, easing, duration, delay, url)
function M.slide_in_bottom(node, to, easing, duration, delay, cb)
slide_in(BOTTOM, node, to.pos, easing, duration, delay, cb)
end
local function slide_out(direction, node, from, easing, duration, delay, url)
local function slide_out(direction, node, from, easing, duration, delay, cb)
local to = from + direction
gui.set_position(node, from)
gui.animate(node, gui.PROP_POSITION, to, easing, duration, delay, function()
msg.post(url, monarch.TRANSITION.DONE)
end)
gui.animate(node, gui.PROP_POSITION, to, easing, duration, delay, cb)
end
function M.slide_out_left(node, from, easing, duration, delay, url)
slide_out(LEFT, node, from.pos, easing, duration, delay, url)
function M.slide_out_left(node, from, easing, duration, delay, cb)
slide_out(LEFT, node, from.pos, easing, duration, delay, cb)
end
function M.slide_out_right(node, from, easing, duration, delay, url)
slide_out(RIGHT, node, from.pos, easing, duration, delay, url)
function M.slide_out_right(node, from, easing, duration, delay, cb)
slide_out(RIGHT, node, from.pos, easing, duration, delay, cb)
end
function M.slide_out_top(node, from, easing, duration, delay, url)
slide_out(TOP, node, from.pos, easing, duration, delay, url)
function M.slide_out_top(node, from, easing, duration, delay, cb)
slide_out(TOP, node, from.pos, easing, duration, delay, cb)
end
function M.slide_out_bottom(node, from, easing, duration, delay, url)
slide_out(BOTTOM, node, from.pos, easing, duration, delay, url)
function M.slide_out_bottom(node, from, easing, duration, delay, cb)
slide_out(BOTTOM, node, from.pos, easing, duration, delay, cb)
end
function M.scale_in(node, to, easing, duration, delay, url)
function M.scale_in(node, to, easing, duration, delay, cb)
gui.set_scale(node, ZERO_SCALE)
gui.animate(node, gui.PROP_SCALE, to.scale, easing, duration, delay, function()
msg.post(url, monarch.TRANSITION.DONE)
end)
gui.animate(node, gui.PROP_SCALE, to.scale, easing, duration, delay, cb)
end
function M.scale_out(node, from, easing, duration, delay, url)
function M.scale_out(node, from, easing, duration, delay, cb)
gui.set_scale(node, from.scale)
gui.animate(node, gui.PROP_SCALE, ZERO_SCALE, easing, duration, delay, function()
msg.post(url, monarch.TRANSITION.DONE)
end)
gui.animate(node, gui.PROP_SCALE, ZERO_SCALE, easing, duration, delay, cb)
end
--- Create a transition for a node
@ -86,24 +78,45 @@ function M.create(node)
local instance = {}
local transitions = {
[monarch.TRANSITION.SHOW_IN] = M.instant,
[monarch.TRANSITION.SHOW_OUT] = M.instant,
[monarch.TRANSITION.BACK_IN] = M.instant,
[monarch.TRANSITION.BACK_OUT] = M.instant,
}
local transitions = {}
local initial_data = {}
initial_data.pos = gui.get_position(node)
initial_data.scale = gui.get_scale(node)
-- Forward on_message calls here
function instance.handle(message_id, message, sender)
if transitions[message_id] then
transitions[message_id](sender)
local function create_transition(fn, easing, duration, delay)
return {
fn = fn,
easing = easing,
duration = duration,
delay = delay,
in_progress = false,
urls = {},
}
end
local function start_transition(transition, url)
table.insert(transition.urls, url)
if not transition.in_progress then
transition.in_progress = true
transition.fn(node, initial_data, transition.easing, transition.duration, transition.delay or 0, function()
transition.in_progress = false
while #transition.urls > 0 do
local url = table.remove(transition.urls)
msg.post(url, monarch.TRANSITION.DONE)
end
end)
end
end
-- Forward on_message calls here
function instance.handle(message_id, message, sender)
local transition = transitions[message_id]
if transition then
start_transition(transition, sender)
end
end
-- Specify the transition function when this node is transitioned
-- to
-- @param fn Transition function (see slide_in_left and other above)
@ -111,39 +124,37 @@ function M.create(node)
-- @param duration Transition duration
-- @param delay Transition delay
function instance.show_in(fn, easing, duration, delay)
transitions[monarch.TRANSITION.SHOW_IN] = function(url)
fn(node, initial_data, easing, duration, delay or 0, url)
end
transitions[monarch.TRANSITION.SHOW_IN] = create_transition(fn, easing, duration, delay)
return instance
end
-- Specify the transition function when this node is transitioned
-- from when showing another screen
function instance.show_out(fn, easing, duration, delay)
transitions[monarch.TRANSITION.SHOW_OUT] = function(url)
fn(node, initial_data, easing, duration, delay or 0, url)
end
transitions[monarch.TRANSITION.SHOW_OUT] = create_transition(fn, easing, duration, delay)
return instance
end
--- Specify the transition function when this node is transitioned
-- to when navigating back in the screen stack
function instance.back_in(fn, easing, duration, delay)
transitions[monarch.TRANSITION.BACK_IN] = function(url)
fn(node, initial_data, easing, duration, delay or 0, url)
end
transitions[monarch.TRANSITION.BACK_IN] = create_transition(fn, easing, duration, delay)
return instance
end
--- Specify the transition function when this node is transitioned
-- from when navigating back in the screen stack
function instance.back_out(fn, easing, duration, delay)
transitions[monarch.TRANSITION.BACK_OUT] = function(url)
fn(node, initial_data, easing, duration, delay or 0, url)
end
transitions[monarch.TRANSITION.BACK_OUT] = create_transition(fn, easing, duration, delay)
return instance
end
-- set default transitions (instant)
instance.show_in(M.instant)
instance.show_out(M.instant)
instance.back_in(M.instant)
instance.back_out(M.instant)
return instance
end