Application walkthrough - xkp/Doc GitHub Wiki
Nothing like looking at code to figure out how it works, so here we'll go through a semi-complex application. The application browses images of a friend-o-mine paintings. It includes topics such as animation, on-demand resource loading and UI. You can see the app running here.
This app is made out of barely 300 lines of code (including the xml). I don't see any platform where this can be accomplished on that few lines. Keep in mind that the resulting code runs on any browser that supports HTML5 and has about 1500 LOCs.
As you know, xs applications are described in xml (or json) files, then the code fills in the blanks. So here is the full xml for the app:
<includes>
<include src="anims.xs" def="anims.xml"/>
</includes>
<application source="images.xs" width="1000" height="640" title="">
<resources>
<package id="ui_package">
<resource src="images/arrow.png"/>
<resource src="images/arrow2.png"/>
<resource src="images/progress.empty.png"/>
<resource src="images/progress.full.png"/>
</package>
<package id="page1">
<resource src="images/n739882607_704246_8746.jpg"/>
<resource src="images/n739882607_704109_46.jpg"/>
<resource src="images/n739882607_704119_5840.jpg"/>
</package>
<package id="page2">
<resource src="images/n739882607_794182_2354.jpg"/>
<resource src="images/n739882607_704116_4921.jpg"/>
<resource src="images/n739882607_704120_6153.jpg"/>
</package>
<package id="page3">
<resource src="images/n739882607_704118_5476.jpg"/>
<resource src="images/n739882607_704247_9725.jpg"/>
<resource src="images/n739882607_704117_5209.jpg"/>
</package>
</resources>
<!--An array describing every page-->
<property id="pages" type="array">
<object type="page">
<property name="image_location" type="array">
<object x="300" y="200" w="350" h="247"/>
<object x="700" y="50" w="200" h="516"/>
<object x="0" y="0" w="250" h="337"/>
</property>
<property name="package" value="page1"/>
</object>
<object type="page">
<property name="image_location" type="array">
<object x="200" y="300" w="600" h="274"/>
<object x="20" y="20" w="280" h="273"/>
<object x="600" y="40" w="200" h="200"/>
</property>
<property name="package" value="page2"/>
</object>
<object type="page">
<property name="image_location" type="array">
<object x="700" y="0" w="300" h="221"/>
<object x="300" y="50" w="400" h="346"/>
<object x="0" y="200" w="300" h="300"/>
</property>
<property name="package" value="page3"/>
</object>
</property>
<!--The sequence that loads a particular page-->
<sequence id="load_package">
<every t="0.3">
<run method="percentage"/>
</every>
</sequence>
<!--Three images at the time-->
<img id="img1" x="0" y="0" width="100" height="100"/>
<img id="img2" x="120" y="140" width="200" height="200"/>
<img id="img3" x="300" y="300" width="200" height="200"/>
<!--And their frames, made by individual lines-->
<line id="ln1" x1 ="20" y1="40" x2 ="40" y2="140" lineWidth="1"/>
<line id="ln2" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln3" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln4" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln5" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln6" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln7" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln8" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln9" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln10" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln11" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<line id="ln12" x1 ="20" y1="40" x2 ="20" y2="140" lineWidth="1"/>
<!--One animation per image-->
<frame_anim id="anim1"/>
<frame_anim id="anim2"/>
<frame_anim id="anim3"/>
<fade_in id="fade1"/>
<fade_in id="fade2"/>
<fade_in id="fade3"/>
<!--UI-->
<label id="label1" caption="Paintings by Dany Dennis" font="bold 12px Verdana" x="420" y="10"/>
<progress_bar id="loading_bar" x="430" y="610" width="100" height="4"/>
<button id="next" up="images/arrow2.png" down="images/arrow.png" x="300" y="580" width="60" height="60"/>
<button id="prev" up="images/arrow.png" down="images/arrow2.png" x="600" y="580" width="60" height="60"/>
</application>
From there we'll go step by step covering the following topics:
<includes>
<include src="anims.xs" def="anims.xml"/>
</includes>
Includes are straightforward, they instruct the compiler to process the included files and register whatever they contain (usually classes). Whatever classes are declared in the includes can be used later in the application.
As you can see, includes support both code and description. In this particular case the include defines a couple animations to be reused later on: frame_anim and fade_in. These will be discussed in detail later.
<application source="images.xs" width="1000" height="640" title="">
This tag describes basic elements of your app, such as width, height and most importantly where the code for the app is written.
Resources describe the media your app will use, not all of which must be loaded (and downloaded) at start up time. And thats why there are resource "packages". A package has a name that will allow the programmer to load them later if needed, other wise you can set the auto_load property to true.
There is a little convention in here, the UI package (needed for buttons, etc) will always be loaded prior to the application start.
If you've seen the application running you would have noticed that there are 3 pages of paintings and such pages will display the images at different locations. Without having to write any code the programmer can create objects that describe such data:
<property id="pages" type="array">
<object type="page">
<property name="image_location" type="array">
<object x="300" y="200" w="350" h="247"/>
<object x="700" y="50" w="200" h="516"/>
<object x="0" y="0" w="250" h="337"/>
</property>
<property name="package" value="page1"/>
</object>
Note the package (resource collection) is also specified so the app can load such resources can be loaded only when the user request to see the page.
The sequencer system in xs was designed primarily for animation. However, it can be applied to any process that happens over time in your application. In this particular case we use a sequence to monitor the loading progress:
<sequence id="load_package">
<every t="0.3">
<run method="percentage"/>
</every>
</sequence>
So, every 0.3 seconds (while the sequence is running) the system will call a method called "percentage" that will check the loading progress. The code for that method reads:
instance load_package
{
property target : package;
method percentage()
{
if (!target)
return;
float progress = target.progress();
if (progress >= 1)
{
stop();
loading_bar.visible = false;
application.apply_images(target);
}
else loading_bar.percent = progress*100;
}
}
Note the sequence is parametric, meaning that it will monitor the progress on a particular "target" (a resource package). I intend to write the docs for the sequencer, but for the moment I will leave you with a sample sequence meant for animation:
<class id="fade_in" super="sequence">
<at time="0">
<animate property="target.alpha" from="0" to="0.3" in="1"/>
</at>
<at time="1">
<animate property="target.alpha" from="0.3" to="1" in="1"/>
</at>
</class>
At the end of the day this app is about showing images and animating their frames to match the positions. This works in a very similar fashion as HTML, components are described and then rendered. In this case we show 3 images and their frames made up of individual lines:
<!--Three images at the time-->
<img id="img1" x="0" y="0" width="100" height="100"/>
<img id="img2" x="120" y="140" width="200" height="200"/>
<img id="img3" x="300" y="300" width="200" height="200"/>
<!--And their frames, made by individual lines-->
<line id="ln1" x1 ="20" y1="40" x2 ="40" y2="140" lineWidth="1"/>
....
Such components are controlled by animations also described in the xml:
<!--One animation per image-->
<frame_anim id="anim1"/>
<frame_anim id="anim2"/>
<frame_anim id="anim3"/>
<fade_in id="fade1"/>
<fade_in id="fade2"/>
<fade_in id="fade3"/>
Remember frame_anim and fade_in are classes defined in the includes. Please check the code section to see how these are wired.
Whats an application without components to control its behavior? UI controls are described in the same HTML like way:
<label id="label1" caption="Paintings by Dany Dennis" font="bold 12px Verdana" x="420" y="10"/>
<progress_bar id="loading_bar" x="430" y="610" width="100" height="4"/>
<button id="next" up="images/arrow2.png" down="images/arrow.png" x="300" y="580" width="60" height="60"/>
<button id="prev" up="images/arrow.png" down="images/arrow2.png" x="600" y="580" width="60" height="60"/>
See the code section to see how they get their behavior.
First the listing:
property page_count = 3;
property page_index = 0;
property animations = [anim1, anim2, anim3];
property fades = [fade1, fade2, fade3];
on init()
{
//hook up the animations
anim1.target = [ln1, ln2, ln3, ln4];
anim2.target = [ln5, ln6, ln7, ln8];
anim3.target = [ln9, ln10, ln11, ln12];
fade1.target = img1;
fade2.target = img2;
fade3.target = img3;
do_transition();
}
method apply_images(package)
{
var pos = pages[page_index].image_location;
array images = [img1, img2, img3];
int curr = 0;
for(img i in images)
{
i.image = package.items[curr].resource;
i.rect(pos[curr].x, pos[curr].y, pos[curr].w, pos[curr].h);
curr++;
}
}
method do_transition()
{
int cnt = 0;
var page = pages[page_index];
for(frame_anim anim in animations)
{
anim.dest = page.image_location[cnt];
//shuffle the lines
int seed = Math.floor(Math.random()*4);
array target = [];
for(int i = 0; i < 4; i++)
{
int idx = seed + i;
if (idx > 3)
idx -= 4;
target.push(anim.target[idx]);
}
anim.target = target;
//and lets go
anim.start();
fades[cnt].start();
cnt++;
}
//load resources
if (page.package)
{
var package = resources.get(page.package);
load_package.target = package;
if (!package.loaded)
{
load_package.start();
loading_bar.visible = true;
if (!package.loading)
package.load();
}
else
{
loading_bar.visible = false;
load_package.start();
}
}
}
on next.click()
{
if (page_index < pages.length - 1)
page_index++;
else
page_index = 0;
do_transition();
}
on prev.click()
{
if (page_index > 0)
page_index--;
else
page_index = page_count - 1;
do_transition();
}
instance load_package
{
property target : package;
method percentage()
{
if (!target)
return;
float progress = target.progress();
if (progress >= 1)
{
stop();
loading_bar.visible = false;
application.apply_images(target);
}
else loading_bar.percent = progress*100;
}
}
And some topics:
The init event is called whenever the application is ready to run, in this application the event is implemented so so we can hook the animations with its data:
anim1.target = [ln1, ln2, ln3, ln4];
...
fade1.target = img1;
...
So each frame animation gets 4 lines components to animate and every fade gets its image.
Whenever a image package (3 images) gets selected by the user the app must account for it. It does by setting up positions and resources to the images. Pretty straightforward stuff:
method apply_images(package)
{
var pos = pages[page_index].image_location;
array images = [img1, img2, img3];
int curr = 0;
for(img i in images)
{
i.image = package.items[curr].resource;
i.rect(pos[curr].x, pos[curr].y, pos[curr].w, pos[curr].h);
curr++;
}
}
Page changes are animated, so here (do_transition method) we perform a couple operations. First the lines in the frame animation get shuffled randomly, for eye candy reasons. Secondly we must load the page's resources if needed and thats the snippet we're presenting:
var package = resources.get(page.package);
load_package.target = package;
if (!package.loaded)
{
load_package.start();
loading_bar.visible = true;
if (!package.loading)
package.load();
}
else
{
loading_bar.visible = false;
apply_images(package);
}
So we find the package the package with the appropriate name. In case it hasn't been loaded yet, we'll setup the load_package sequence and tell the package to start loading.