The basics of Silvermoon
Silvermoon is a UI framework for Windows Mobile 6.x to build hardware accelerated user interfaces with OpenGL ES 1.1 as kernel. When I started development, I planned to make Silvermoon independent from the hardware layer, so that the OpenGL
rendering engine could easily be replaced by ActiveX. But since development progressed, I recognized that this abstraction leads to several restrictions and so from beta 2 to beta 3 I dropped this feature completely.
Silvermoon has currently no design time supports. This is because Silvermoon is not a windows form application. As for Silverlight, a separate designer is required, but for now it doesn’t exist yet. The name “Silvermoon” is a derivation from “Silverlight” and
“Moonlight” and in fact, I was orientating on Silverlight to design Silvermoon. But although similar, it has nothing to do with it.
Before I go into the details, I give a brief introduction of the elements of Silvermoon.
Shapes are primitive classes which are used by controls to render its appearance. Generally spoken, a shape is a rectangle with an image. The image can be a rectangle (RectangleShape) or it can be a Circle (RoundShape) or any other image.
A shape can even contain other shapes (MultiShape) to build complex images. But more about shapes later…
Controls are the heart of Silvermoon. Controls have properties that describe the current characteristics and appearance of a control. For instance a button control has a IsPressed property, or almost every Control has a Color property. Controls
use shapes to render their appearance. Therefore every control has a protected Shapes class which is a linked list of shapes (I prefer linked lists over lists, as they are about 20% faster for enumeration and consume only the storage they required). A Control
can also host one (Decorator) or more (ContainerControl) controls. For instance a ListBox contains Items. There are controls which don’t have any appearance and are supposed to be used as container only, like Panel, and some have, like Button. Visual controls
(or ChromeControl) have a Background and a Chrome property which is of type shape. The background is the shape which is rendered as first of all other shapes and child controls. Chrome is the opposite and it is rendered after all other shapes and child controls.
For instance, the picker makes use of the chrome to render a lens.
Some controls have additional shapes, and you can always change the default shape directly by changing the property to another shape.
A control does not directly (or should not) change it’s appearance when a property changes it’s value. The only thing it does is to change the state. So the button sets it’s state to “Pressed” when IsPressed changes to true, and back to “Normal”
when it returns to false. And this is the moment where a VisualStateManager comes into the play:
VisualStateManager is a description of how the control appears when a control has a distinct state. Of course it can happen that a control has more than one state, e.g. a radio button can be selected and disabled. Therefore VisualStateManager is divided into
groups. Every group can contain a state and they are mutually exclusive. But it’s also possible that one state is included in more than one group. Each state finally contains a collection of properties and their values for that state. They also describe how
to transition to the new value instead of simply change to the new value on sudden. VisualsStateManager can either be applied to all controls of a distinct class (VisualStateManager.SetClassManager) or for an explicit control only (Control.VisualStateManager).
Property accessors are the Silvermoon equivalent of dependency properties. Property accessors allow access the value of a property from a class without knowledge of the class itself. This feature is required to describe animations for properties
(see VisualStateManager) and later how to bind them. Unless dependency properties, they are no sparse properties who only consume storage when they are assigned. That because a dependency property is slower in access (70xwrite, 20xread) than a direct property
due to boxing/unboxing value types and maintaining the dictionary. Therefore I decided to create something new, which allows to access a property from outside but does not slow down accessing a property. A property accessor also allows to remember the value
of the property before an animation/transition is applied on it. A property accessor also ensures that there is only one animation of the property at one time.
Style and StyleDictionary
Principally spoken, a style is a class which changes properties of a control. Instead of writing a custom class which overrides the default shapes of a control, styles can do this more elegant. As soon as a control initializes itself (this
is when shortly before it gets rendered for the very first time), it looks for a style to use. This can be a named style (Control.StyleName) or a class style. The style comes from the first ancestor control, which contains a StyleDictionary. This is a control
of type ContainerControl. For instance, a window is a container control and therefore has a StyleDictionary. Therefore, you can give all buttons on a distinct window another appearance as for a second window. You can also change the style of controls embedded
in a panel. As an example, ToolBar contains a Style in is dictionary for any button control to render it different from its default style. But it’s also possible, to override this default style again.
Transformations can be attached to controls as well as to shapes and their intention is – as the name applies – to transform the control or shape. There are transformations to translate the location as well as to scale, or rotate in 3d space.
Transformations are mostly used for transitions.
Commands are used to notify ancestor controls about an event that occured. The ancestor (parent) can analyze the command and decide whether to do something or not. The command is then bubbled down to the next parent. If and when a command
is raised, depends of the class of the control. For instance, a button command raises a command when the button is tapped. However, it is possible to raise any command at any time with RaiseCommand. The explorer example in the demo uses commands to decouple
child controls from their parents.
Many events in Silvermoon are SparseEvents. SparseEvents, as the name implies are events which don't consume any storage unless they are used. You can register own sparse events by creating a new static instance, and use AddHandler/RemoveHandler/RaiseEvent
to add/remove/raise the event.