Categories
Corona SDK Lua OOP

Send custom events with EventDispatcher for Corona SDK / Lua

In Corona SDK, event listeners can only be added to the global Runtime object or to display objects. In order to broadcast messages to event listeners, the event dispatcher is limited to either one or the other. This limitation places messaging in the two scopes instead of between objects that are supposed to be talking to each other. I’ve seen many examples with display objects that are created solely for the purpose of dispatching events. This just doesn’t feel right to me; so I’m releasing my EventDispatcher, perhaps other developers may find it useful too.

Those who came from the good old Flash 5 days may remember FLEM (FLash Event Model). It was the first listener event model for Flash and ActionScript, and was created by Branden Hall and further developed and maintained by me. I’ve adapted the basic event model mechanism found in ActionScript 2/3 to Lua. This EventDispatcher has a similar interface as the listener model in Corona SDK, with some extra features thrown in (such as optional extra parameters when dispatching events, and returning status).

EventDispatcher provides custom event broadcaster/listener mechanism to regular Lua objects, it works as regular Lua 5.1 / 5.2 code, in Corona SDK, and likely other Lua-based frameworks.

[ Update: See http://swfoo.com/?p=718 ]

Basic usage:

    local EvtD = require "EventDispatcher"

    local listener = {
        eventName = function(event)
            print(event.name)
        end
    }

    local broadcaster = EvtD()

    broadcaster:addEventListener( "eventName", listener )
    broadcaster:hasEventListener( "eventName", listener )
    broadcaster:dispatchEvent( { name="eventName" } )
    broadcaster:removeEventListener( "eventName", listener )

Sample code below demonstrates how it can be used:

local EvtD = require "EventDispatcher"

---------------------------------------------------------------------------

-- shared function for cowboys; shows the use of event.target
local function cowboyDraw(event, ...)
	if event.subject then
		print(event.target.name .." is ".. event.name .."ing a gun and shooting a ".. event.subject)
	else
		print(event.target.name .." is ".. event.name .."ing a gun")
	end
end

---------------------------------------------------------------------------

-- table listeners

local cowboy1 = {
	name = "Cowboy1",
	draw = cowboyDraw
}

local cowboy2 = {
	name = "Cowboy2",
	draw = cowboyDraw
}

---------------------------------------------------------------------------

-- listener as table; shows the use of event.source
local iPad = {
	turnOn = function(event, ...)
		print("iPad is turned on by ".. event.source.name .." (table)")
	end,

	turnOff = function(event, ...)
		print("iPad is turned off by ".. event.source.name .." (table)")
	end
}

-- listener as function
local function turnOniPad(event, ...)
	print("iPad is turned on by ".. event.source.name .." (function)")
end

---------------------------------------------------------------------------

-- basic artist draw function
local function artistDraw(event, ...)
	print(event.target.name .." is ".. event.name .."ing a picture")
end

---------------------------------------------------------------------------

-- artist1 is both a listener and a event dispatcher
local artist1 = EvtD{
	name = "Artist1",
	draw = artistDraw,

	-- responds to the 'rest' message, and sends a message to the iPad
	rest = function(event, ...)
		print(event.target.name .." is ".. event.name .."ing")

		-- event.target is artist1
		event.target:dispatchEvent( { name="turnOff" } )
	end
}

-- artist1 tells iPad to listen to the 'turnOff' message
artist1:addEventListener( "turnOff", iPad)

---------------------------------------------------------------------------

-- artist2 is both a listener and a event dispatcher
local artist2 = EvtD{
	name = "Artist2",

	draw = function(event, ...)
		-- event.target is artist2
		event.target:dispatchEvent( { name="turnOn" } )

		if event.subject then
			print(event.target.name .." is ".. event.name .."ing a ".. event.subject .." on the iPad")

			-- shows the use of extra arguments
			local func,pieces,name = ...
			func(pieces,name)
		else
			print(event.target.name .." is ".. event.name .."ing on the iPad")
		end
	end,

	rest = function(event, ...)
		event.target:dispatchEvent( { name="turnOff" } )
		print(event.target.name .." is ".. event.name .."ing")
	end
}

-- shows the use of table and function listeners
artist2:addEventListener( "turnOff", iPad)
artist2:addEventListener( "turnOn", turnOniPad)

---------------------------------------------------------------------------

-- mayor is a event dispatcher who tells others what to do
local mayor = EvtD()

-- mayor shows how much gold is collected
mayor.collectGold = function(nPieces, fromName)
	print("Mayor collected ".. nPieces .." pieces of gold from ".. fromName)
end

-- mayor tells these four people to pay attention to different messages
mayor:addEventListener( "draw", cowboy1 )
mayor:addEventListener( "draw", cowboy2 )
mayor:addEventListener( "draw", artist1 )
mayor:addEventListener( "draw", artist2 )
mayor:addEventListener( "rest", artist2 )

-- mayor sends the 'rest' message
mayor:dispatchEvent( { name="rest" } )
print("Rested 1")

-- mayor tells everyone to draw
mayor:dispatchEvent( { name="draw" } )

-- mayor tells these people to stop listening to the 'draw' message
mayor:removeEventListener( "draw", cowboy1 )
mayor:removeEventListener( "draw", artist1 )
print("Removed")

-- mayor tells artist1 to listen to the 'rest' message
mayor:addEventListener( "rest", artist1 )

-- mayor tells whoever is still listening to rest
mayor:dispatchEvent( { name="rest" } )
print("Rested 2")

-- mayor tells whoever is still listening to draw, with a subject and extra parameters
mayor:dispatchEvent( { name="draw", subject="bandit" }, mayor.collectGold, 42, "Dave" )

Here is the output from the code:

iPad is turned off by Artist2 (table)
Artist2 is resting
Rested 1
Cowboy1 is drawing a gun
Cowboy2 is drawing a gun
Artist1 is drawing a picture
iPad is turned on by Artist2 (function)
Artist2 is drawing on the iPad
Removed
iPad is turned off by Artist2 (table)
Artist2 is resting
Artist1 is resting
iPad is turned off by Artist1 (table)
Rested 2
Cowboy2 is drawing a gun and shooting a bandit
iPad is turned on by Artist2 (function)
Artist2 is drawing a bandit on the iPad
Mayor collected 42 pieces of gold from Dave

And here is the EventDispatcher module:

Update (Sept 12, 2014): Added aliases ‘on’ and ’emit’ for ‘addEventListener’ and ‘dispatchEvent’ for Coronium GS.


-- EventDispatcher.lua
--
-- Provides custom event broadcaster/listener mechanism to regular Lua objects.
--
-- Created by: Dave Yang / Quantumwave Interactive Inc.
--
-- http://qwmobile.com  |  http://swfoo.com/?p=632
--
-- Version: 1.1.4
--
-- Basic usage:
--		local EvtD = require "EventDispatcher"
--		local listener = {
--			eventName = function(event)
--				print(event.name)
--			end
--		}
--		local broadcaster = EvtD()
--		broadcaster:addEventListener( "eventName", listener ) or broadcaster:on( "eventName", listener )
--		broadcaster:hasEventListener( "eventName", listener )
--		broadcaster:dispatchEvent( { name="eventName" } ) or broadcaster:emit( { name="eventName" } )
--		broadcaster:removeEventListener( "eventName", listener )
---------------------------------------------------------------------------

local EventDispatcher = {}

function EventDispatcher:init(o)
	local o = o or {}
	o._listeners = {}
	self.__index = self
	return setmetatable(o, self)
end

---------------------------------------------------------------------------

-- Check if the event dispatcher has registered listener for the event eventName
-- Return boolean (true/false), and if found also return the index of listener object
function EventDispatcher:hasEventListener(eventName, listener)
	if eventName==nil or #eventName==0 or listener==nil then return false end

	local a = self._listeners
	if a==nil then return false end

	for i,o in next,a do
		if o~=nil and o.evt==eventName and o.obj==listener then
			return true, i
		end
	end
	return false
end

---------------------------------------------------------------------------

-- Add a listener for event eventName (a string).
-- Return addition status (true/false), position of listener is also returned if false; position=0 if failed
function EventDispatcher:addEventListener(eventName, listener)
	local found,pos = self:hasEventListener(eventName, listener)
	if found then return false,pos end

	local a = self._listeners
	if a==nil then return false,0 end

	a[#a+1] = { evt=eventName, obj=listener }
	return true
end

-- 'on' is an alias of 'addEventListener'
EventDispatcher.on = EventDispatcher.addEventListener

---------------------------------------------------------------------------

-- Dispatch event (a table, must have a 'name' key), with optional extra parameters.
-- Return dispatched status (true/false).
function EventDispatcher:dispatchEvent(event, ...)
	if event==nil or event.name==nil or type(event.name)~="string" or #event.name==0 then return false end

	local a = self._listeners
	if a==nil then return false end

	local dispatched = false
	for i,o in next,a do
		if o~=nil and o.obj~=nil and o.evt==event.name then
			event.target = o.obj
			event.source = self

			if type(o.obj)=="function" then
				o.obj(event, ...)
				dispatched = true
			elseif type(o.obj)=="table" then
				local f = o.obj[event.name]
				if f~= nil then
					f(event, ...)
					dispatched = true
				end
			end
		end
	end
	return dispatched
end

-- 'emit' is an alias of 'dispatchEvent'
EventDispatcher.emit = EventDispatcher.dispatchEvent

---------------------------------------------------------------------------

-- Remove listener with eventName event from the event dispatcher.
-- Return removal status (true/false).
function EventDispatcher:removeEventListener(eventName, listener)
	local found,pos = self:hasEventListener(eventName, listener)
	if found then
		table.remove(self._listeners, pos)
	end
	return found
end

---------------------------------------------------------------------------

-- create syntactic sugar to automatically call init()
setmetatable(EventDispatcher, { __call = function(_, ...) return EventDispatcher:init(...) end })

return EventDispatcher

EventDispatcher provides a broadcaster/listener event mechanism to regular Lua objects. Corona developers can write cleaner object-oriented messaging code that doesn’t rely on display objects or send messages from the global Runtime.

For the latest code, check out EventDispatcher at github.

Categories
Corona SDK Lua

Lua table remove by key

Lua has a table.remove() function that deletes an element from a table at the specified position, or the last element by default. However, it doesn’t work when the table is an associative array (e.g. {name=”swfoo”, domain=”swfoo.com”} ).

Below is a simple function that deletes a key-value pair from a table. One important feature to note is that instead of modifying the original table, it returns a new table.


-- Remove key k (and its value) from table t. Return a new (modified) table.
function table.removeKey(t, k)
	local i = 0
	local keys, values = {},{}
	for k,v in pairs(t) do
		i = i + 1
		keys[i] = k
		values[i] = v
	end

	while i>0 do
		if keys[i] == k then
			table.remove(keys, i)
			table.remove(values, i)
			break
		end
		i = i - 1
	end

	local a = {}
	for i = 1,#keys do
		a[keys[i]] = values[i]
	end

	return a
end

local t = {name="swfoo", domain="swfoo.com"}
t = table.removeKey(t, "domain")
Categories
Corona SDK iPhone

A mighty PNG

This innocent-looking PNG image makes a huge difference to iOS apps if you need to target retina display devices. At 640×1136 and named “Default-568h@2x.png”, this default launch image tells the app to run in full retina resolution on devices such as iPhone 5. What’s different with this image is the tiny file size of only 184 bytes. You’re welcome to grab it and use it.

Default-568h@2x.png

Categories
Corona SDK

Corona SDK Graphics 2.0 RGB Conversion

Update: A newer version that converts from and to between Graphics 1.0 and 2.0 can be found here.

Here is a simple tool I created to convert the old RGB values (from 0 to 255) to the Graphics 2.0 RGB values (0 to 1):

Enter RGB color values (0-255) separated by commas (e.g. 0, 128, 255):

Categories
Corona SDK Mobile & Devices

Develop and publish mobile apps for free

Corona Labs announced a completely free version of Corona SDK for developing and publishing mobile apps and games. This is great news for anyone who wants to create apps without paying a single cent. Students, indie developers and hobbyists would benefit most from this announcement.

What’s the catch? Well, there really isn’t one, except the new Starter version does everything the Pro version did, minus these three features: In-app purchases, analytics, and access to daily builds. Corona Labs doesn’t even require their splash screen to be displayed like some other “free” SDKs. A developer can use the Starter version to make apps and earn money by either charging up-front, using ads, or other means.

You’re probably thinking why would Corona Labs make this move. The reason is simple: Attract more developers to their platform (SDK and Corona Cloud). If developers feel the need for the three features and upcoming “pro” features, they would upgrade to the Pro version. Some pro features Corona Labs mentioned they’re working on include: new graphic features using OpenGL 2.0, plugins, device access through native code (maybe, but not full-blown like the Enterprise edition).

For existing developers with Indie subscription, they’re now automatically upgraded to the Pro version. For Pro subscribers, they get two extra months of subscription. For Enterprise subscribers, there’s no change.

There is, however, a price increase for the Pro version starting May 1st, 2013 from $349 to $599 per year. For pro subscribers, as long as their subscriptions are not expired by April 30th, they can upgrade twice at the old price of $349. For anyone who wants to take advantage of the lower price (for the next two years), subscribe by April 30th! More info at their pricing page and the conversation with their COO.

As a side note, Corona Cloud is an exciting server solution for any developer (not just Corona developers) who needs features such as User accounts & authentication, Leaderboards & Achievements, Multiplayer, Push Notifications, Social Connect, Cloud Sync, Chat, News, and Analytics. This simplifies greatly dealing with the server from a developer’s point-of-view.

For those who are interested, Walter Luh (CEO of Corona Labs), will be coming to FITC Toronto this month. His presentation is Building Native Apps: A Digital Canvas for Coders and Designers.

Off to making apps that people love…

Categories
Corona SDK Mobile & Devices Tutorials

Screen scaling for different devices

In multi-device development, one of the issues to deal with is displaying the same content in different screen resolutions. Corona SDK offers a simple solution by specifying the scaling mode in the config.lua file. The scaling modes include: none, letterbox, zoomEven, zoomStretch. Sometimes it’s easier to understand by seeing the visual difference than reading the docs.

Below are the 4 modes displayed in four different devices (starting top-left clockwise: Galaxy Tab, iPad, iPhone & iPhone 4). The background photo is loaded at the Galaxy Tab screen resolution (1024×600).

To view the original full-size images, click on the images to view at Flickr (then choose Actions -> View all sizes), or use the links at the bottom of this post.

 

None

Screen Scaling None

 

 

Letterbox

Screen Scaling Letterbox

 

ZoomEven

Screen Scaling ZoomEven

 

ZoomStretch

Screen Scaling ZoomStretch

 

The source files used to create the display can be downloaded here. The four full size images are here: None, Letterbox, ZoomEven, ZoomStretch.

Hope this is useful.

 

Categories
Corona SDK iPhone Mobile & Devices

Another 3rd-party iPhone SDK: Corona

Corona: Another 3rd-party iPhone development SDK is now open to developers for pre-beta testing.

One thing that sets this SDK apart: The engineers are “former Adobe mobile software veterans” who worked on the mobile Flash ecosystem.

And no, the programming language is not ActionScript or JavaScript, but is Lua – a simple scripting language that should be easy for ActionScript developers to pickup. One thing uncertain to me is whether Corona is generating native Objective-C code from Lua code, or simply interprets the Lua code at runtime (I certainly hope it’s not the latter).

At the moment, there is no public distribution build. The only way to test a project is by using the Corona Simulator that comes with the SDK. Corona is still at its early stage, and the final product is targeting end of Q3 2009.

As Apple has been known to reject apps for unknown reasons, many PhoneGap developers have encountered the most-feared notice that their app is rejected. One possible reason is PhoneGap relies on the Safari JavaScript engine, and JavaScript code is interpreted at runtime, which is not allowed by the Apple SDK agreement.

For me, there are some important questions:

  1. Does Corona generate native Objective-C code or interprets Lua code at runtime?
  2. How much does the final distribution license cost?
  3. How stable is the final code?
  4. Is memory management handled efficiently?

Nevertheless, it is good to see more 3rd-party tools and SDKs for iPhone development.

Update: Today (June 24), this article talks about Corona. “When the developer has his Lua code the way he wants it, he submits it to the Ansca Web site, where it will be compiled into an iPhone application ready to submit to Apple’s App Store.”

Even though it seems to simplify the workflow, this may not be ideal because:

  1. the complete project and code is submitted to Ansca
  2. sounds like the developer does not get any Objective-C code for tweaking
  3. developer is locked into Ansca’s terms for future deployment

Hopefully Ansca would address these concerns in their FAQ or forum soon.