Archive for January, 2009
Naive Mouse Gestures Implementation in ActionScript (Twitterified)
In the Twitterified Client, users can quickly perform some operations using mouse gestures.
When I implemented this, I googled for ActionScript mouse gesture classes but only found one and it was a commercial package.
I am sure that there are many ways to implement gestures, using rule engines, neural networks, etc.
But, curious and optimistic as usual, I decided to empirically build my own. Could it be done?
After some fumbling and several dead-ends, I came up with this solution which works as it should and that can easily be adapted to your own needs.
I believe that all the relevant source code is here; let me know if I’m wrong and do not hesitate to share your improvements!
Let’s begin with a few instance variables…
private var drawingCanvas:Canvas = new Canvas(); private var ctrlKeyIsDown:Boolean = false; private var lastPoint:Point; private var lastDrawPoint:Point; private var gesturePath:Array; |
drawingCanvas is going to be our, er, drawing canvas. We will use it to give the user visual feedback when doing a mouse gesture.
Note that lastDrawPoint will contain the coordinates of the last rendered point, whereas lastPoint will be used to track direction changes in the mouse’s course.
Similarly, gesturePath will only be updated when a significant update is detected.
public function main_init():void { ... systemManager.addChild(drawingCanvas); systemManager.addEventListener(KeyboardEvent.KEY_DOWN, main_onKeyUpDown); systemManager.addEventListener(KeyboardEvent.KEY_UP, main_onKeyUpDown); systemManager.addEventListener(MouseEvent.MOUSE_MOVE, main_onMouseMove); ... } |
We are adding drawingCanvas to the top of our user interface, but not giving it a size, yet. We will wait for the user to actually initiate a mouse gesture to, then, detect our current real estate.
I am asking Flex to keep me updated of any keyboard change as I wish to use the [Ctrl] key as a hint that we wish to perform a mouse gesture. Obviously, you can go down a very different path.
private function main_onKeyUpDown(e:KeyboardEvent):void { ctrlKeyIsDown = (e.ctrlKey); if(ctrlKeyIsDown) { drawingCanvas.x = systemManager.stage.x; drawingCanvas.y = systemManager.stage.y; drawingCanvas.width = systemManager.stage.width; drawingCanvas.height = systemManager.stage.height; drawingCanvas.graphics.lineStyle(2, 0x6EBAE5, 1); lastPoint = new Point(systemManager.stage.mouseX, systemManager.stage.mouseY); lastDrawPoint = lastPoint; gesturePath = new Array(); gesturePath.push(new PathBit(lastPoint, lastPoint)); } else { drawingCanvas.graphics.clear(); // Gesture main_encodeGesture(); } } |
Two possibilities: [Ctrl] is depressed and we initialize visual feedback and store the original mouse location, or it is up and we clear the feedback information and encode the gesture.
Note that this could be improved to not be disturbed by, say, the user depressing another key!
private function main_onMouseMove(e:MouseEvent):void { if(!ctrlKeyIsDown) return; var x:int = systemManager.stage.mouseX; var y:int = systemManager.stage.mouseY; drawingCanvas.graphics.moveTo(x, y); drawingCanvas.graphics.lineTo(lastDrawPoint.x, lastDrawPoint.y); var newPoint:Point = new Point(x, y); var pathBit:PathBit = new PathBit(lastPoint, newPoint); // > 5% of stage? if(Math.abs(pathBit.horizontal) * 20 > systemManager.stage.width || Math.abs(pathBit.vertical) * 20 > systemManager.stage.height) { gesturePath.push(pathBit); lastPoint = newPoint; } lastDrawPoint = newPoint; } |
If [Ctrl] is depressed, store the mouse location. If significant motion has taken place, draw a new line and store the relevant waypoint.
What does a PathBit object look like? This is fairly simple: based on the latest mouse move angle, we determine a very rough direction.
package com.voilaweb.tfd { import flash.geom.Point; public class PathBit { static public const UP:int = 0x01; static public const DOWN:int = 0x02; static public const RIGHT:int = 0x04; static public const LEFT:int = 0x08; static public const U:String = "U"; static public const D:String = "D"; static public const L:String = "L"; static public const R:String = "R"; static public const UL:String = "7"; static public const UR:String = "9"; static public const DL:String = "1"; static public const DR:String = "3"; public var point:Point = new Point(); public var horizontal:int; public var vertical:int; public var direction:int; function PathBit(point1:Point, point2:Point) { horizontal = point2.x - point1.x; vertical = point2.y - point1.y; point.x = point2.x; point.y = point2.y; direction = 0; if(0 == horizontal && 0 == vertical) return; // Compute angle, in degrees: horizontal == new x, vertical == new y var angle:Number = Math.atan2(horizontal, -vertical) * 180 / Math.PI; if(angle < 0) angle = 360 + angle; if(angle >= 337.5 || angle < 22.5) { direction = UP; } else if(angle >= 22.5 && angle < 67.5) { direction = UP | RIGHT; } else if(angle >= 66.5 && angle < 112.5) { direction = RIGHT; } else if(angle >= 112.5 && angle < 157.5) { direction = RIGHT | DOWN; } else if(angle >= 157.5 && angle < 202.5) { direction = DOWN; } else if(angle >= 202.5 && angle < 247.5) { direction = DOWN | LEFT; } else if(angle >= 247.5 && angle < 292.5) { direction = LEFT; } else if(angle >= 292.5 && angle < 337.5) { direction = LEFT | UP; } } } } |
You may have noticed that our constant values are also very basic; I was not lying when talking about a naive implementation.
private function main_encodeGesture():void { var container:Container = null; switch(showingTab) { case FRIENDS_TL: container = friends_timeline; break; case USER_TL: container = user_timeline; break; case PUBLIC_TL: container = public_timeline; break; } if(!container) return; // Analyze path var children:Array = container.getChildren(); var statusRow:UIComponent; var multiComponent:Boolean = false; var curStatus:StatusRow; var lastStatus:StatusRow = null; var pathBit:PathBit; var overallPathString:String = ''; for each(pathBit in gesturePath) { curStatus = null; for each(statusRow in children) { if(statusRow is StatusRow) { if(StatusRow(statusRow).isBounding(pathBit.point)) { curStatus = statusRow as StatusRow; break; } } } if(lastStatus && curStatus != lastStatus) multiComponent = true; lastStatus = curStatus; if(pathBit.direction & PathBit.DOWN) { if(pathBit.direction & PathBit.LEFT) overallPathString += PathBit.DL; else if(pathBit.direction & PathBit.RIGHT) overallPathString += PathBit.DR; else overallPathString += PathBit.D; } else if(pathBit.direction & PathBit.UP) { if(pathBit.direction & PathBit.LEFT) overallPathString += PathBit.UL; else if(pathBit.direction & PathBit.RIGHT) overallPathString += PathBit.UR; else overallPathString += PathBit.U; } else if(pathBit.direction & PathBit.LEFT) overallPathString += PathBit.L; else if(pathBit.direction & PathBit.RIGHT) overallPathString += PathBit.R; } overallPathString = main_cleanupPath(overallPathString); main_parseGesture(multiComponent, overallPathString, lastStatus, container); } |
The switch statement is used to figure out which container is currently displayed. Because this container contains a list of Tweets, I can then check whether the mouse gesture entirely took place within the confines of a single Tweet, or multiple Tweets. This allows me, for instance, to mark a Tweet as ‘read’ if the mouse did not wander outside that Tweet’s display area.
I then build a path string, called ‘overallPathString‘, based on the content of gesturePath.
At this point, you may be wondering about the implementation of isBounding(). Here it is:
public class StatusRow extends WindowShade { ... public function isBounding(point:Point):Boolean { var pt:Point = this.globalToLocal(point); return (pt.x >=0 && pt.y >= 0 && pt.x <= this.width && pt.y <= this.height); } ... } |
A simple helper.
private function main_cleanupPath(path:String):String { var i:int, l:int = path.length; var c:String, prevC:String = null, ret:String = ''; for (i=0; i<l; i++) { c = path.charAt(i); if(prevC) { if(c != prevC) ret += c; } else { ret = c; } prevC = c; } return ret; } |
As you can see, all this method does is collapse redundant information. Actually, without it, we would never be able to identify any gesture.
private function main_parseGesture(multi:Boolean, op:String, status:StatusRow,container:Container):void { if(!multi) { // Single-status gesture! if(PathBit.L == op) // L main_toggleReadFlag(status, true); else if(PathBit.R == op) // R main_toggleReadFlag(status, false); } else { if("13" == op) // DL, DR main_toggleReadFlagForAll(container, true); else if("31" == op) // DR, DL main_toggleReadFlagForAll(container, false); } } |
I am sure that this could be rewritten to be more cleanly; potentially with no hard-coding at all. But it does the trick:
- If the gesture happened within the confines of a single Tweet, and is a single “right-to-left” move, then we mark this Tweet as ‘read’.
- If the gesture happened across multiple Tweets and goes “top-right-to-bottom-left-to-bottom-right” then mark all Tweets as read.
Well, there you have it. At least it’s easy to maintain
If you enjoyed this post, make sure you subscribe to my RSS feed!
A quick-fix for the Tweetback spam issue
I do not get it. I am a big fan of Smashing Magazine and I think that it was very nice of @jdevalk to write a plug-in that gives us full control over “Tweetbacks”. See this blog entry.
Where Smashing Magazine and the author lose me, though, is that I have not seen any reaction, either on the magazine’s web site, or the author’s blog, regarding the issue that many blogs who installed the plug-in seem to now be suffering: they are being inundated with thousands of spammy Tweetbacks, most of them containing text like “This is a test”, presaging a potentially much more virulent attack when the spammers get their tool working.
In fact, it may not be spam, but simply some tests gone wrong, but whatever the reason it caused grief to many blog owners — including me.
After de-activating the plug-in, here is the SQL command you can use to get rid of these Tweetbacks:
delete from wp_comments where comment_author_url like 'http://twitter.com/%/statuses/%'; |
If you do not have access to your database, contact me, I may end up writing a short plug-in that will wrap this query.
Again, I really like Smashing Magazine and that is why I am very surprised by their silence on this issue.
If you enjoyed this post, make sure you subscribe to my RSS feed!
Chris’ Weekly High-Protein Tweets
If you are drowning in Twitter updates or simply missed a few of my Tweets, here is a list of what I consider worth reading this week.
By that, I mean that less interesting, personal and topical tweets are not in this list. This list will be published every week, save the occasional attack of laziness.
View
View
View
View
View
View
View
If you enjoyed this post, make sure you subscribe to my RSS feed!
ExtPHP now on Github
About 10 months ago, I released this tool, allowing developers to handle ExtJS like “managed” code in PHP: ExtPHP.
Shortly thereafter, Jack Slocum changed ExtJS’ licensing model and it all became very muddy but my main question was: “As a commercial user, what would the status of the extensions be?”
It seems that it’s OK to create an extension and not GPL it as long as it doesn’t contain any ExtJS original code. The claim is that most extensions now fall under that category. Of course, there is still the risk of an extension still containing ExtJS code — hint: that’s very likely — and the company who paid for ExtJS commercial licensing is now burdened with…what? De-facto GPL code? Or does the code that was reused considered “commercially licensed” as well? Even though it is part of an open-source extension?
Ten months later, I am not getting a sense that things were clarified well enough. Look at this topic on ExtJS’ forums. It looks like, after September, everybody gave up.
So, what now?
Well, if anyone could point me to a comprehensive answer regarding this issue, that will sure help me decide whether to revisit my decision to give up on ExtJS from a commercial standpoint.
In the meantime, ExtPHP is now available at Github.
If you enjoyed this post, make sure you subscribe to my RSS feed!
Chris’ Weekly High-Protein Tweets
If you are drowning in Twitter updates or simply missed a few of my Tweets, here is a list of what I consider worth reading this week.
By that, I mean that less interesting, personal and topical tweets are not in this list. This list will be published every week, save the occasional attack of laziness.
View
View
View
View
View
View
View
View
If you enjoyed this post, make sure you subscribe to my RSS feed!
What would you like to read?
With the new year, this is your chance to shape the future of this space:
What do you think this blog’s focus should be?
- Technical issues?
- Web design?
- Building communities?
- What else..?
Your comments will be all be read and everybody will get a reply, so feel free to share!
If you enjoyed this post, make sure you subscribe to my RSS feed!
Six Resources To Study And Master Git Source Control
It’s no secret that Git, as a source code control tool, has become quite popular in 2008, in part boosted by the success of the socially oriented github.com Here is a list of six resources that will help you get more comfy with Git, whatever your current knowledge of the tool. Let me know if you think that, after reading these six documents, you can still point to a few stones left unturned.
3 Reasons To Switch To Git From Subversion
A to-the-point document, created by Mark McBride, that covers several scenarios and for each scenario, shows how Git could make your life easier if you are not using it yet.
My Git Cheatsheet
A concise guide for the absolute beginner who wants to hit the ground running. Go through the exercises and you will end up with a pretty good picture of what Git can do.
Everyday GIT With 20 Commands Or So
A quick cheat sheet: start with the section corresponding to your role (individual developer, contributor, integrator…) You will not learn a lot about Git’s philosophy, but you will certainly get the job done.
Git Magic
Ben Lynn wrote a whole book covering not just cloning and branching but also some precious recipes: What to do when something goes wrong? What if my commit is too big? Remote access magic, history digging…Even Git’s shortcomings and their workarounds are covered.
Git from the bottom up
John Wiegley makes a convincing case that approaching Git from a 10,000 ft level is not the best way to understand the tool’s philosophy. Rather, building your knowledge of the tool based precisely on the very concepts that it uses make it much easier to get an overall grasp of Git’s world.
Using Git to Maintain Your Website
Using source code control has always been an effective tool to maintain a web site, especially avoiding these “Uh-oh I clobbered something I shouldn’t have” moments. But Git is particularly well suited to this task, due to its self-sufficient nature. Daniel Miessler shows us how to do just that using Git hooks.
If you enjoyed this post, make sure you subscribe to my RSS feed!
A very geeky Holidays break
Was your break as geeky as mine? Come on, admit it: you’ve done at least one incredibly unsexy thing in the last couple weeks. I know I have. Well, in fact, I had to take a four weeks-long break and it shows in the number of silly things I’ve played with.
In no particular order:
- I improved this blog’s look — well, I like to think that I have:

- I finally created a personalized Twitter page:

- I also created a Twitter page for Twitterified:

- I created an icon set call “More Blaqua“:

- I added a drawer to the Twitterified client – you will see why sometime in January, hopefully!

(Oh, and I finally mastered transparency in Flex, too! Yay)
- I started separating nextBBS v2’s components so that the framework can be used on its own and the message board part is now a module.
It is the first MVC PHP framework that seamlessly support plug-ins.
- I added to nextBBS v2 a limited amount of compatibility with Wordpress plug-ins.
I re-read Getting Things Done by David Allen and made a new year resolution to stick with the program, this time.
So far my Inbox is empty and my tasks list still is a manageable size…
I have installed Medialink on my iMac and use it to stream Divx movies to my PS3. Works flawlessly.
I have also installed PlayOn! in Parallels to stream Netflix. I wish there was an equivalent program for OS X. Well, I “kind of” wish because Netflix’s streaming choice is not that exciting. Not to mention that Netflix innovates by being, to my knowledge, the first company to proudly blog about letting go 50 employees.
- I setup an old P4 with Nexentra. The project bills itself as “The land of free and open source distribution combining OpenSolaris kernel with Ubuntu userland.”
In fact I installed it because I wanted to create a ZFS array. Unfortunately the clunky old PC is way too noisy. Fortunately I realized that a read-write implementation of ZFS for Leopard is available at Mac OS Forge.
- I cancelled XM Radio. They had been annoying me for quite a while, inserting their stupid advertisements in talk radio channels, and now that they merged with Sirius they got rid of some channels I happened to like so, good riddance XM, welcome free radios on my iPhone! — and ironically but quite logically I have better reception in tunnels.
If you enjoyed this post, make sure you subscribe to my RSS feed!
Flex: A hack to give instant focus to a form field
I was working on a “quick add” tool for Toodledo — which I am liking more and more — and I assumed that my biggest challenge would be wiring a hotkey to my application — a can of worms in its own right. My mini-application’s main requirement is to be fast and not get in the user’s way. Part of this requirement is that when the “Add” window is displayed, the “Task description” field should be automatically selected, an inviting cursor happily blinking. Easier said than done!
Flex will let me display the native window and select the description field using setFocus() but no cursor for me. I googled the issue and found that when a Flex application runs in a browser, the solution is to give the application itself focus using Javascript.
But I’m not running in a browser!
And now, my silly workaround: since displaying an other panel container — such as an alert message — and dismissing it will return proper focus to my original field, I will simply “display” an invisible container and immediately get rid of it.
There is no guarantee that, on the odd instance, a race condition will not occur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.voilaweb.addtdtask { import flash.events.TimerEvent; public class Main { import mx.core.Application; import mx.containers.TitleWindow; import flash.display.DisplayObject; import flash.utils.Timer; import flash.events.TimerEvent; import mx.managers.PopUpManager; private var _tw:TitleWindow; public function Main() { Application.application.task_description.setFocus(); Application.application.task_description.drawFocus(true); _tw = new TitleWindow(); _tw.width = _tw.height = 0; var t:Timer = new Timer(100, 1); t.addEventListener(TimerEvent.TIMER, showCursor); t.start(); mx.managers.PopUpManager.addPopUp(_tw, Application.application as DisplayObject, true); } public function showCursor(event:TimerEvent):void { mx.managers.PopUpManager.removePopUp(_tw); } } } |
If you enjoyed this post, make sure you subscribe to my RSS feed!












