Thursday, July 9, 2009

Initial Exploration of Draw2d on GWT/SVG

It's been some time since I updated this blog. The short story is, I made some progress with Java2Script, and produced a small prototype of SWT GC on HTML Canvas, which you can see here.

Unfortunately, when trying to compile the next level of the stack, Draw2d, I ran into low-level compiler bugs. I'm continuing to work with the Java2Script developers on these issues, but I don't really feel like I'm in a position to fix or work around bugs in the Java2Script compiler, so my mentor and I have been discussing alternative strategies.

One such alternative is to start a level higher up the SWT/Draw2d/GEF stack, at the Draw2d layer, and attempt to implement the Draw2d API in terms of SVG or dojox.gfx, and use GWT to compile it. This is very interesting to me for a number of different reasons, so I've been drilling down into it. Here is what I have initially determined about the feasibility of this project:

The main problem with this strategy is that the architecture of Draw2d encourages the use of an immediate-mode graphics API, exposed by the org.eclipse.draw2d.Graphics class, which is just a thin wrapper over GC's immediate-mode API.

The way Draw2d encourages the use of this API is by way of subclassing the Figure class. Figure subclasses implement, among other things, the paint or paintFigure methods, which get passed a Graphics instance, and use that instance for drawing. I think the whole process is that LightweightSystem sets up UpdateManager, which knows when to call the Figure's paint method.

This is a problem, because if Draw2d is to be implemented in terms of SVG (or some other browser-based, retained-mode graphics API, e.g. VML), it can't be implemented in terms of an immediate-mode API.

In my opinion, this is a limitation in Draw2d's architecture with regard to our intentions, and I should note that it could have been easily avoided by making Figure protected. This would have forced everything outside of Draw2d to only use the API exposed by Draw2d. Now, however, there's external code that depends on being able to subclass Figure.

Less code than you might think, though. Here are all of the classes in GEF and GEF examples that extend Figure:


org.eclipse.gef.editpolicies - src - org.eclipse.gef
SnapFeedbackPolicy
FadeIn
org.eclipse.gef.examples.flow.figures - src - org.eclipse.gef.examples.flow
SubgraphFigure
org.eclipse.gef.examples.flow.parts - src - org.eclipse.gef.examples.flow
ActivityDiagramPart
org.eclipse.gef.examples.logicdesigner.figures - src - org.eclipse.gef.examples.logic
BentCornerFigure
NodeFigure
org.eclipse.gef.handles - src - org.eclipse.gef
AbstractHandle
org.eclipse.gef.internal.ui.palette.editparts - src - org.eclipse.gef
DetailedLabelFigure
DrawerFigure
PaletteStackEditPart
PinnablePaletteStackFigure
SeparatorEditPart
SeparatorFigure
ToolbarEditPart
org.eclipse.gef.internal.ui.rulers - src - org.eclipse.gef
GuideEditPart
GuideLineFigure
GuideFigure
RulerFigure
org.eclipse.gef.tools - src - org.eclipse.gef
MarqueeSelectionTool
MarqueeRectangleFigure


So, not that many, and nothing from any of the GEF examples. So implementing Draw2D in terms of SVG may not be a perfect fit, but it might still be OK. Basically, the solution would be to limit usage of the Draw2d API only to those Figures that are declared in Draw2d (e.g. org.eclipse.draw2d.Triangle), and to those that are declared in GEF. Here's how we would then proceed:

Each class that implements Figure would keep a private member which is basically an SVG handle, which maps it into SVG. Or, if we don't want a direct dependency on SVG, we can make it even more abstract and just create an INativeGraphicsAdapter interface, then implement classes that basically just a wrap around a handle to the native JS object, exposed via JSNI. Unfortunately, dependency injection is not an option here because Draw2d's figure API doesn't support it and we can't change it. Messing with the class hierarchy (specializing for a particular implementation of this interface), is also not an option. So it seems as though we'll have a strong dependency on some implementation class. I can't see a way around this right now, unfortunately... But maybe the solution is in GWT's deferred binding technique. I'll research this more and see. For the moment, I'll continue to think in terms of having a direct reference to a wrapped native SVG DOM node handle, because it's easier to think about.

The way it would then work is, for each Figure concrete subclass, either in the constructor, or in some init() method, an SVG DOM node gets created, but does not get added to the Document. The IFigure.add() method would add the node in DOM. IFigure.dispose would remove it. All operations in the IFigure API would then get mapped onto whatever native operations on the SVG DOM node are required.

Also note that even though SVG does not natively include a concept of asynchronous updates (where you make a number of updates to a Figure, and it gets applied later on, asynchronously), it would still be possible to make use of this pattern by taking advantage of the fact that we have an extra API wrapping the native SVG. The way this would work is we implement the Command pattern, such that for most operations on IFigure, we create a command and save it in a private queue on each Figure object, and then wait until UpdateManager calls paint to flush all of the operations in the queue.

And that's it.

We will also have some SWT requirements to satisfy, but I expect these to be very minimal, and now that I have a deeper understanding of SWT, I feel I should be able to spec these out fairy quickly and easily.

No comments:

Post a Comment