Designing Metacity Themes -

Designing a Metacity theme is a relatively simple task, although tweaking it to meet your needs might prove somewhat time consuming. A Metacity theme is based on a defined XML format and, depending on a given theme, a number of images [generally Portable Network Graphic, PNG, file type].

Getting Started -

The first step that you will need to do is to create a directory in either of the following places -
 $PREFIX/share/themes/[theme_name]/metacity-1/
 $HOME/.themes/[theme_name]/metacity-1/
according to the name of your theme. Although Metacity will detect themes in either location, it is perhaps wise if you keep the theme within your home directory while you write and debug it.

The next step to creating a Metacity theme is by editing a file within that directory called 'metacity-theme-1.xml'. This is the file that contains the XML description for the theme which has the following DTD description. When creating a new Metacity theme, it is useful to take an existing theme, copy across the xml description and then modify it, instead of creating a new theme entirely from scratch.

The first few lines of the format are -

<?xml version="1.0"?>
<metacity_theme>
<info>
   <name>Atlanta</name>
   <author>Havoc Pennington</author>
   <copyright>Havoc Pennington, 2002</copyright>
   <date>September 2, 2002</date>
   <description>Atlanta- a simple theme using Gtk+ default theme</description>
</info>

   <!-- This is where we need to start specifying your theme -->

</metacity_theme>

Theme Basics -

Before we go into a detailed explanation of each part, we will give an overview of the steps taken.

Supported window types

There are 6 specific window types that Metacity recognizes -

normal a normal top-level window
dialog a dialog window
modal_dialog a dialog window that has modal state ie. intervention required by user before they can interact with it's parent window
menu a pinnable menu window ie. tearoff menu
utility a small persistant utility window ie. paletde or toolbox
border a window that should not show any decorations typically ie. full screen window


Styles

For each window type, you need to map a 'style set' [or window decoration] onto it. Each 'style set' is a composition of styles for several different 'frame states'. You will need to specify how each of these frame states should look in your style. Frame states are determined by the following -

 Whether the window is focused or un-focused
 Whether the window is maximized or shaded [or both]
 Whether the window can be vertically, horizontally, vertically and horizontally resized

Each 'frame state' is mapped to a 'frame style'. A frame style is divided up into two different parts - frame 'pieces' and window 'buttons'. This seperation makes it easier to construct a Metacity theme.


Frame pieces

If you omit any of the pieces, then nothing will be drawn for that piece. Metacity recognises the following frame pieces -

entire_background the whole frame of the window which is drawn first
titlebar area above the application's window
titlebar_middle area of titlebar not considered an 'edge' piece
left_titlebar_edge area to the left side of the titlebar
right_titlebar_edge area to the right side of the titlebar
top_titlebar_edge area to the top of the titlebar
bottom_titlebar_edge area to the bottom of the titlebar
title area containing the title
left_edge the left edge of the frame
right_edge the right edge of the frame
bottom_edge the bottom edge of the frame
overlay same as entire_background, only that it is drawn last


Window buttons

Metacity recognizes the following window buttons -

close Close window button
maximize Maximize window button
minimize Minimize window button
menu Menu button
 
and for the following button positions -
 
left_left_background Specifies the background of the 1st button on the left
left_middle_background Specifies the background of the 2nd button on the left
left_right_background Specifies the background of the 3rd button on the left
right_left_background Specifies the background of the 1st button on the right
right_middle_background Specifies the background of the 2nd button on the right
right_right_background Specifies the background of the 3rd button on the right

Metacity window buttons
Metacity window buttons

For each button on your window, you must specify how they should appear under a given number of button states. Metacity recognizes the following button states -

normalHow the button should appear normally for a given frame state
pressedHow the button should appear when 'clicked' [with a mouse]
prelightHow the button should appear when it gets focus


Window menus

The last step to creating a theme is by specifying the menu icons. These appear in the window menu for the following entries -

close Close window icon
maximize Maximize window icon
minimize Minimize window icon
unmaximize Unmaximize window icon
 
and for the following states, that correspond to GtkStateType -
 
normalHow the menu icon should appear normally in the menus
prelightHow the menu icon should appear when the menu entry gets focus
activeHow the menu icon should appear when the menu entry is active [ie. checked]
selectedHow the menu icon should appear when the menu entry gets selected
insensitiveHow the menu icon should appear when the menu entry is insensitive [ie. greyed out]
Metacity window menu
Metacity window menu

Testing your theme

When creating a Metacity theme, it is advisable to use an application especially designed for testing themes, metacity-theme-viewer. To use this application, simply provide the theme that you want to load as an argument. The theme you want to load gets parsed, and if there are errors, it will output any errors to the command line. You must fix any errors before a theme is successfully loaded.

This application is really only useful when designing the style of the window decorations, since you will not be able to interact with the buttons or the window menus.

To switch to your new theme, you need to use either gconftool-2 or using the UI in Desktop Preferences > Theme by selecting the 'Window Border Theme' tab. If using gconftool2-, you need to use the following command -

gconftool-2 --type=string --set /apps/metacity/general/theme [theme_name]

Metacity theme viewer application - for testing themes
Metacity theme viewer application - for testing themes

Deepdown Internals -

Frame Geometry

The first thing you will need to create is the 'frame geometry'. The frame geometry name is referenced later on by a given 'frame style'.

<frame_geometry name="my_frame_geometry">

   <!-- This is where we need to start specifying your frame geometry -->

</frame_geometry>

The following diagram shows the various widths and heights that you can modify in a given frame geometry -

Frame geometry specifications
Frame geometry specifications

The frame geometry has a number of optional attributes that you may provide -

has_title Determines whether the title text height should be included in the height calculator. This defaults to true if not specified.
title_scale Uses Pango markup - xx-small, x-small, small, medium, large, x-large and xx-large. This defaults to pick up your desktop font if not specified.
rounded_top_left Determines whether the top left corner of the window should be rounded. This defaults to false if not specified.
rounded_top_right Determines whether the top right corner of the window should be rounded. This defaults to false if not specified.
rounded_bottom_left Determines whether the bottom left corner of the window should be rounded. This defaults to false if not specified.
rounded_bottom_right Determines whether the bottom right corner of the window should be rounded. This defaults to false if not specified.

<frame_geometry name="normal_geometry">
   <distance name="left_width" value="6"/>
   <distance name="right_width" value="6"/>
   <distance name="bottom_height" value="7"/>
   <distance name="left_titlebar_edge" value="6"/>
   <distance name="right_titlebar_edge" value="6"/>
   <distance name="button_width" value="17"/>
   <distance name="button_height" value="17"/>
   <distance name="title_vertical_pad" value="4"/>
   <border name="title_border" left="3" right="12" top="4" bottom="3"/>
   <border name="button_border" left="0" right="0" top="1" bottom="1"/>
</frame_geometry>

When specifying the frame geometry, it is possible to use inheritance. This simply overwrites any values that you provide that are inherited from the parent.

<frame_geometry name="borderless_geometry" rounded_top_left="true" rounded_top_right="true" parent="normal_geometry">
   <distance name="left_width" value="0"/>
   <distance name="right_width" value="0"/>
   <distance name="bottom_height" value="0"/>
   <distance name="left_titlebar_edge" value="0"/>
   <distance name="right_titlebar_edge" value="0"/>
</frame_geometry>

Instead of specifying button widths and heights, you can specify an aspect ratio instead.

<aspect_ratio name="button" value="1.0"/>


Drawing Operations

'drawing operations' are the heart of designing a Metacity theme. In order to successfully draw a part of the frame, you will need to specify a drawing operation for that 'frame piece'.

<draw_ops name="my_drawing_operation">

   <!-- This is where we need to start specifying your drawing operation -->

</draw_ops>

Operators

Drawing operations are generally forward declared, but can also be placed inline [see example below]. The following list of operators are allowed within a drawing operation -

OperatorMeaningExample
+Plus2 + 3
-Minus5 - 4
*Multiply3 * 2
/Divide10 / 2
%Modulus34 % 3
`max`Maximum4 `max` 5
`min`Minimum7 `min` 3
()Parenthesis(5 * 3) + 5
It should be noted that normal precidence rules apply with all operators.

Constants

Within drawing operations, it is possible to use predefined variables or constants. Constants need to be forward declared and must start with a capital letter.

<constant name="MyConstant" value="3"/>

The following is a list of predefined variables that can be used -

widthWidth of the target area
heightHeight of the target area
object_widthNatural width of the object being drawn
object_heightNatural height of the object being drawn
left_widthDistance from left of frame to client window
right_widthDistance from right of frame to client window
top_heightDistance from top of frame to client window
bottom_heightDistance from bottom of frame to client window
mini_icon_widthWidth of mini icon for window
mini_icon_heightHeight of mini icon for window
icon_widthWidth of large icon
icon_heightHeight of large icon
title_widthWidth of title text
title_heightHeight of title text

Operations

Metacity supports the following operations in any given drawing 'operation' -

line Draws a line with origin (x1, y1) and destination (x2, y2) and given color. Colors can be a color name like "blue", a hexadecimal number like "#FF0099" or a color from a GTK theme given as "gtk:base[NORMAL]" [See some more examples below]. It takes additional attributes width, dash_on_length and dash_off_length, which are both "0" by default.
<line color="#00FF00" x1="3" y1="4" x2="0" y2="height" dash_off_length="2" dash_on_length="3"/>
 
rectangle Draws a rectangle with origin (x,y) and given width and height. It takes optional attributes filled, which is "false" by default.
<rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL] x="0" y="0" width="width" height="height" filled="true"/>
 
arc Draws an arc with origin (x,y) and given width, height, start_angle and extent_angle. It takes optional attributes filled, which is "false" by default.
<arc color="yellow" x="0" y="0" width="width-1" height="height-1" start_angle="30" extent_angle="180"/>
 
tint Applys a tint with origin (x,y) and given width, height, color and alpha.
<tint color="orange" alpha="0.2" x="0" y="0" width="width - mini_icon_width" height="height"/>
 
gradient Draws a gradient with origin (x,y) and given width, height, type [either vertical, horizontal or diagonal] and any number of color elements.
<gradient type="vertical" x="10" y="10" width="width - title_width" height="height / 4">
   <color value="blue">
   <color value="gtk:fg[SELECTED]>
   <color value="blend/gtk:light[SELECTED]/gtk:dark[ACTIVE]">
</gradient>
 
image Maps an image to the element with origin (x,y) and given width, height and filename. It takes optional arguments alpha and colorize, which are "0" and no color by default.
<image filename="my_image.png" x="0" y="0" width="width" height="height" alpha="0.5" colorize="#FF3399"/>
 
gtk_arrow Draws an arrow with origin (x,y) and given width, height, GTK state, shadow [none, in, out, etched_in and etched_out] and direction [up, down, left or right]. It takes optional argument filled, which is "false" by default.
<gtk_arrow state="normal" x="2" y="2" width="width - 4" height="height" shadow="in" arrow="up" filled="true"/>
 
gtk_box Draws a box with origin (x,y) and given width, height, GTK state and shadow.
<gtk_box state="normal" x="2" y="2" width="width - 4" height="height" shadow="out"/>
 
gtk_vline Draws a vertical line with origin (x,y1) and destination (x,y2) and GTK state.
<gtk_vline state="normal" x="0" y1="0" y2="height"/>
 
icon Draws the window icon with origin (x,y) and given width and height. It takes optional argument alpha, which is "0" by default.
<icon x="10" y="30" width="width / 3" height="height / 3" alpha="0.3"/>
 
title Draws the window title text with origin (x,y) and given color.
<title x="10" y="30" color="gtk:text[NORMAL]"/>
 
clip Clips a given area with origin (x,y) and given width and height.
<clip x="5" y="2" width="width - 10" height="height - SpacerHeight"/>
 
include Include other drawing operations with given name. It takes optional arguments (x,y), width and height, with defaults FIXME.
<include name="other_drawing_operations"/>
 
tile Tile another drawing operations list with given name, tile_width and tile_height. It takes optional arguments (x,y), width, height, tile_xoffset and tile_yoffset with defaults FIXME.
<tile name="other_drawing_operations" tile_width="10" tile_height="10"/>


Frame Style

When creating a 'frame style', you tie in the various different 'frame pieces' and 'window buttons' to a specific 'frame geometry'. We generally need to create a style for window states normal, maximized, shaded, maximized_and_shaded and depending on whether the window is in focus or not.

We first create a template that will contain all the information needed to draw a given frame style.

<frame_style name="my_frame_style" geometry="my_frame_geometry">

   <!-- This is where we need to start specifying your frame style -->

</frame_style>

Inheritance is also allowed in a given frame style. Anything can be re-specified to override the parent style.

<frame_style name="my_child_frame_style" parent="my_frame_style" geometry="my_frame_geometry">

</frame_style>

Frame pieces

To draw parts of the frame, you will need to provide a drawing operation to each frame piece. If you omit a piece, then nothing will be drawn for that part of the frame.

<piece position="entire_background" draw_ops="my_drawing_operation"/>

Alternatively, as mentioned earlier, you can provide a drawing operation inline.

<piece position="left_edge">
   <draw_ops>
      <rectangle color="#FF0000" x0="0" y0="0" x1="width" y1="height" filled="true"/>
   </draw_ops>
</piece>

The following diagrams shows the various pieces that you can style in a given frame -

Non-title frame pieces
Non-title frame pieces
 
 
Titlebar frame pieces
Titlebar frame pieces
 
 
Title frame piece
Title frame piece

Window buttons

As mentioned previously, you need to specify a minimum set of window buttons for a given theme. Drawing methods must be provided for the close, maximize, minimize and menu buttons defined for each of the two states - normal and pressed. If prelight is not specified, normal will be used for that state.

<button function="close" state="normal" draw_ops="my_drawing_operation"/>


Along with specifying the window buttons, you can can specify how to draw part of the button depending on the position within the window frame. If all your buttons have the same background, you need only specify drawing operations for left_middle_background and right_middle_background.

<button function="left_middle_background" state="pressed" draw_ops="my_background_drawing_operation"/>


Putting all this information together into a single 'frame style' might look like the following -

<frame_style name="my_frame_style" geometry="my_frame_geometry">

   <!-- We first display the title -->
   <piece position="title" draw_ops="title_normal"/>

   <!-- Let's give the edges some prettiness -->
   <piece position="left_edge" draw_ops="draw_left_edge"/>
   <piece position="right_edge" draw_ops="draw_right_edge"/>
   <piece position="bottom_edge" draw_ops="draw_bottom_edge"/>

   <!-- We need to specify the button positions now -->
   <button function="left_middle_background" state="pressed" draw_ops="background_button"/>
   <button function="right_middle_background" state="pressed" draw_ops="background_button"/>

   <!-- We need to specify the buttons now -->
   <button function="close" state="normal" draw_ops="close_button"/>
   <button function="close" state="pressed" draw_ops="minimize_button"/>
   <button function="minimize" state="normal" draw_ops="minimize_button"/>
   <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
   <button function="maximize" state="normal" draw_ops="maximize_button"/>
   <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
   <button function="menu" state="normal" draw_ops="menu_button"/>
   <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
</frame_style>

Menu Icons

In the window menus you must specify icons for the menu entries Close, Maximize, UnMaximize and Minimize. It is enough to specify drawing operations for normal state only. You may optionally specify drawing operations for the other states, as mentioned above.

<window_icon function="close" state="normal" draw_ops="menu_close_icon"/>
<window_icon function="maximize" state="normal" draw_ops="menu_maximize_icon"/>
<window_icon function="minimize" state="normal" draw_ops="menu_minimize_icon"/>
<window_icon function="unmaximize" state="normal" draw_ops="menu_unmaximize_icon"/>


Frame Style Set

Once we have our various frame styles we need to map them onto the various window states. We do this by creating a 'frame style set'. The name attribute is referenced later on by a a given 'window type'.

<frame_style_set name="my_style_set">
   <frame focus="yes" state="normal" resize="both" style="my_normal_focused_style"/>
   <frame focus="no" state="normal" resize="both" style="my_normal_unfocused_style"/>
   <frame focus="yes" state="maximized" style="my_maximized_focused_style"/>
   <frame focus="no" state="maximized" style="my_maximized_unfocused_style"/>
   <frame focus="yes" state="shaded" style="my_shaded_focused_style"/>
   <frame focus="no" state="shaded" style="my_shaded_unfocused_style"/>
   <frame focus="yes" state="maximized_and_shaded" style="my_maximized_shaded_focused_style"/>
   <frame focus="no" state="maximized_and_shaded" style="my_maximized_shaded_unfocused_style"/>
</frame_style_set>

As you can see above, you must provide a frame for each of the window states, with window focus yes and no. The style attribute references your 'frame style'. You must also provide a resize attribute for any frames with a normal state with the value 'both'. You may optionally specify frames for other resize attributes none, horizontal and vertical.


Windows

The final mapping that you must provide is to map the 'window types' to given 'frame style sets'. Each window type needs a style set - normal, dialog, modal_dialog, menu, utility and border

<window type="normal" style_set="my_normal_style_set"/>
<window type="dialog" style_set="my_dialog_style_set"/>
<window type="modal_dialog" style_set="my_modal_dialog_style_set"/>
<window type="menu" style_set="my_menu_style_set"/>
<window type="utility" style_set="my_utility_style_set"/>
<window type="border" style_set="my_border_style_set"/>


Overview

As you can see, creating a Metacity theme will take a somewhat large amount of time. It is advisable to take existing themes and modify and observe the changes that you make gradually, instead of starting afresh. Many themes [Crux and Aqua being good examples] are based almost solely on images, which will be harder for you to modify. While a theme comprised of images might look at times very tempting, you must allow consider that it takes an appreciable amount of time to render the theme.

A good resource for Metacity themes is art.gnome.org. Bug reports should be made at bugzilla.gnome.org under the 'Metacity' component.

Metacity was written by Havoc Pennington and is licensed under the GNU General Public License [GPL]. This document was written by Glynn Foster, who has zero artistic talent, is licensed under the GPL and copyright 2002, Sun Microsystems Inc.