Showing posts with label adobe air. Show all posts
Showing posts with label adobe air. Show all posts

Monday, October 11, 2010

Adobe AIR Scrollbars in Mac OSX

If you are just getting started with Adobe AIR development, it might come as a bit of a surprise to see the scrollbar implementation on OSX. They're not even close to the native WebKit implementation. Check it out:

Fugly ... I don't know where these scrollbars came from. I wonder if they're the default Flash styled scrollbars. Or if they're using the default windows scrollbars and didn't bother making specific scrollbars for mac AIR users.

Regardless, with some WebKit specific CSS declarations, you can actually manage to style the scrollbars:
::-webkit-scrollbar-button:horizontal {
background-image:;
background-repeat: repeat;
}
::-webkit-scrollbar-button:horizontal:decrement {
background-image: url(../images/chrome/scroll_arrow-left.png), url(../images/chrome/scroll_arrowbox-horizontal.png);
background-repeat: no-repeat, repeat;
background-position: 2px 2px, 0 0;
}
::-webkit-scrollbar-button:horizontal:increment {
background-image: url(../images/chrome/scroll_arrow-right.png), url(../images/chrome/scroll_arrowbox-horizontal.png);
background-repeat: no-repeat, repeat;
background-position: 2px 2px, 0 0;
}
::-webkit-scrollbar-button:vertical:decrement {
background-image: url(../images/chrome/scroll_arrow-up.png), url(../images/chrome/scroll_arrowbox-vertical.png);
background-repeat: no-repeat, repeat;
background-position: 2px 3px, 0 0;
}
::-webkit-scrollbar-button:vertical:increment {
background-image: url(../images/chrome/scroll_arrow-down.png), url(../images/chrome/scroll_arrowbox-vertical.png);
background-repeat: no-repeat, repeat;
background-position: 2px 3px, 0, 0;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track-piece:horizontal {
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F6F6F6), to(#CBCBCB), color-stop(0,#CBCBCB), color-stop(.08,#D2D2D2), color-stop(.15,#DBDBDB), color-stop(.23,#E2E2E2), color-stop(.3,#E9E9E9), color-stop(.38,#EFEFEF), color-stop(.45,#F4F4F4), color-stop(.53,#F8F8F8), color-stop(.6,#FBFBFB), color-stop(.68,#FCFCFC), color-stop(.76,#FDFDFD), color-stop(.85,#FAFAFA), color-stop(.93,#F6F6F6));
}
::-webkit-scrollbar-track-piece:vertical {
background: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#F6F6F6), to(#CBCBCB), color-stop(.93,#CBCBCB), color-stop(.85,#D2D2D2), color-stop(.76,#DBDBDB), color-stop(.68,#E2E2E2), color-stop(.6,#E9E9E9), color-stop(.53,#EFEFEF), color-stop(.45,#F4F4F4), color-stop(.38,#F8F8F8), color-stop(.3,#FBFBFB), color-stop(.23,#FCFCFC), color-stop(.15,#FDFDFD), color-stop(.08,#FAFAFA), color-stop(0,#F6F6F6));
}
::-webkit-scrollbar-button:end:decrement,::-webkit-scrollbar-button:end:increment {
height: 15px;
display: block;
background-color: #CCC;
}
::-webkit-scrollbar-thumb:vertical {
background: -webkit-gradient(linear, 100% 0, 0 0, from(#2E4DBE), to(#5D5D5D), color-stop(0,#2E4DBE),color-stop(.08,#9CB5E4),color-stop(.15,#AFC6EA),color-stop(.23,#A8C0E9),color-stop(.3,#A2BEE9),color-stop(.38,#729CE4),color-stop(.45,#83AAED),color-stop(.53,#95BBFA),color-stop(.6,#A4C9FD),color-stop(.68,#B4D9FD),color-stop(.76,#BDE3FE),color-stop(.85,#B1D6FD),color-stop(.93,#5D5D5D));
border: 1px solid #666;
height: 50px;
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-thumb:horizontal {
height: 50px;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#2E4DBE), to(#5D5D5D), color-stop(0,#2E4DBE),color-stop(.08,#9CB5E4),color-stop(.15,#AFC6EA),color-stop(.23,#A8C0E9),color-stop(.3,#A2BEE9),color-stop(.38,#729CE4),color-stop(.45,#83AAED),color-stop(.53,#95BBFA),color-stop(.6,#A4C9FD),color-stop(.68,#B4D9FD),color-stop(.76,#BDE3FE),color-stop(.85,#B1D6FD),color-stop(.93,#5D5D5D));
border: 1px solid #666;
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-corner {
background: #EEE;
}

Here's the end result:

Not 100% perfect, but they're definitely closer to a native Mac OSX scrollbar. Might be an idea to prefix these css declarations with some kind of mac OSX identifier so Linux/Windows users don't get Mac specific scrollbars in the application.

Thanks to Vasken at Eye-Fi for helping me figure out some of the gradations and background images.

Some useful links:
http://webkit.org/blog/363/styling-scrollbars/
http://beautifulpixels.com/goodies/create-custom-webkit-scrollbar/
http://almaer.com/blog/creating-custom-scrollbars-with-css-how-css-isnt-great-for-every-task

Monday, August 30, 2010

File Promises and dojo drag and drop

When Adobe pushed AIR 2.0 out via auto update, one of the first new API features that I wanted to try to take advantage of is the File Promises API. Armed with Christian Cantrell's article, I went about integrating File Promises into my application.

Drag and drop isn't a new interaction paradigm in the application I work on. Users have always been able to drag and drop items in the application thanks to dojo's drag and drop APIs. With AIR 2.0 and File Promises though, I wanted to give the user the ability to drag files internally in the application and also out of the application and onto their desktop. To allow both internal and external drag and drop operations, I needed to override the default dojo.dnd.Avatar. Overriding the default Avatar for dojo dnd operations is really easy - the constructor for your dnd source should include the following line (basically, you need some way of telling the dnd manager to use a different function when creating the avatar):
dojo.dnd.manager().makeAvatar = dojo.hitch(this, "makeAvatar");

You'll need a makeAvatar function which returns your custom Avatar:
makeAvatar: function() {
return new my.widget.FileListAvatar(dojo.dnd.manager());
},

Once you've told the dojo dnd manager to use your custom dnd avatar, you'll need to figure out when the avatar in question is being dragged close to the edge of the application. See the _handleMouseMove function below:

dojo.declare(
"my.widget.FileListAvatar",
[dojo.dnd.Avatar],
{
_mouseOverConnects: [],

construct: function() {
// override and cancel the autoScroll function to prevent whitespace from appearing
// in the application when the user is dragging files near the border.
dojo.dnd.autoScroll = function(){};
this._mouseOverConnects.push(dojo.connect(this.manager, "onMouseMove", this, "_handleMouseMove"));
}

_handleMouseMove: function() {
var pos, viewport = dojo.window.getBox();
if(this.node) {
pos = dojo.position(this.node);
if(viewport.w <= pos.x + pos.w || pos.x <= 0
|| viewport.h <= pos.y + pos.h || pos.y + pos.h <= 0) {
// at this stage we know that the user is dragging the avatar close to the application's boundaries.
// time to make a switch from an internal dnd operation to an external dnd.
this. _handleExternalDnd();
}
}

The _handleExternalDnd is the point in the application where we start using the AIR 2.0 file promises API. In the following code, all the items in my dnd source have a store and item property which contain information on the url and file name. The url property from each dnd item is passed to a new URLFilePromise object.
_handleExternalDnd: function() {
var f, url, cb = new air.Clipboard(), promises = [], vp = dojo.window.getBox(), isDraggedOut = false;
for(var j = 0, k = this.manager.nodes.length; j < k; j++) {
f = this.manager.source.getItem(this.manager.nodes[j].id).data;
url = f.store.getValue(f.item, "Url");
if(url !== "") {
var fp = new air.URLFilePromise();
fp.request = new air.URLRequest(url);
fp.relativePath = f.store.getValue(f.item, "FileName");
promises.push(fp);
}
}
cb.setData(air.ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises);
// the next line causes the switch from internal dnd to external dnd
air.NativeDragManager.doDrag(window.htmlLoader, cb);
air.NativeApplication.nativeApplication.activeWindow.stage.addEventListener(window.runtime.flash.events.NativeDragEvent.NATIVE_DRAG_UPDATE, function(evt) {
if(!isDraggedOut && evt.localX > vp.w) {
isDraggedOut = true;
} else if (isDraggedOut && evt.localX < vp.w) {
// TODO - this is the case where the file has been dragged out of the application and the user is dragging the file back in.
// would be nice at this stage to revert to revert back to an internal dnd operation.
}
});
dojo.style(this.node, "display", "none");
},

I've left some commented code in there which might give you hints of what I'd like to do when the user drags files back into the application after initially dragging them out. I haven't figured out how to cancel a native drag and drop operation just yet - if I do, I'll be sure to update this blog post.

That's pretty much it. Once the user drags files out of the application and into their native file system browser, the urls included in the URLFilePromise object will be downloaded. Check out some of the events you can connect to if you want to give your users a progress indicator of where their files are in terms of being downloaded. One point to note when using File Promises - users are only allowed to drag files from an AIR application into the default file browser. For example, the user cannot drag files directly from an Adobe AIR application into iPhoto or Lightroom. They need to drag the files into Finder first, wait for the files to be downloaded and then drag them into the file management application of their choice.

Looking to the future, it looks like this type of functionality will start to become available natively in the browser.

Some useful links which might help with further reading:

Getting started with dojo dnd
dojocampus' dnd articles
Advanced dojo dnd tutorial
CSS Ninja article on drag and drop
CSS Ninja article how gmail's dnd works

Thursday, March 4, 2010

Javascript testing in Adobe AIR

I'm in the process of migrating a web application to an Adobe AIR application and I finally got around to trying to migrate some of my JavaScript unit tests to run in AIR. I'd previously written some doh tests and wanted to get them up and running in AIR. Turns out it was relatively simple. All you need to do is:

In your apache configuration file, add an entry to point to the base of your AIR project:
<VirtualHost *>
ServerName air-testing.my.domain
DocumentRoot /path/to/my/trunk/htdocs
</VirtualHost>

(The htdocs directory here should have a subdirectory called dojo which has the 4 dojo subdirectories (dojo, dijit, dojox, util)

Then, in the .html file where you previously had a <meta> redirect (lets call this runTests.html - it probably looked something like this)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en-US" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Unit Tests</title>
<meta http-equiv="REFRESH" content="0; url=../../dojo/util/doh/runner.html?testModule=my.module.testAll&registerModulePath=mydomain,../../mydomain">
</head>
<body>
Redirecting to D.O.H runner.
</body>
</html>

... change this file to look like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en-US" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Unit Tests</title>
<script type="text/javascript">
var addOnLoad = function() {
window.location = "http://air-testing.my.domain/dojo/util/doh/runner.html?testModule=my.module.testAll&registerModulePath=mydomain,../../mydomain"";
}
</script>
</head>
<body onload="addOnLoad();">
Redirecting to D.O.H runner.
</body>
</html>

I found it useful to have a testing application descriptor xml file in the same directory as my runTests.html test file:
<?xml version="1.0" encoding="utf-8" ?>
<application xmlns="http://ns.adobe.com/air/application/1.5">
<id>tests</id>
<filename>runTests</filename>
<name>
<text xml:lang="en_US">Run Tests</text>
</name>
<version>0.0001</version>
<description>
<text xml:lang="en_US">Run Tests</text>
</description>
<initialWindow>
<title>TESTS</title>
<content>runTests.html</content>
<systemChrome>standard</systemChrome>
<transparent>false</transparent>
<visible>true</visible>
<width>1250</width>
<height>900</height>
<x>20</x>
<y>20</y>
<minimizable>true</minimizable>
<maximizable>true</maximizable>
<minSize>850 636</minSize>
<resizable>true</resizable>
</initialWindow>
</application>

Running adl on this new test application descriptor file should kick off your doh unit tests.