Changes from Version 1 of documentation/tutorials/WindowTemplate

Show
Ignore:
Author:
peterc (IP: 192.168.0.1)
Timestamp:
11/29/07 10:19:01 (10 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • documentation/tutorials/WindowTemplate

    v0 v1  
     1[[PageOutline(1-5, Contents)]] 
     2= Window Templating Tutorial = 
     3 
     4This tutorial will take you step-by-step through the development of the RML and RCSS of the window template we used for Rocket Invaders from Mars. By the end of this tutorial you'll be able to create complex, flexible templates for your own application. 
     5 
     6To go through this tutorial you'll need an understanding of [wiki:documentation/RML RML] and [wiki:documentation/RCSS RCSS]. 
     7 
     8== Step 1: Taking a look == 
     9 
     10Download the tutorial source here and extract the file into the directory you've installed Rocket into. Compile the source and run the program; you should end up seeing this: 
     11 
     12[[Image(tutorial_template_1.gif, nolink)]] 
     13 
     14All the the program does is load and show the document defined in data/tutorial.rml. The RML itself file references data/tutorial.rcss and data/tutorial.png. All we're interested in is the RML file and the RCSS file; open them both and take a look. 
     15 
     16All we've got to start with in the RML is a simple document with no child elements. It has a class of 'window' which, as you'll see in the RCSS, is what is giving it the glassy background decorator. The style declared in the document's header tag specifies a fixed width and height for the document (so it has some dimensions), and gives it auto margins to centre it within the context. As for the RCSS, we've got nothing more than the a font specification and a tiled-box decorator for drawing the background. And that's it! Simple, but not very useful as a window template yet. 
     17 
     18== Step 2: Adding a title bar == 
     19 
     20We're going to look at adding the title bar first. What we need for this is: 
     21 
     22 * an element in the top-left of the window 
     23 * a horizontal tiling decorator on the element to render the title bar 
     24 * a way to set the text on the element so we can procedurally change the title 
     25 * a handle on the element so we can drag the window 
     26 
     27=== Defining the title elements === 
     28 
     29Add a 'div' element into the body, and give it an ID of 'title-bar'. The 'div' is a block-level element, as defined in the base style sheet, so by default it will size itself horizontally to take up the entire length of its parent element, the window. We could put the decorator straight into this element, but then it will be as long as the window; we only want it as long as the title text. So inside the 'div' element, add a 'span' element, and give it an ID of 'title'. Add a dummy title string inside the span, so we can easily get some text up there. By now, you should have this: 
     30 
     31{{{ 
     32<body class="window"> 
     33        <div id="title-bar"> 
     34                <span id="title">Dummy Title</span> 
     35        </div> 
     36</body> 
     37}}} 
     38 
     39Fire up the application; not much to look at yet, but we'll get there! 
     40 
     41The title text is rendering with the body text; we'll need something much bigger and bolder. In the RCSS file, add a new rule for the title span, assigning it a font size of 22, and a weight of bold. And why not a black text shadow while we're here? The rule should look something like this: 
     42 
     43{{{ 
     44div#title-bar span 
     45{ 
     46        font-size: 22; 
     47        font-weight: bold; 
     48 
     49        text-shadow: 2px 2px black; 
     50} 
     51}}} 
     52 
     53That looks a bit better, but still no decorator. 
     54 
     55=== Setting up the decorator === 
     56 
     57If you open up the file 'tutorial.png', you'll see it contains image information for the title bar (among other things). We'll need to now define the title element's decorator using the texture coordinates from this image. So, in the same rule that you added before, we declare the decorator: 
     58 
     59{{{ 
     60        background-decorator: tiled-horizontal; 
     61}}} 
     62 
     63Then, define the left side of the decorator: 
     64 
     65{{{ 
     66        background-left-image: tutorial.png 147px 0px 229px 85px; 
     67}}} 
     68 
     69Note that all the coordinates are in pixels from the top-left of the image, rather than 0.0 to 1.0; this means that if you have to resize the image later (to fit new elements, icons, etc) you don't have to recalculate all of your decorator coordinates. You can specify texture coordinates directly if you'd like however, just leave off the 'px' or use the '%' sign. 
     70 
     71For the centre, we want to stretch the middle pixels on the texture between the left and right sides all the way across. 
     72 
     73{{{ 
     74        background-center-image: tutorial.png stretch 229px 0px 230px 85px; 
     75}}} 
     76 
     77And lastly, the right side: 
     78 
     79{{{ 
     80        background-right-image: tutorial.png 231px 0px 246px 85px; 
     81}}} 
     82 
     83Run the application again and see what we've got. 
     84 
     85[[Image(tutorial_template_2.gif, nolink)]] 
     86 
     87Well that looks pretty crap! Because the 'span' element is inline, its height is derived from the height of its content; in this case, the dummy title text. The decorator squishes itself down to fit into the element. We can't throw a 'height' RCSS property in there either, as inline elements (except in a few cases) cannot have their heights set directly. So what do we do? Padding! Margins, padding and borders can all be set on inline elements and, while they don't affect the vertical positioning of the element, they do affect the size of the element and influence the position of child elements. So we can use padding to set the element to the right size and position the text right in the middle of the title bar. 
     88 
     89Add some padding along the top of the span to begin with: 
     90 
     91{{{ 
     92        padding-top: 50px; 
     93}}} 
     94 
     95Take a look at the result. The element is now 50 pixels bigger, so the title bar is looking a bit healthier, and the text has been pushed to the bottom of the element. So how big do we need to make the title bar? If you look at the decorator declaration, you can see the title bar image is 85 pixels high. Therefore ideally the element should also be 85 pixels high. So we've got to add padding to get it that high - but how high is it now? 
     96 
     97To find out, you can use the debugger; press SHIFT-~ to open the debugging menu. Click on the 'Element Info' button, and then click on the title bar element. The info pane should change to show you a heap of information on the element, including the properties defined on it and their source, dimensions, children and ancestor elements. You can see the height of the element under the 'Position' heading; it work out to be 80px. So, we have to add another 5px of vertical padding. 
     98 
     99Set the padding to 55px and take a look; the element should now be 85px high. Now shift some of the padding to the bottom and have a play around to get the text centered. I found the following combination got the text to look in the right place: 
     100 
     101{{{ 
     102        padding-top: 13px; 
     103        padding-bottom: 42px; 
     104}}} 
     105 
     106There's not much space to the left or right of the text yet; easy fixed, just add some left and right padding! We used the following for Rocket Invaders from Mars: 
     107 
     108{{{ 
     109        padding-left: 85px; 
     110        padding-right: 25px; 
     111}}} 
     112 
     113But have a play and see what works best. By now the title bar rule should look something like this: 
     114 
     115{{{ 
     116div#title-bar span 
     117{ 
     118        padding-left: 85px; 
     119        padding-right: 25px; 
     120        padding-top: 17px; 
     121        padding-bottom: 48px; 
     122 
     123        font-size: 22; 
     124        font-weight: bold; 
     125 
     126        text-shadow: 2px 2px black; 
     127 
     128        background-decorator: tiled-horizontal; 
     129        background-left-image: tutorial.png 147px 0px 229px 85px; 
     130        background-center-image: tutorial.png stretch 229px 0px 230px 85px; 
     131        background-right-image: tutorial.png 231px 0px 246px 85px; 
     132} 
     133}}} 
     134 
     135And the application should be looking like this: 
     136 
     137[[Image(tutorial_template_3.gif, nolink)]] 
     138 
     139=== Placing the title bar === 
     140 
     141As I'm sure you'll have noticed by now, the title bar's in the wrong place! This can be fixed a number of ways, such as negative margins on the containing title element, margins on the body, etc, but we chose to solve it by positioning the title-bar element. To do this, add a new rule for the 'title-bar' element, declaring it as absolutely positioned. 
     142 
     143{{{ 
     144div#title-bar 
     145{ 
     146        position: absolute; 
     147} 
     148}}} 
     149 
     150That in itself won't do much, but now we can play around with its position to the pixel with the 'top' and 'left' properties. 
     151 
     152If you don't set the 'top' or 'left' (or 'right' or 'bottom') to change the height of an absolutely positioned element, it will stay where the layout engine positioned it, but be removed from flow so it will not affect the layout of future elements. If you do change it position, with the 'top' property for example, its top edge will be aligned against the top padded edge of its offset parent (in our case, the window), offset by the amount of the property. 
     153 
     154We need to shift the element up, so we use the 'top' property to do this. If we declare 'top: 0px;', it will be aligned at the very top of the window; so, exactly where it is now. To move it up, specify a negative number. 43 pixels seems to do the trick. 
     155 
     156{{{ 
     157div#title-bar 
     158{ 
     159        position: absolute; 
     160        top: -43px; 
     161} 
     162}}} 
     163 
     164=== Adding a handle === 
     165 
     166We still need a handle so we can drag the window around. This is easy; Rocket ships with a 'handle' element that can do just that (or resize an element). In the RML, wrap the contents of the 'title-bar' element with a 'handle' element. You can set its move target with the 'move_target' attribute; set it to '#document' so it knows to move its parent document when it is dragged. You should end up with this: 
     167 
     168{{{ 
     169<div id="title-bar"> 
     170        <handle move_target="#document"> 
     171                <span id="title">Dummy Title</span> 
     172        </handle> 
     173</div> 
     174}}} 
     175 
     176Now you should be able to drag the window around by holding onto the title. The application should now be looking something like this: 
     177 
     178[[Image(tutorial_template_4.gif, nolink)]] 
     179 
     180== Step 3: Placing the content == 
     181 
     182Now we've got a title bar, we need somewhere to place the actual page content. What we're after is: 
     183 
     184 * an empty block element we can put our page content into 
     185 * a vertical scrollbar in case the contents of the page overflow 
     186 
     187Add the block content element now; within the 'body' tag, just below the 'title-bar' element. Give it an ID of 'content' so we can identify it. 
     188 
     189{{{ 
     190        <div id="title-bar"> 
     191                <handle move_target="#document"> 
     192                        <span id="title">Dummy Title</span> 
     193                </handle> 
     194        </div> 
     195        <div id="content"> 
     196        </div> 
     197}}} 
     198 
     199Why do we do this, rather than put the content directly into the 'body' element? When we come to convert this document into a reusable document template, we'll need an empty element that all of a document's content is put into. 
     200 
     201Put some dummy content text into the new element and see what we've got. 
     202 
     203[[Image(tutorial_template_5.gif, nolink)]] 
     204 
     205So we've got a couple of issues already: 
     206 
     207 * the title bar's reflection is displaying over the content 
     208 * the content is rendering outside of the window's border 
     209 
     210=== Using z-index === 
     211 
     212All elements have a default z-index of 0, so normally they would be rendered in the order they are declared in the document. This means elements declared further down in the RML are usually rendered on top of earlier elements. However, floating and positioned elements jump to the front of the queue and are always rendered after normal elements with a similar z-index. 
     213 
     214So to bring the content window above the title bar, create a new rule for the content element and assign it a z-index of 1. 
     215 
     216{{{ 
     217div#content 
     218{ 
     219        z-index: 1; 
     220} 
     221}}} 
     222 
     223Much better. 
     224 
     225=== Padding the content area === 
     226 
     227We need to push the document's content area so it appears entirely inside the window border. The decorator we have on the 'body' element will render over the entire padded area of the element, so if padding is added it will force all content away from the edges of the decorated area. 
     228 
     229Add some padding to the 'body' rule and take a look at the result. We found that 10px top / bottom and 15px right / left padding worked out pretty well. Our rule looks like this: 
     230 
     231{{{ 
     232body.window 
     233{ 
     234        background-decorator: tiled-box; 
     235        background-top-left-image: tutorial.png 0px 0px 133px 140px; 
     236        background-top-right-image: tutorial.png 136px 0px 146px 140px; 
     237        background-top-image: tutorial.png stretch 134px 0px 135px 140px; 
     238        background-bottom-left-image: tutorial.png 0px 140px 11px 151px; 
     239        background-bottom-right-image: tutorial.png 136px 140px 146px 151px; 
     240        background-bottom-image: tutorial.png stretch 11px 140px 12px 151px; 
     241        background-left-image: tutorial.png stretch 0px 139px 10px 140px; 
     242        background-center-image: tutorial.png stretch 11px 139px 12px 140px; 
     243 
     244        padding: 10px 15px; 
     245} 
     246}}} 
     247 
     248Nice! Now the content's in the right place, but what happens if there's too much to fit? Try that out now by putting more dummy content into the content element. 
     249 
     250=== Dealing with overflow === 
     251 
     252If you open the debugger again and look at the content element, you can see the problem: 
     253 
     254[[Image(tutorial_template_6.gif, nolink)]] 
     255 
     256We haven't explicitly set the 'height' property on the content element, so it defaults to 'auto'. When calculating the height of a block-level element, 'auto' means it will grow to fit the content, regardless of the size of its containing element. If we set the 'height' property on the content element to '100%', it will force the height to be exactly that of its containing element's content area. Try that and see what we get. 
     257 
     258As you can see, the overflow is still showing up. If you open up the debugger and inspect the content element again, you'll see that the element itself is now the right size, but the overflowing text is still visible. How overflow is handled is determined by the 'overflow' property; it defaults to 'visible', meaning descendant elements are not clipped by the element. Set the 'overflow' property to 'hidden' on the content element; the entire rule for the content element should look like this: 
     259 
     260{{{ 
     261div#content 
     262{ 
     263        height: 100%; 
     264        overflow: hidden; 
     265 
     266        z-index: 1; 
     267} 
     268}}} 
     269 
     270Take a look at the result; the overflowing content is hidden, but we can't get to it! Time to add a scrollbar. 
     271 
     272== Step 4: Adding a scrollbar == 
     273 
     274To tell Rocket a scrollbar is required on the content element, we can change the 'overflow' property from 'hidden' to either 'auto' or 'scroll'. 'scroll' will put a scrollbar around the element all the time, even if it isn't required; 'auto' will only put a scrollbar on an axis with overflow. 
     275 
     276Rocket also supports different overflow properties per axis, so you can (for example) set vertical overflow to 'scroll' and horizontal overflow to 'hidden' if you wish. 
     277 
     278Change the 'overflow' property on the content element to 'auto' or 'scroll' and check out the result. 
     279 
     280[[Image(tutorial_template_7.gif, nolink)]] 
     281 
     282=== Resizing the scrollbar === 
     283 
     284Well that doesn't look right! So what's happened here? When an element needs to generate a vertical scrollbar, it creates a block-level child element with a tag of 'scrollbarvertical' and anchors it to the right edge of the element. As it is block-level, its width defaults to 'auto' so it occupies the entire content area of its parent, the content element. So there's no space for the text! Not only that, but we haven't attached a decorator to the scrollbar elements so we can't actually see it yet. 
     285 
     286Elements that Rocket dynamically creates, like the scrollbar, can be styled through RCSS like normal elements. All we need to do is create a rule that will match the element 'scrollbarvertical'. First thing to do? Set its width so it doesn't occupy the whole element. The scrollbar graphics we've designed for Rocket Invaders from Mars are designed to be 27 pixels wide. This RCSS rule will resize the scrollbar: 
     287 
     288{{{ 
     289scrollbarvertical 
     290{ 
     291        width: 27px; 
     292} 
     293}}} 
     294 
     295That's looking a bit better; of course, we can actually ''see'' the scrollbar yet, but it is there. You can drag the window up and down if you manage to click in the right place. 
     296 
     297=== Decorating the scrollbar === 
     298 
     299The scrollbar itself has four child elements that can be individually sized and decorated. These are tagged: 
     300 
     301 * 'slidertrack', the track that runs from the top to the bottom of the scrollbar underneath the bar. 
     302 * 'sliderbar', the bar (or knob, thumb, etc) that lies on top of the track and can be dragged up and down. 
     303 * 'sliderarrowinc', 'sliderarrowdec', the buttons you can click to move the bar up or down the track. 
     304 
     305We'll start by decorating the track. If you open up 'tutorial.png' again, you'll see under the window background there's all the scrollbar images. We'll save you all the hard work, so here's the pixel offsets for the track's decorator: 
     306 
     307{{{ 
     308scrollbarvertical slidertrack 
     309{ 
     310        background-decorator: tiled-vertical; 
     311        background-top-image: tutorial.png 56px 199px 83px 201px; 
     312        background-center-image: tutorial.png stretch 56px 201px 83px 202px; 
     313        background-bottom-image: tutorial.png 56px 203px 83px 204px; 
     314} 
     315}}} 
     316 
     317Fire up the application again, and you've got a scrollbar track! As you can see it is lying in the content area of the window, so doesn't yet stretch to the edges like it should; we'll fix that up later. 
     318 
     319We've got more vertical decorators to define for the bar element: 
     320 
     321{{{ 
     322scrollbarvertical sliderbar 
     323{ 
     324        width: 23px; 
     325 
     326        background-decorator: tiled-vertical; 
     327        background-top-image: tutorial.png 56px 152px 79px 175px; 
     328        background-center-image: tutorial.png stretch 56px 175px 79px 175px; 
     329        background-bottom-image: tutorial.png 56px 176px 79px 198px; 
     330} 
     331}}} 
     332 
     333Note that we set the width to 23 pixels, as the image for the bar is only 23 pixels wide. If you take a look at the result, you'll notice the bar is now decorated, but is displaying on top of the border of the track. To be in the right place, we need to move it 4 pixel to the right. How can we do this? With a margin on the left side! Add a four pixel left margin to the 'sliderbar' and it'll be in the right place. 
     334 
     335One last thing on the bar; as no height has been explicitly set on the element, the scrollbar will resize it to fit the requirements of the element it is attached to. As the content gets taller or the element gets shorter, the bar will shrink to match. However, we don't want it shrinking below a certain size, as then the image will need to be shrunk and it won't look the best. The smallest size it can display at without shrinking is 46 pixels; you can set the 'min-height' property then to '46px' to prevent it from going below that. 
     336 
     337Now the window should be looking like this: 
     338 
     339[[Image(tutorial_template_8.gif, nolink)]] 
     340 
     341=== Adding the arrows === 
     342 
     343So where are the arrows? If you don't resize them yourself, they'll stay hidden. If we want to add them, first step is resizing them. Add a rule to resize 'sliderarrowinc' and 'sliderarrowdec' elements to 27 x 24 pixels: 
     344 
     345{{{ 
     346scrollbarvertical sliderarrowdec, 
     347scrollbarvertical sliderarrowinc 
     348{ 
     349        width: 27px; 
     350        height: 24px; 
     351} 
     352}}} 
     353 
     354And add decorators to each of them: 
     355 
     356{{{ 
     357scrollbarvertical sliderarrowdec 
     358{ 
     359        icon-decorator: image; 
     360        icon-image: tutorial.png 0px 152px 27px 176px; 
     361} 
     362 
     363scrollbarvertical sliderarrowinc 
     364{ 
     365        icon-decorator: image; 
     366        icon-image: tutorial.png 28px 152px 55px 176px; 
     367} 
     368}}} 
     369 
     370And hey presto, we've got arrows! The scrollbar automatically resizes the slider track to fit the arrows in. 
     371 
     372=== Fitting the scrollbar === 
     373 
     374How do we now resize the scrollbar so it fits in nicely with the window? We can give the scrollbar element itself negative margins, which cause it to push outside of its parent's content area. If you take a take a screenshot of the application and paste it into your paint program, you can see exactly how many pixels it needs to be shifted to the right, and extended on the top and bottom. We worked out 6 pixels up and down, 11 pixels to the left. Add these properties as negative margins to the 'scrollbarvertical' rule: 
     375 
     376{{{ 
     377scrollbarvertical 
     378{ 
     379        width: 27px; 
     380        margin-top: -6px; 
     381        margin-bottom: -6px; 
     382        margin-right: -11px; 
     383} 
     384}}} 
     385 
     386And from that we've got: 
     387 
     388[[Image(tutorial_template_9.gif, nolink)]] 
     389 
     390=== Adding hover and click decoration === 
     391 
     392Now the scrollbar is functional, but there's no extra decoration for clicks and mouse-overs. You can add these in easily by changing the texture coordinates on the decorations for the :hover and :active pseudo-classes. For example, add the following rule to put in hover decoration on the bar: 
     393 
     394{{{ 
     395scrollbarvertical sliderbar:hover 
     396{ 
     397        background-top-image-s: 80px 103px; 
     398        background-center-image-s: 80px 103px; 
     399        background-bottom-image-s: 80px 103px; 
     400} 
     401}}} 
     402 
     403== Step 5: Templating the document == 
     404 
     405Now we've got a complete window document. But what we really want is a window template, so we can easily create new documents that reuse the layout. 
     406 
     407=== Creating the template === 
     408 
     409Make a copy of the RML file and call it 'template.rml'. To change it from a document into a template, change the top 'rml' tag to 'template'. The 'template' tag needs a couple of bits of information; the name of the template, set by the 'name' attribute, and the ID of the element where the document's content should go, set by the 'content' attribute. The final tag should look like: 
     410 
     411{{{ 
     412<template name="window" content="content"> 
     413}}} 
     414 
     415Delete the title and the style declaration in the template header; we won't need those. Also remove the contents of the content element. You should end up with a template file looking like this: 
     416 
     417{{{ 
     418<template name="window" content="content"> 
     419<head> 
     420        <link type="text/css" href="../../assets/rkt.rcss"/> 
     421        <link type="text/css" href="tutorial.rcss"/> 
     422</head> 
     423<body class="window"> 
     424        <div id="title-bar"> 
     425                <handle move_target="#document"> 
     426                        <span id="title">Dummy Title</span> 
     427                </handle> 
     428        </div> 
     429        <div id="content"> 
     430        </div> 
     431</body> 
     432</template> 
     433}}} 
     434 
     435=== Adapting the document === 
     436 
     437Now we want to change the document we've been working on to use the new template. Open the 'tutorial.rml' file. 
     438 
     439The links to the RCSS files are no longer required, as the template will load them. So both of those in the header should go. However, we need to add another link to the template. Add a new link of type 'text/template', with the href pointing to 'template.rml'. 
     440 
     441Both the title and style declaration stay; these are unique to this document. 
     442 
     443The 'body' tag needs to be changed so it knows which template to inject itself into. Do this with the 'template' attribute, and set it to the name of the template. In our case, we called the template 'window'. Now delete the window elements inside the 'body' element, except for the actual content. You should end up with something like this: 
     444 
     445{{{ 
     446<rml> 
     447<head> 
     448        <link type="text/template" href="template.rml"/> 
     449        <title>Window</title> 
     450        <style> 
     451                body 
     452                { 
     453                        width: 400px; 
     454                        height: 300px; 
     455 
     456                        margin: auto; 
     457                } 
     458        </style> 
     459</head> 
     460<body template="window"> 
     461        Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 
     462</body> 
     463</rml> 
     464 
     465}}} 
     466 
     467And we're done. When the document is loaded, it will load the template and inject the contents of its 'body' into the 'content' element of the template. Any new windows you make can use the same template. If you ever want to reskin your window, or maybe design an entirely new one, you only have to alter the template file! 
     468 
     469== Step 6: Setting the title == 
     470 
     471We still have one last thing to implement; the title of the document isn't set on the title bar. We'll show you how to do this through the C++ API, but you can also easily do this through a scripting interface. 
     472 
     473The document is loaded on line 58 of main.cpp. Before the document is rendered, we want to get the 'span' element containing the dummy title and set its inner RML content to the title of the document we just loaded. To fetch the element, call the 'GetElementById()'. Once you have the element, you can remove all of its children and set new RML content with 'SetInnerRML()'. The document itself has the 'GetTitle()' function to fetch the title. 
     474 
     475{{{ 
     476        // Load and show the tutorial document. 
     477        Rocket::Core::ElementDocument* document = context->LoadDocument("data/tutorial.rml"); 
     478        document->GetElementById("title")->SetInnerRML(document->GetTitle()); 
     479        document->Show(); 
     480}}} 
     481 
     482In a real-world application, you can automate this by putting an 'onload' event into the document template and setting the title in the event handler.