r5 - 27 Mar 2007 - 14:30:18 - PhilippeBossutYou are here: OSAF >  Projects Web  >  DevelopmentHome > ApplicationProject > CpiaFramework > VisualStyle

Visual Style

This document describes a proposal for creating a more abstract visual style model for blocks that allows for easy updating of the UI appearance without getting bogged down in the details of block behavior. The concept is similar to CSS though at the moment we don't expect to be using CSS itself anytime soon.

This proposal is being written because I don't believe that our current mechanism of maintaining visual appearence is at all scalable to a finished application. For half the visual attributes of a block, we have to tweak the block itself, and the rest of the attributes are spread across various style objects (CharacterStyle?, PresentationStyle?, ColorStyle?, etc)

Goals

This VisualStyle proposal has the following goals:

  • Easier management of application appearence, across the whole application: Currently font information is sprinkled throughout the product. As the product grows in complexity, we want to be able to centrally manage the appearance of the application as a whole, without having to venture into each corner of the app.
  • Rationalizing fonts across the product: similar to the previous point, we need a centralized system for managing fonts so the application is consistent across the product, and across platforms.
  • Separate appearance from function: Right now the block code handles both the behavior of each block as well as the visual style. This code would be easier to maintain if we could factor out the visual style from the behavior. This would allow certain designers to fiddle with colors and settings without wading into a mess of blocks.
  • Mechanism to specify per-platform styles: There is no current mechanism for this right now. In addition to simply making fonts look good on each platform, we will probably need to make specific per-platform changes to the UI so that a Mac app looks like a Mac app, and so forth.
  • Support for locale-specific styling information: Certain locales are going to have requirements to make parts of the the UI bigger or smaller, or change the font to support a certain language. We can allow for this by letting translators attach style information without modifying the original block declarations.

Style sheets

Style sheets are the preferred standard for defining visual appearence for the web. The goals of CSS include the separation between content and appearance. The structure of a document doesn't necessarily need to define its visual appearance. You should be able to define UI behavior and structure independent of its stylings, and apply consistent style across different UI elements regardless of how or where they are rendered in their containing window/document.

Style Rules

All visual appearance (borders, colors, what have you) should all be expressed via some set of style blocks, which could optionally inherit or be constructed from other style blocks.

I think we can accomplish something very similar to id/class matching like CSS by making use of some of chandler's Query infrastructure, and some of the bidirectional stuff.

CSS

The basic idea behind CSS is that you have a selector, which is a rule, and a set of style information. The selector is just a rule that applies to specific elements.

For example, if you say:

.foo { border: 1px black }

.foo is the selector and "{ border: 1px black }" is the style definition. The "." is part of the selector syntax that means "match all elements that have class=foo"

So that:

<div class="foo">
and
<input type="text" class="foo">

will both recieve the style "border: 1px black".

Style in Chandler

We already have a rule system: the Query system in the repository. We also have a way of providing bidirectional reference between items so we don't even necessarily need the query system.

We have a very weak system of styling with the tags <CharacterStyle> and <ColorStyle>. This system is meant to replace these two, but they provide the obvious hooks that we'll need for this.

Using these two concepts we should be able to apply any style as necessary to any block, without the block itself knowing anything about the styles being applied to it until runtime.

This ends up with an N:N relationship between blocks and styles. A style can apply to multiple blocks, and a block can have multiple styles. Dealing with style conflicts is an implementation detail.

Style Declarations

A style declaration is a single bit of style information about a specific style. Style declarations might include font information, borders, colors, etc.

For example, a color style:

<FontStyleDeclaration itsName="bigRed">
  <fontFamily value="Arial"/>
  <fontSize value="24"/>
</FontStyleDeclaration>

Here are a few of the many visual attributes that we might want to include in the style information:

  • fonts (family, size, weight, etc)
  • size (min size, actual size, etc)
  • border (width, style)
  • alignment (left/right/center/top/bottom/horizontal-center)
  • color (text, background)

Applying style blocks

We probably want a few different ways to apply style to a block or set of blocks. Below are listed 4 mechanism, probably in order of importance:

... to a specific block

<StyleRule>
  <!-- almost identical to CSS's #id selector, 
       applying to a specific block -->
  <applyToBlock itemref="somedoc:SomeSpecificBlock">
  <declarations itemref="doc:bigRed"/>
</StyleRule>

... to a set of blocks

<StyleRule>
  <!-- almost identical to CSS's .class selector, 
       applying to a set of blocks with their "visualClass" attribute set-->
  <applyToClass value="detailview-item"/>
  <declarations itemref="doc:bigRed"/>
</StyleRule>

... to specific widgets

<StyleRule>
  <!-- almost identical to CSS's tag selector, 
       applying to all blocks of a given kind -->
  <applyToKinds value="Tree"/>
  <declarations itemref="doc:bigRed"/>
</StyleRule>

... to arbitrary queries

<StyleRule>
  <!-- almost identical to CSS's tag selector, 
       applying to all blocks of a given kind -->
  <applyToQueryResults value="for (i in ...."/>
  <declarations itemref="doc:bigRed"/>
</StyleRule>

If down the line someone wanted to adapt this stuff to read rules from CSS and store them in the repository in the same way they would if they came in from parcel.xml, more power to them! The serialization format doesn't matter as long as we're consistent about how we store it in the repository.

Kind declarations

Here's a stab at a <StyleRule> Kind:
<Kind itsName="StyleDeclaration">
</Kind>

<!-- specific style kinds -->
<Kind itsName="FontStyleDeclaration">
  <Attribute itsName="fontFamily">
    <type itemref="String"/>
  </Attribute>
  <Attribute itsName="fontSize">
    <type itemref="Int"/>
  </Attribute>
  <Attribute itsName="fontWight">
    <type itemref="String"/>
  </Attribute>
</Kind>

<Kind itsName="StyleRule">
  <Attribute itsName="applyToBlock">
    <type itemref="block:Block"/>
    <cardinality value="list"/>
  </Attribute>
  <Attribute itsName="class"/>
    <type itemref="String"/>
    <cardinality value="list"/>
  </Attribute>
  <Attribute itsName="declarations">
    <type itemref="doc:StyleDeclaration"/>
    <cardinality value="list"/>
  </Attribute>

  <!-- this might be set up automatically by the associated style class using a query? -->
  <Attribute itsName="targetBlocks">
    <type itemref="block:Block"/>
    <cardinality value="list"/>
    <inverseAttribute itemref="doc:styles"/>
  </Attribute>
</Kind>

<Attribute itsName="styles">
  <type itemref="doc:StyleBlock"/>
  <cardinality value="list"/>
</Attribute>

What's especially nice about this system is that when the link happens between a style and a matching block, the block knows about all the matching styles, and the styles know which blocks they apply to. This allows blocks to be blind to the actual styles but still be able to retrieve the styles that they need to apply.

Now we can probably start applying these rules to specific block types. The style system could optionally do rule validation, indicating which style rules can apply to which blocks. I'll have to think about that. One way would be to indicate in the declaration of the style, which rules could actually apply.

Implementation

There are two sides to this mechanism: the widget has to go out and use the style blocks associated with each widget, and the style blocks have to somehow apply their styles to the blocks in question.

Thanks to bidirectional references, it should be easy to find and reference blocks of a given style. Since we generally want to apply styles to blocks as they are rendered, each block just needs to have its applyStyles() method called:

class Block(...):
  def applyStyles(self):
    for styleRule in self.styles:
      styleBlock.applyTo(self)

(this is as opposed to iterating through all styles and trying to apply them to all target blocks)

Then, each style block can apply each style:

class StyleRule(...):
  def apply(self, block):
    for style in self.declarations:
      style.applyTo(block.widget)

class StyleDeclaration(...):
  def applyTo(self, widget):
    raise NotImplemented, "Styles must override apply method!"

class FontStyleDeclaration(StyleDeclaration):
  def applyTo(self, widget):
    widget.SetFont(wx.Font(self.fontSize, self.fontFamily, ...))

Platform issues

This system also allows us to deal with styles on a per-platform basis. For example, let's say the Mac needs a smaller minsize on the duration field because the fonts tend to be narrower. The mac build could have its own parcel.xml that declares:

<Style itsName="MacDurationStyle">
  <applyToBlock itemRef="detailView:CalendarDuration">
  <StyleDeclaration itsName="MinSize">
    <minimumSize value="250"/>
  </StyleDeclaration>
  <declarations itemref="doc:MacDuration/MinSize"/>
</Style>

Note that no special changes were needed to the original block.

Locale issues

A similar pattern can be used on a per-locale basis. Certain locales can end up pushing UI wider or taller and this allows the translators to have a degree of control over this.

Simpler solutions

We could accomplish some of the stuff in the Goals section above with simpler solutions. It seems only fair to discuss them, and their relative merits and failures.

  • Easier management of application appearence, across the whole application: The current characterStyle/colorStyles could be centralized as-is by moving them all to one or two parcel.xml's. The styles themselves could be given names that correspond to their function, rather than their appearance. i.e. instead of having a "black" colorStyle and "arial" characterStyle, you'd have a "dialogBoxText" color style which would give black text and the Arial font.
  • Rationalizing fonts across the product: the previous solution could help us get there as well.
  • Separate appearance from function: We could introduce other styles beyond characterStyle/colorStyle, or simply combine them into generic style objects that are still referenced the same way. We wouldn't use the bidirectionality, so blocks would still need to be explicit about what styles they wanted to apply.
  • Mechanism to specify per-platform styles: we could still have a set of parcel.xml files for each platform. The difference would be that the blocks would still have to reference them directly. For example, you'd introduct a platformStyle XML namespace, and then reference a style within that platform. For the above Mac example, the CalendarDuration? block would have to say something like:
         <AEBlock itsName="CalendarDuration>
           <PresentationStyle itemref="platformStyle:CalDurationStyle"/>
         </AEBlock>
         
    And then the platformStyle would have to be implemented in each platform's parcel.xml
  • Support for locale-specific styling information: there's a similar problem for per-platform styling information.

Baby Steps

The above proposal, and some of the stuff from Simpler Solutions, can be implemented in stages. Here's my suggestion:

  1. Centralize the style information for much of the product in parcels/osaf/styles/parcel.xml and fix up itemref's to point there.
  2. Refactor CharacterStyle?, ColorStyle?, and PresentationStyles? into one unit (StyleBlock?)
  3. Move items that aren't already in this style into it, like size, minimumSize, border, and so forth.
  4. Build in capability for bidirectional styles without dropping support for old style mechanism
  5. Migrate existing StyleBlocks? to use bidirectional mechanism
  6. Remove support for old style mechanism

Except for the last step, each of these steps can be completed without affecting backwards compatibility, and without breaking any existing functionality. So if not all stages make a given milestone, that milestone is not at risk.

Add your comments here:

Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r5 < r4 < r3 < r2 < r1 | More topic actions
 
Open Source Applications Foundation
Except where otherwise noted, this site and its content are licensed by OSAF under an Creative Commons License, Attribution Only 3.0.
See list of page contributors for attributions.