Animation is just moving something over time. The rate at which the something moves is defined by a function called an easing equation or interpolation function. It is these equations which make something move slowly at the start and speed up, or slow down near the end. These equations give animation a more life like feel. The most common set of easing equations come from Robert Penner's book and webpage.

Penner created a very complete set of equations, including some fun ones like 'spring' and 'bounce'. Unfortunately their formulation isn't the best. Here is one of them in JavaScript


easeInCubic:function(x,t,b,c,d){
returnc*(t/=d)*t*t+b;
},
easeOutCubic:function(x,t,b,c,d){
returnc*((t=t/d-1)*t*t+1)+b;
},

They really obscure the meaning of the equations, making them hard to understand. I think they were designed this way for efficiency reasons since they were first implemented in Flash ActionScript, where speed would be important on the in-efficient. It makes them much harder to understand and extend, however. Fortunately, we can refactor it to be a lot clearer.

Let's start with the easeInCubic equation. Ignore the x parameter (I'm not sure why it's there since it's not used). t is the current time, starting at zero. d is the duration in time. b and c are the starting and ending values.

easeInCubic:function(x,t,b,c,d){
returnc*(t/=d)*t*t+b;
},

If we divide t by d before calling this function, then t will always be in the range of 0 to 1, and d can be left out of the function. If we define the returned version of t to also be from 0 to 1, then the actual interpolation of b to c can also be done outside of the function. Here is some code which will call the easing equation then interpolate the results:


                var t = t/d;
                t = easeInCubic(t);
                var val = b + t*(c-b);

We have moved all of the common code outside the actual easing equation. What that leaves is a value t from 0 to 1 which we must transform into a different t. Here's the new easeInCubic function.


        function easeInCubic(t) {
            return Math.pow(t,3);
        }

That is the essence of the equation. Simply raising t to the power of three. The other formulation might be slightly faster, but it's very confusing, and the speed difference today is largely irrelevant since modern VMs can easily optimize the cleaner form.

Now let's try to transform the second one. An ease out is the same as an ease in except in reverse. If t when from 0 to 1 then the out version will go from 1 - 0. To get this we subtract t from 1 as shown:

        function cubicOut(t) {
            return 1-Math.pow(1-t,3);
        }

However, this looks awfully close to the easeIn version. Rather than writing a new equation we can factor out the differences. Subtract t from 1 before passing it in, then subtract the result from one after getting the return value. The out form just invokes the in form:

function easeOutCubic(t) {
     return 1 - easeInCubic(1-t);
}

Now we can write other equations in a similarly compact form. easeInQuad and easeOutQuad go from:

easeInQuad:function(x,t,b,c,d){
returnc*(t/=d)*t+b;
},
easeOutQuad:function(x,t,b,c,d){
return-c*(t/=d)*(t-2)+b;
},

to

function easeInQuad(t) {  return t*t; }
function easeOutQuad(t) { return 1-easeInQuad(1-t); }

Now let's consider the easeInOutCubic. This one smooths both ends of the equation. In reality it's just scaling the easeIn to the first half of the t, from 0 to 0.5. Then it applies an easeOut to the second half, from 0.5 to 1. Rather than this complex form:

easeInOutQuad:function(x,t,b,c,d){
if((t/=d/2)<1)returnc/2*t*t+b;
return-c/2*((--t)*(t-2)-1)+b;
},

We can compose our previous functions to define it like so:

        function cubicInOut(t) {
            if(t < 0.5) return cubicIn(t*2.0)/2.0;
            return 1-cubicIn((1-t)*2)/2;                
        }

Much cleaner.

Here is the original form of elastic out, which gives you a cartoon like bouncing effect:

easeOutElastic:function(x,t,b,c,d){
vars=1.70158;varp=0;vara=c;
if(t==0)returnb;if((t/=d)==1)returnb+c;if(!p)p=d*.3;
if(a<Math.abs(c)){a=c;vars=p/4;}
elsevars=p/(2*Math.PI)*Math.asin(c/a);
returna*Math.pow(2,-10*t)*Math.sin((t*d-s)*(2*Math.PI)/p)+c+b;
},

and here is the reduced form:


        function easeOutElastic(t) {
            var p = 0.3;
            return Math.pow(2,-10*t) * Math.sin((t-p/4)*(2*Math.PI)/p) + 1;
        }

Moral of the story: "Math is your friend" and "always refactor".

These new equations will be included in a future release of Amino, my cross platform graphics library.

When working on big projects I often create little projects to support the larger effort. Sometimes these little projects turn into something great on their own. It's time for me to tell you about one of them: AppBundler.

AppBundler is a small tool which packages up Java (client side) apps with a minimum of fuss. From a single app description it can generate Mac OSX .app bundles, Windows .EXE files, JNLPs (Java Web Start), double clickable jars; and as of yesterday evening: webOS apps! I started the project to support Leonardo Sketch but I think it's time for AppBundler to stand on it's own.

Packaging Java apps has historically been an exercise in creative swearing. The JVM provides no packaging mechanism other than double clickable jars, which are limited and feel nothing like native apps. Mac and Windows have their own executable formats that involve native code, and Sun has never provided tools to support them. Java Web Start was supposed to solve this, but it never took off the way the creators hoped and has it's own idiosyncrasies. Long term we will have more a more environments with Java available but with different native package systems. Add in native libs, file extension registration, and other metadata; and now you've got a real problem. After hacking Ant files for years to deal with the issue I decided it was finally time to encode my build scripts and Java lore into a new tool that will solve the issue once and for all. Thus AppBundler was born.

How it works

You create a simple XML descriptor file for your application. It lists the jars that make up your app along with some metadata like the App name and main class. It can optionally include icons, file extensions, and links to native libraries.

<?xml version="1.0" encoding="UTF-8"?>
<app name="Amino Particles"> 
   <jar name="Amino2.jar"/> 
   <jar name="amino_sdl.jar"/> 
   <jar name="examples.jar" 
         main-class="com.joshondesign.amino.examples.Particles"/> 
   <property name="com.joshondesign.amino.impl" value="sdl"/> 
   <native name="sdl"/> 
</app> 

Then you run AppBundler on this file from the command line along with a list of directories where the jars can be found. In most apps you have a single directory with all of your jars, plus the app jar itself, so you usually only need to list two directories. You also specify which output format you want or --all for all of them. Here's what it looks like called from an ant script (command line would be the same).

 <java classpath="lib/AppBundler.jar;lib/XMLLib.jar" classname="com.joshondesign.appbundler.Bundler" fork="true"> 
<arg value="--file=myapp.xml"/> <arg value="--target=mac"/> <arg value="--outdir=dist/"/> <arg value="--jardir=lib/"/> <arg value="--jardir=build/jars/"/> </java> 
AppBundler will then generate the executable for each output format.

What it does

For Mac it will create a .APP bundle containing your jars, then include a copy of the JavaApplicationStub and generate the correct Info.plist files (Mac specific metadata files), and finally zip up the bundle. For Windows it uses JSmooth to generate a .EXE file with an icon and class path. For Java WebStart it will generate the JNLP file and copy over the jars. For double click jar files it will actually squish all of your jars together into a single jar with the right META-INF files. And all of the above works with native libraries like JOGL too. For each target it will set the correct library paths and do tricky things like decompress native libs into temp directories. Registering for file extensions and requesting JREs mostly works.

What about webOS?

All of the platforms except webOS ship with a JVM or one can be auto-installed on demand (the Windows EXEs do this). There is no such option for webOS, however. webOS has a high level HTML 5 based SDK and a low level C based PDK. To run Java on webOS you'd have to provide your own JVM and class libraries, so that's exactly what I've done. The full OpenJDK would be too big to run on a lightweight tablet, and a port would take a team of people months to do. Instead I cross compiled the amazingĀ open source JVM Avian to ARM. Avian was meant to be embedded and already has an ARM port, so compiling it was a snap. Avian can use the full OpenJDK runtime, but it also comes with it's own minimal classpath.jar that provides the bare minimum needed to run Java code. Using the smaller runtime meant we wouldn't have a GUI like Swing, but using Swing would require months of AWT porting anyway, which I wasn't interested in doing. Instead I created a new set of Java bindings to SDL (Simple DirectMedia Layer), a low level graphics API available on pretty much every platform. Then I created a port of Amino (my 2D scene graph library) to run on top of SDL. It sounds complicated (and it was, actually), but the scripts hide the complexity. The end result is a set of tools to let you write graphical apps with Java on webOS. Amino already has ports to Java2D and HTML 5 Canvas (and OpenGL is in the works), so you can easily create cross platform graphics apps. And now with AppBundler you can easily package them as well. Interestingly, Avian runs on desktops nicely, so putting Java apps into the Mac App Store might now be possible. There's already some enterprising developers trying to get Avian working on iOS.

How you can help.

While functional, I consider AppBundler to be in an alpha state. There's lots of things that need work. In particular it needs Linux support (generate rpms or debs?) and a real Ant task instead of the Java exec commands you see above. I would also like to have it be included in Maven and any other useful repo. And as a final request (as long as I have you here), I need some servers to run builds tests on. I have already have Hudson running on a Linux server. I'd love it if someone could run a Hudson slave for me on their Windows or Mac server. And of course we need lots of testing and bug fixes. If you are interested please join the mailing list.

Client Java Freedom

AppBundler is another facet of my efforts to let help Java provide a good user experience everywhere. Apps should always feel native, and that includes the installation and start experience. I've used AppBundler to provide native installs of Leonardo Sketch on every desktop platform. I hope AppBundler will help you do the same. Enjoy! -Josh  

References:

 

You may be a new programmer, or a web designer, or just someone who's heard the word 'RegEx', and asked: What is a Regex? How do I use it? And why does it hurt my brain? Well, relax. The doctor is in. Here's two aspirin and some water.

What is it doctor?

Oh, it's two parts hydrogen and one part oxygen; but that's not important right now. We're here to talk about RegEx. RegEx is short for Regular Expressions, but that's also not important right now. Regexs are just patterns for matching bits of text. Whenever you hear RegEx, just think: pattern. In fact, I'll stop using the word regex right now, since it mainly sounds like the kind of medicine you'll need after trying to write a complex regex.

What are these patterns they good for?

Patterns are mainly used for three things: to see if some text contains the pattern (matching), to replace part of the text with other text (replacement), and pulling out portions of the text for later use (extraction). Patterns contain a combination of regular letters and special symbols like ., *, ^, $, \w and \d. Most programming languages use pattern matching with a subset of the Perl syntax. My examples will use JavaScript, but the same pattern syntax should work in Java, Python, Ruby, Perl, and many other languages.

Matching

Suppose you want to know if the text "Sally has an apple and a banana." contains the word 'apple'. You would do it with the pattern 'apple'.

var text = "Sally has an apple and a banana.";
if(text.match(/apple/)) { console.log("It matches!");
}

Now suppose you want to know if the text begins with the word 'apple'. You'd change the pattern to '^apple'. The ^ is a special symbol meaning 'the start of the text'. So this will only match if the 'a' of apple is right after the start of the text. Call it the same way as before.

var text = "Sally has an apple and a banana.";
if(text.match(/^apple/)) { //this won't be called because the text doesn't start with apple console.log("It matches!");
}

Besides the ^ symbol for 'start of text', here's some other symbols are important to know (there are far more than this, but these are the most important).

$ = end of the text
\s = any whitespace (spaces, tabs, newlines)
\S = anything *but* whitespace
\d = any number (0-9)
\w = any word (upper & lower case letters, numbers, and the underscore _)
. = anything (letter, number, symbol, whitespace)

If you want to match the same letter multiple times you can do this with a quantifier. For example, to match the letter q one or more times put the '+' symbol after the letter.

var text = "ppqqp";
if(text.match(/q+/)) console.log("there's at least one q");

For zero or more times use the '*' symbol.

var text = "ppqqp";
if(text.match(/q*/)) console.log("there's zero or more q's");

You can also group letters with parenthesis:

var text = "ppqqp";
if(text.match(/(pq)+/)) console.log("found at least one 'pq' match");

So, to recap:

. = any x+ = match 'x' one or more times
x* = match 'x' zero or more times ex: match foo zero or more times, followed by bar one or more time = (foo)*(bar)+
x|y = match x or y

Replacing text

Now that you can match text, you can replace it. Replace every instance of 'ells' with 'ines'.

var text = "Sally sells seashells"
var text2 = text.replace(/Sally/,"Billy"); //turns into "Billy sells seashells"
var text2 = text.replace(/ells/,"ines"); //turns into "Sally sines seashines"

Modifiers

Most pattern apis have a few modifiers to change how the search is executed. Here's the important ones:

Make the search case insensitive: text.match(/pattern/i)

Normally the patterns are case sensitive, meaning the pattern 'apple' won't match the word 'Apple'. Add the i parameter to match() to make it case insensitive.

Make the search multiple lines: text.match(/pattern/m)

Normally a pattern will only match the first line of the text. It will stop at the newline character '\n'. With the m parameter it will treat newlines as whitespace and let you search the entire string.

Make a replace global: text.replace(/foo/bar/g)

Normally the replace() function will only replace the first match. If you want to replace every match in the string use the g parameter. This means you could replace every copy of 'he' to 'she' in an entire book with a single replace() call.

Substring Extraction

Another major use for patterns is string extraction. When you do a match, every group of parenthesis becomes a submatch, which you can use individually. Suppose you have a text string with a date in it and you want to get the year and month and day parts out of it. You could do it like this:</p

var text = "I was born on 08-31-1975 and I'm a Virgo."
var parts = text.match("(\d\d)-(\d\d)-(\d\d\d\d)");
//pull out the matched parts
var month = parts[1]; //08
var day = parts[2]; //31
var year = parts[3]; //1975
//parts[0] would give you the entire match, eg: 08-31-1975

The Cheet Sheet

The standard pattern syntax in most languages is expansive and complex, so I'll only list the ones that are actually useful. For a full list refer to the documentation for the programming language you are working with.

Match anywhere in the text: "Sally sells seashells".match(/ells/) (matches both sells and seashells)
Match beginning of text: "Sally sells seashells".match(/^Sally/)
Match end of text: "Sally sells seashells".match(/ells$/) (matches only the seashells at the end)

any word at least three letters long \w\w\w
anything .
anything followed by any letter .\w
the letter q followed by any letter q\w
the letter q followed by any white space q\s
the letter q one or more time q+
the letter q zero or more times q*

any number \d
any number with exactly two digits: \d\d
any number at least two digits long: \d+
any decimal number \d+\.\d+ //ex: 5.0
any number with an optional decimal \d+(\.\d+)* //ex: 5.0 or 5
match the numbers in this date string: 2011-04-08 (\d\d\d\d)-(\d\d)-(\d\d)
also be able to match this date string: 2001-4-8 (\d\d\d\d)-(\d+)-(\d+)

Conclusion

Patterns are a very complex subject so I've just tried to give you the basics. While complex, they are also incredibly powerful and useful. As you learn them you'll find you use them more and more for all sorts of cool things. For a more in-depth tutorial read the Mozilla JavaScript Regular Expression guide.

PS: Found a bug in the above code? Want to suggest more examples of useful regex's? Want to just shoot the breeze? I've turned off comments due to 99% of it being spam, so please tweet your suggestions to me instead: @joshmarinacci. Thanks for understanding!

As part of my ongoing efforts to create better designed software, I some how ended up creating my own new UI toolkit. This is really a part of my belief that a decade from now 90% of people will use phones, slates, or netbooks as their primary computing device. Amino is my experiment building software for that other 10%: the content creators who need killer desktop apps, the programmers who want great tools, and the knowledge workers who need to manage incredible amounts of information at lightning speed. Amino is the toolkit for these apps.

Read the full description of Amino at my Java.net blog, or go play with Amino right now.

Software Libraries are good. They allow abstraction and encapsulation; which encourages reuse. They also allow the library to be written by one person and used by another. This is reuse at the programmer level, not just the system level. A library can be written by a person who has domain expertise but used by someone else who has less or no expertise in that domain. For example: an XML parser. The implementer knows the XML spec inside and out, but the user of the lib needs only a basic understanding of XML in order to use the lib.

Now that we've established that libraries are good (regardless of whether are are implemented as classes, packages, DLLs, etc.), how can we extend them? Traditionally libraries are very limited in how they can influence the experience of the programmer. The coder simply has new functions to call. A more advanced case is component driven GUI builders where the lib exposes more information so that the IDE can make the library easier to use. Annotating of types in a map component could enhance the GUI builder draw markers, use a dummy tile set, and set positions. This is all standard stuff. Could libraries do more? How could the library author further influence the experience of the library user?

Let's do a quick brainstorm:

  • Extend the language with new operators and syntax specific to the library. A physics library could import operator overloads to enable more math like syntax when dealing with physical units and vectors.
  • Tests that verify aspects of the code that calls the library. This could automate the advice that would be in the documentation, such as "don't pass null to certain functions" or "this function returns an object that you must dispose of".
  • If the lib is used from within an IDE, the lib can insert new documentation into the searchable index of docs.
  • The lib could come with example code and reusable annotated code snippets that would only be applied to projects using the lib.
  • The lib could insert a new package manager into the compiler toolchain. No more messing with Maven POMs.
  • The lib can provide the compiler with new code optimizers for the particular tasks. An imaging analysis library could instruct the compiler on how to better use SIMD instructions when available.

Obviously modifying the environment of the programmer is a dangerous activity so we would need very strict scoping on the changes. A library which extends the language applies only in the parts of the code that actually import the library. Changes are scoped to the code which asks for them.

Fundamentally a library is a thing created by one programmer for use by another programmer (or the same programmer at a future date). We should extend the concept of a library to apply to the entire programming experience, not just adding objects and methods. This would allow all environment extensions to be managed in a standard way that is more understandable, flexible, and scoped. I would never need to set up a makefile again. I could simply import in my main.foo code the package manager, repos, code chunks and syntax that I want to use for this project. The library extensions to the compiler handle everything else automatically.

Clearly there are challenges with such an approach. We would have to define all of the various ways a library could modify your environment. How would compiler hooks actually work? Annotation processors? A function to modify the AST a different stages of compilation? Would it imply a particular back end like LLVM? I don't have the answers. There clearly are challenges to be solved, but I think the approach is a step forward from today where we have many different components of our programming environment that are dependent on each other but don't actually know about each other. Having a way to organize this mess, even if imperfect, would certainly help us make better software.

Teach them well and let them lead the way
someone who sounds like Whitney Houston

The Status Quo

Last summer I released Leonardo, a cross platform vector drawing tool. Later in the year I released an alpha of Amino, the new Java UI toolkit I wrote in the process of building Leonardo. Since then I've released updates for both, but haven't quite reached a 1.0 for Amino. It's time for some changes: Amino 2 and Amino.js.

Overall I've been pretty pleased with most of Amino. It is reasonably fast, pretty skinnable, and has a very clean API with generics, an event bus, and isolated models. The underlying graphics stack, however, is a mess. Originally I planned to build a second graphics stack on top of JOGL, but it's never worked properly (you can still see the disabled code in the repo). Amino has a basic component tree structure built on a Java2D wrapper but for a modern UI toolkit we need more. We need a real scene graph. Something that has animation and transforms built right in. A library fully isolating the app from the underlying graphics, with framerate and buffering control. Without a real scenegraph Amino will never work well on top of JOGL.

Modern Graphics Research

You have may noticed a slowdown in Amino and Leo commits. That's because I've spent the last few months researching modern hardware acceleration and teaching myself OpenGL Pixel Shaders. (Oh, and traveling around the world, announcing new products, and getting ready for my first baby to arrive in May). After all of the research I've come to a few surprising conclusions (surprising to me, at least).

First, on modern GPUs memory usage and bandwidth are the bottle necks, not drawing speed. In the realm of 2D graphics, at least, our biggest headache is managing the data structures and shipping them up to the GPU. The pixel shader units themselves are far faster than what we need. It's often much better to send a tiny amount of information over the bus and use an inefficient algorithm in a pixel shader, rather than do it on the CPU and upload a texture.

Two: OpenGL sucks for traditional 2D drawing tasks. Sure, it's great at compositing, but the actual line and polygon routines are primitive if you want antialiased shapes and smooth compositing. There's a whole lot of stuff we have to rewrite by hand on the CPU or re-implement with shaders.

Three: on platforms without accessible GPUs we can do lots of tricks to speed up apparent drawing speed. Reponsiveness and consistency matter more than raw drawing speed. And if a GPU is available, then we can efficiently cache portions of the drawing as textures and use the GPU for compositing. All of this is hard to do in a reusable way without a real scene graph to build apps on.

Four: Web and mobile apps need a scenegraph as well. For a long time I've mainly thought of web apps as UIs built with static images and text. However, in the last year HTML Canvas has come a long way. Games with sustained 60fps are quite possible in recent builds of Chrome, and mobile devices are catching up as well. (I'm amazed with what I've been able to do on the upcoming TouchPad). So, if we are going to put high powered UIs on web technology, then we need a real scene graph.

Amino 2: The New Plan

I've decided to start a new branch called Amino 2. The current Amino will be split into two parts:

  • Amino Core, the scenegraph, event bus, and common utility classes
  • Amino UI, the UI controls and CSS skinning layer

Amino Core will have multiple implementations. First, a scenegraph for desktop Java apps that will use a mixture of Java2D and JOGL. Eventually it may be possible to do full anti-aliased shape and clip rendering with pure shaders, but this is still an open area of research. For now we will do the shape rasterization in software with the existing mature Java2D stack, then do compositing, fills, and effects on the GPU with shaders. Over time we may shift more to the GPU. With a scenegraph this will be transparent to the app developer, which is a very good thing.

Announcing Amino.js

Amino GFX will also have a second implementation built in JavaScript and HTML Canvas. I'm calling this Amino.js. It will let you build a scene graph of shapes and images, complete with declarative animations and special effects, all drawn efficiently into the a canvas on your page. Amino.js will not have the CSS and UI controls layer, because that stuff is already well provided my many existing JavaScript libraries. There's no point in making new stuff where it's not needed

I've also considered making a pure in memory version of the scenegraph without any Java2D dependencies, or porting it to a pure C++ lib on SDL or OpenGL. However, these requires a lot of research into rasterization algorithms so I'm not going to work on them unless someone really wants it.

Next Steps

That's it for today. I've built a prototype of Amino.js and over the next few weeks I'll show you more of the API and walk through it's construction. I hope you'll find it interesting to watch as the pieces come together, and any code contributions will be very welcome. Come back next week for some code samples and I'll dive into a puzzle game prototype called Osmosis. Here's a few quick demos to whet your appetite.

Google ChromeScreenSnapz013.png
Animated bar chart

Google ChromeScreenSnapz012.png
draggable scene nodes

Google ChromeScreenSnapz011.png
Simple particles at 60fps

Oh, and I forgot to mention that Amino2 and Amino.js will again be fully free and open source. BSD for the win!

Mmmwaa haa haa. It lives! I've gotten Java to run on webOS natively with a new set of Java SDL bindings. That means it just *might* time to start a new project. Read on for how it works and how you could help.

 

For a while I've been following an open source project called Avian. It's a very lightweight and highly portable JVM that can run almost anywhere. Recently I tried a new build and was able to get the ARM port running on webOS!  This is good news because Avian can run pretty much any Java code if you supply it with the right runtime (it can optionally work with the OpenJDK libs).

Now, of course getting a command line app it run is not very interesting. Really we want to talk to the screen to make some real graphical applications. So that brings us to part two: SDL.

If you've been doing desktop programming for a while you've probably heard of SDL, the Simple DirectMedia Library. It's a fairly low level graphics and audio API that runs pretty much everywhere, including on webOS.   But, of course, like many low level APIs it's built in C. So if I want to use Java I need to some wrapper to call it.  The existing wrappers out there are very old and didn't work well on Mac, so it was time to build my own.

Over the weekend I learned how to use Swig, a JNI wrapper generator and successfully ran my new SDL wrappers on Mac, Linux, and webOS.  Here's a quick screenshot:

 

Screen Shot 2011 08 31 at 3 28 52 PM

it's not much but it proves that everything is working.

So what's the next step?  Honestly... I don't know. I created this specifically to let me code Java on webOS, but the SDL bindings would probably be useful for cross platform desktop applications as well.  We could port Amino to it, or do some funky multitouch stuff. It would certainly be great for people creating games. I need your advice.

What would you like to do with this library? What higher level APIs would you like? If you have any ideas of what you'd do with this lib, or would like to contribute to the project (help on Windows compilation would be greatly appreciated), then please message me on twitter: @joshmarinacci.

Thanks!

- Josh

Most of of my free time work for the past few months has gone into Amino, the UI toolkit that Leonardo is built on, but Leo itself has gotten a few improvements as well. I'm happy to announce that the next beta of Leo is up, including:

  • Amino gives us a more uniform look and feel, now skinnable with CSS
  • Rulers!
  • The Make a Wish Button, now the easiest way to send feedback.
  • the canvas properly scrolls and zooms with real scrollbars now
  • Under the "Path" menu you can now add, subtract, and intersect shapes.
  • export to PDF
  • actual unit tests for exporting to SVG & PNG, almost every kind of shape is supported now
  • Tons of bug fixes

Unfortunately, as part of the Amino toolkit overhaul certain things have gotten slower. In particular doing layout when you resize the window is significantly slower. Don't worry, I have lots of speed improvements coming to Amino which will address this in Leo.

As always, please test and file bug reports. The export filters in particular are probably still have bugs to fix.

Builds are here: