Sunday, July 24, 2011

Experimenting with ribbons in LiveCode (part 2)

In an earlier post, we kicked off a series on the construction of a ribbon using pure LiveCode. For now, we'll concentrate on the Windows 7 Wordpad look-and-feel, but future posts will refine it for cross-platform consistency. After looking into Ribbon components such as Infragistics NetAdvantage WinToolbars and Microsoft Ribbon for WPF, I pondered how a developer would define and customize our ribbon implementation.

For better or worse, I opted for the developer setting the 'ribbonText' property of our custom control group, and that this property would be an XML structure. So let's break the ribbon down into pieces, break those pieces down into other pieces, and so on. Initially, one would think that the ribbon is a set of tabs, each with their own groups, each with their own buttons. But there's a little more to the story: there are several types of buttons, and aren't we glossing over the application menu and quick access toolbar?
Plus, we need some centralized management for what these buttons, menuitems, etc. look like, what label they have, etc. Microsoft calls it a 'RibbonCommand', but since theseribbon items could also be toggle buttons, that spounded odd - so I think we'll stick with the Infragistics terminology of 'Tool' - far more neutral. This tool will also include a 'message' that is sent when the user clicks the button.

So here's a first example of our ribbonText XML structure:

<?xml version="1.0"?>
<Ribbon>
<Tools>
<Tool ToolId="CutTool" Label="Cut" Message="CutText"/>
<Tool ToolId="CopyTool" Label="Copy" Message="CopyText"/>
</Tools>
<Tabs>
<Tab TabId="HomeTab" Label="Home">
<Groups>
<Group GroupId="ClipboardGroup" Label="Clipboard">
<GroupItems>
<CommandButton ToolId="CutTool" />
<CommandButton ToolId="CopyTool" />
</GroupItems>
</Group>
</Groups>
</Tab>
</Tabs>
</Ribbon>


As you can see, the Tool entry defines the label and message, and the CommandButton instance referenes that Tool via its ToolId attribute. Now let's add two more things to the mix: DropMenuButton and SplitMenuButton - clicking on the former will drop down a menu, whereas clicking the latter has different results, depending on where you click: it either executes the main function, or drops down a menu.

<?xml version="1.0"?>
<Ribbon>
<Tools>
<Tool ToolId="CutTool" Label="Cut" Message="CutText"/>
<Tool ToolId="CopyTool" Label="Copy" Message="CopyText"/>
<Tool ToolId="PasteTool" Label="Paste" Message="PasteText"/>
<Tool ToolId="PasteSpecialTool" Label="Paste special" Message="PasteSpecial"/>
<Tool ToolId="InchesTool" Label="Inches" Message="SwitchToInches"/>
<Tool ToolId="CentimetersTool" Label="Centimeters" Message="SwitchToCentimeters"/>
<Tool ToolId="PointsTool" Label="Points" Message="SwitchToPoints"/>
</Tools>
<Tabs>
<Tab TabId="HomeTab" Label="Home">
<Groups>
<Group GroupId="ClipboardGroup" Label="Clipboard">
<GroupItems>
<SplitMenuButton ToolId="PasteTool">
<MenuItems>
<CommandButton ToolId="PasteTool" />
<CommandButton ToolId="PasteSpecialTool" />
</MenuItems>
</SplitMenuButton>
<CommandButton ToolId="CutTool" />
<CommandButton ToolId="CopyTool" />
</GroupItems>
</Group>
</Groups>
</Tab>
<Tab TabId="ViewTab" Label="View">
<Groups>
<Group GroupId="SettingsGroup" Label="Settings">
<GroupItems>
<DropMenuButton Label="Measurement units">
<MenuItems>
<CommandButton ToolId="InchesTool" />
<CommandButton ToolId="CentimetersTool" />
<CommandButton ToolId="PointsTool" />
</MenuItems>
</DropMenuButton>
</GroupItems>
</Group>
</Groups>
</Tab>
</Tabs>
</Ribbon>


I think this will do as a first draft of the ribbonText XML structure. Of course, an XML structure needs a good document type definition to verify that what is coming in is valid data; and since the XML structure is still evolving, we should define some specification version into the structure. So let's call the current structure SpecificationVersion 0.1 - and here's the DTD:

<!ELEMENT Ribbon
(Tools,Tabs)>
<!ELEMENT Tools (Tool)+>
<!ELEMENT Tool EMPTY>
<!ELEMENT Tabs (Tab)+>
<!ELEMENT Tab (Groups)>
<!ELEMENT Groups (Group)+>
<!ELEMENT Group (GroupItems)>
<!ELEMENT GroupItems
(CommandButton|SplitMenuButton|DropMenuButton)*>
<!ELEMENT CommandButton EMPTY>
<!ELEMENT ToggleButton EMPTY>
<!ELEMENT SplitMenuButton (MenuItems)>
<!ELEMENT DropMenuButton (MenuItems)>
<!ELEMENT MenuItems
(CommandButton|SplitMenuButton|DropMenuButton)*>
<!ATTLIST Ribbon
SpecificationVersion CDATA #FIXED "0.1">
<!ATTLIST Tool
ToolId ID #REQUIRED
Label CDATA #REQUIRED
Message CDATA #IMPLIED
SmallIcon CDATA #IMPLIED
LargeIcon CDATA #IMPLIED>
<!ATTLIST Tab
TabId ID #REQUIRED
Label CDATA #REQUIRED>
<!ATTLIST Group
GroupId ID #REQUIRED
Label CDATA #REQUIRED>
<!ATTLIST CommandButton
ToolId IDREF #REQUIRED>
<!ATTLIST SplitMenuButton
ToolId IDREF #REQUIRED>
<!ATTLIST DropMenuButton
Label CDATA #REQUIRED>


Since we added a required attribute to the Ribbon root tag, our example XML needs to be slightly amended to:

<?xml version="1.0"?>
<Ribbon SpecificationVersion="0.1">
<Tools>
<Tool ToolId="CutTool" Label="Cut" Message="CutText"/>
<Tool ToolId="CopyTool" Label="Copy" Message="CopyText"/>
<Tool ToolId="PasteTool" Label="Paste" Message="PasteText"/>
<Tool ToolId="PasteSpecialTool" Label="Paste special" Message="PasteSpecial"/>
<Tool ToolId="InchesTool" Label="Inches" Message="SwitchToInches"/>
<Tool ToolId="CentimetersTool" Label="Centimeters" Message="SwitchToCentimeters"/>
<Tool ToolId="PointsTool" Label="Points" Message="SwitchToPoints"/>
</Tools>
<Tabs>
<Tab TabId="HomeTab" Label="Home">
<Groups>
<Group GroupId="ClipboardGroup" Label="Clipboard">
<GroupItems>
<SplitMenuButton ToolId="PasteTool">
<MenuItems>
<CommandButton ToolId="PasteTool" />
<CommandButton ToolId="PasteSpecialTool" />
</MenuItems>
</SplitMenuButton>
<CommandButton ToolId="CutTool" />
<CommandButton ToolId="CopyTool" />
</GroupItems>
</Group>
</Groups>
</Tab>
<Tab TabId="ViewTab" Label="View">
<Groups>
<Group GroupId="SettingsGroup" Label="Settings">
<GroupItems>
<DropMenuButton Label="Measurement units">
<MenuItems>
<CommandButton ToolId="InchesTool" />
<CommandButton ToolId="CentimetersTool" />
<CommandButton ToolId="PointsTool" />
</MenuItems>
</DropMenuButton>
</GroupItems>
</Group>
</Groups>
</Tab>
</Tabs>
</Ribbon>


Next time, we'll work on actually turning such definitions into actual controls in our LiveCode custom control group, so stay tuned!

Sunday, July 10, 2011

Experimenting with ribbons in LiveCode (part 1)

In the previous post, we kicked off a series on the construction of a ribbon using pure LiveCode. For now, we'll concentrate on the Windows 7 Wordpad look-and-feel, but future posts will refine it for cross-platform consistency. Let's get started by creating a new stack "qrtRibbonExperiment" in LiveCode, with two substacks "qrtRexBehaviors" and "qrtRexTemplate" - the mainstack will be a launchpad whereas the substacks will each provide support for the experiment.

The stack qrtRexBehaviors will contain all the behavior scripts as a series of buttons. The stack qrtRexTemplate will be used to as a template to 'clone' isntance stacks from. This way, you have a skeleton of a document editor to learn from as well.

Peeking ahead at the actual ribbon custom control itself, we note that we'll need 5 areas, which we'll implement as subgroups, each with their own behavior script:
- the application menu in the topleft
- the tab items in the topright (minimize and/or help/customize)
- the ribbon tabs between these two blocks at the top
- the ribbon groups in the center
- the quick access toolbar at the bottom

First things first - the main stack:



It contains three simple buttons and some information about this experiment. Nothing spectacular there, so we'll move along to the behaviors stack:



As you can see, it contains seven buttons right now, aptly named after the different items they provide the behavior for. The number of buttons will expand as we add moire features, but this will do for now. Open each of the button scripts for editing and move on to the template stack.

We'll start by creating the menubar for the stack - even though it's only for Mac, it pays off to put it in straight away. Just go to the 'Tools' menu, and select the option 'Menu Builder' ; then click on the 'New...' button to create a menubar called 'qrtRexMenubar' ; not feeling particularly adventurous, we'll use the prefab set for now.
To make the next step easier, uncheck the 'Set as stack Menu bar' box. Now close the menu builder, and you'll see the menubar as part of your stack. When you set the menubar property of the stack, the stack content moves upward, and that's why I want to help you visualize where the controls end up. The menubar group should have its margins set to 4 and thus have a bottom of 22 - which wiill be our anchor point for the ribbon custom control group.

So now we'll add a rectangle graphic as background for our ribbon - just draw one, and set its rectangle using the property inspector to: -1, 21, 401, 161. Set its name to 'Ribbon_Background', make it opaque and set its fill color to a light gray (I picked 'Silver' from the Mac crayons color set).

Next we'll add the background rectangle graphics for each of the ribbon areas:
- ApplicationMenu_Background -> rect: -1, 21, 70, 46 | color: salmon
- TabHeaderItems_Background -> rect: 350, 21, 401, 46 | color: banana
- RibbonTabs_Background -> rect: 70, 21, 350, 46 | color: honeydew
- RibbonGroups_Background -> rect: -1, 46, 401, 137 | color: lavender
- QuickAccessToolbar_Background -> rect: -1, 137, 401, 163 | color: melon

In case you're wondering about the colors: of course they don't match the ribbon color scheme - but they'll help visualize errors in our resizing scripts. But before we start scripting, let's create the necessary custom control groups out of these background graphics.
Once you've grouped the graphic, set the containing group's lockLocation property to true and change its rectangle to clip to the edges of the window
- ApplicationMenu_Background -> group: ApplicationMenu | rect: 0, 21, 70, 46
- TabHeaderItems_Background -> group: TabHeaderItems | rect: 350, 21, 400, 46
- RibbonTabs_Background -> group: RibbonTabs | rect: 70, 21, 350, 46
- RibbonGroups_Background -> group: RibbonTabs | rect: 0, 46, 400, 137
- QuickAccessToolbar_Background -> group: QuickAccessToolbar | rect: 0, 137, 400, 163

Then take those five groups, and the rectangle graphic 'Ribbon_Background', and group everything once more; name that outer group 'Ribbon' and set its margins to 0 as well, before settings its lockLocation to true and its rectangle to 0, 21, 400, 163.
We end up with something like this:



Now we can start setting the behaviors and do some scripting at last. Open the message box, and execute the following seven lines one by one:
set the behavior of this stack to the long id of button "qrtRexStackBehavior" of stack "qrtRexBehaviors"
set the behavior of group "Ribbon" to the long id of button "qrtRexRibbonBehavior" of stack "qrtRexBehaviors"
set the behavior of group "ApplicationMenu" to the long id of button "qrtRexApplicationMenuBehavior" of stack "qrtRexBehaviors"
set the behavior of group "TabHeaderItems" to the long id of button "qrtRexTabHeaderItemsBehavior" of stack "qrtRexBehaviors"
set the behavior of group "RibbonTabs" to the long id of button "qrtRexRibbonTabsBehavior" of stack "qrtRexBehaviors"
set the behavior of group "RibbonGroups" to the long id of button "qrtRexRibbonGroupsBehavior" of stack "qrtRexBehaviors"
set the behavior of group "QuickAccessToolbar" to the long name of button "qrtRexQuickAccessToolbarBehavior" of stack "qrtRexBehaviors"


On to the script of the 'qrtRexStackBehavior' button:
##
on resizeStack pNewWidth, pNewHeight
local tRectangle
lock screen
--> resize the ribbon group
put the rectangle of group "Ribbon" into tRectangle
put pNewWidth into item 3 of tRectangle
set the rectangle of group "Ribbon" to tRectangle
--> pass the message
unlock screen
pass resizeStack
end resizeStack
##


Next comes the script of the 'qrtRexRibbonBehavior' button:
##
on resizeControl
local tRectangle
lock screen
--> resize the background graphic
put the rectangle of graphic "Ribbon_Background" of me into tRectangle
put (the width of me) + 1 into item 3 of tRectangle
set the rectangle of graphic "Ribbon_Background" of me to tRectangle
--> move the tab header items group
set the right of group "TabHeaderItems" of me to the right of me
--> resize the ribbon tabs group
put the rectangle of group "RibbonTabs" of me into tRectangle
put the left of group "TabHeaderItems" of me into item 3 of tRectangle
set the rectangle of group "RibbonTabs" of me to tRectangle
--> resize the ribbon groups group
put the rectangle of group "RibbonGroups" of me into tRectangle
put the width of me into item 3 of tRectangle
set the rectangle of group "RibbonGroups" of me to tRectangle
--> resize the quick access toolbar group
put the rectangle of group "QuickAccessToolbar" of me into tRectangle
put the width of me into item 3 of tRectangle
set the rectangle of group "QuickAccessToolbar" of me to tRectangle
--> pass the message
unlock screen
pass resizeControl
end resizeControl
##


Next comes the script of the 'qrtRexApplicationMenuBehavior' button:
##
--> TODO
##


Next comes the script of the 'qrtRexTabHeaderItemsBehavior' button:
##
--> TODO
##


Next comes the script of the 'qrtRexRibbonTabsBehavior' button:
##
on resizeControl
local tRectangle
--> resize the background graphic
put the rectangle of graphic "RibbonTabs_Background" of me into tRectangle
put (the width of me) + 1 into item 3 of tRectangle
set the rectangle of graphic "RibbonTabs_Background" of me to tRectangle
--> TODO: whatever needs to happen to the group content
--> do not pass the message
end resizeControl
##


Next comes the script of the 'qrtRexRibbonTabsBehavior' button:
##
on resizeControl
local tRectangle
--> resize the background graphic
put the rectangle of graphic "RibbonTabs_Background" of me into tRectangle
put the right of me into item 3 of tRectangle
set the rectangle of graphic "RibbonTabs_Background" of me to tRectangle
--> TODO: whatever needs to happen to the group content
--> do not pass the message
end resizeControl
##


Next comes the script of the 'qrtRexRibbonGroupsBehavior' button:
##
on resizeControl
local tRectangle
--> resize the background graphic
put the rectangle of graphic "RibbonGroups_Background" of me into tRectangle
put (the width of me) + 1 into item 3 of tRectangle
set the rectangle of graphic "RibbonGroups_Background" of me to tRectangle
--> TODO: whatever needs to happen to the group content
--> do not pass the message
end resizeControl
##


Finally comes the script of the 'qrtRexQuickAccessToolbar' button
##
on resizeControl
local tRectangle
--> resize the background graphic
put the rectangle of graphic "QuickAccessToolbar_Background" of me into tRectangle
put (the width of me) + 1 into item 3 of tRectangle
set the rectangle of graphic "QuickAccessToolbar_Background" of me to tRectangle
--> TODO: whatever needs to happen to the group content
--> do not pass the message
end resizeControl
##


Don't worry, there will be more to those scripts soon - bot for now this gives us a set of groups that resize correctly as the stack is resized. You have to start somewhere :-)

In the next installment, we'll build on this foundation by introducing the 'ribbonText' property - an XML structure that describes the content of the ribbon. In the meantime, you can download the first draft of our ribbon experiment here.

Experimenting with ribbons in LiveCode (prelude)

It's a rainy summer afternoon here in Belgium - what better time to pick up on an old experiment, and share the thought process with you, my fellow LiveCode developers? As you may have guessed from this post's subject, we'll be building a cross-platform Ribbon control using only LiveCode controls and behaviors.
Ribbons were introduced by Microsoft in Office 2007, and replace the traditional menubar and toolbar combination with a single tabbed toolbar. The primary goal: provide a better structure for the myriad of available options, allowing the user to discover these options without becoming overwhelmed.

It's something Microsoft developers have been acutely aware of for a long time, but their past forays into this type of user experience had been a mixed success. Take adaptive menus: I'm sure I wasn't the only one to loathe the automatic hiding of unused options that first appeared around 2000.
Sure, there was a chevron widget to bring back the hidden options, but the whole approach was confusing as the user interface became unpredictable. Have you ever had to support an application where the user could completely customize the look-and-feel, including menus and toolbars? Then you know the sort of nightmare this can induce.

Anyway, be sure to check out The Story of the Ribbon by Jensen Harris, and his related series of blog entries on this topic: Why the UI?

Now let's take a look at some screenshots of the ribbon across different Microsoft applications. Here's the original Word 2007 edition:



Followed by the latest Word 2010 edition:



And here's WordPad as it first shipped with Windows 7:



Finally, a screenshot of Word 2011 for Mac:



But before we work on implementing it, what are the different parts of the ribbon?



Hmm, looking at all these screenshots, it's clear some compromises are inevitable to build our ribbon in pure LiveCode:

1. Application menu and quick access toolbar
- in Office 2007, the application menu (the Jewel or Orb as it's sometimes referred to) fuses with the quick access toolbar into the window titlebar.
- we could replace the standard window decorations with our own implementations, but this brings its own challenges (such as smooth window resizing)
- in later releases, Microsoft decided the application menu could be toned down a bit, and moved it into a button next to the ribbon tabs, so let's just skip this problem altogether and go for the Windows 7 WordPad approach
- that leaves us with the quick access toolbar in the window titlebar, but as this can also be displayed beneath the ribbon, we'll sidestep the issue and simply move the quick access toolbar to the bottom of the ribbon, shall we?

2. Application menu panel
- before Office 2010, the application menu panel wasn't constrained to the window but in its own layer
- this makes it a bit tricky, but we'll get to it in a future post

3. Windows vs. Mac
- while the Windows version dispensed with the menubar entirely, the Mac version still has a complete menubar, but no application menu button
- we just decided to move the quick access toolbar out of the window titlebar and to the bottom of the ribbon, but on Mac the user has no option to move this toolbar and it's always between the window titlebar and the ribbon tabs
- the ribbon group titles have different locations as well: at the bottom of the ribbon group on Windows, at the top on Mac

4. Out of scope
For various reasons, the following ribbon features won't be in scope for this experiment
- contextual tabs
- galleries
- enhanced tooltips

So we'll compromise, but should still have a pretty good ribbon implementation at the end of the experiment - not just a one-off ribbon implementation, but a generic one which you can modify from script. Here's what we're aiming for:



It won't be pixel-prefect, but we can get pretty close. In our first installment, we'll take care of the groundworks. Hang in there - I'll post it shortly.

In the meantime, you should definitely read up on these MSDN articles:
- Ribbon design process
- UX Guidelines - Ribbons