1. 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].
Thomas Thurman wrote a blog entry "Understanding Metacity Themes". It is available at http://blogs.gnome.org/metacity/2008/05/30/themes/.
Contents
1.1. 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>
1.2. Theme Basics
Before we go into a detailed explanation of each part, we will give an overview of the steps taken.
1.2.1. 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 |
1.2.2. 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.
1.2.3. 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 |
1.2.4. 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)
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 -
normal |
How the button should appear normally for a given frame state |
pressed |
How the button should appear when 'clicked' [with a mouse] |
prelight |
How the button should appear when it gets focus |
1.2.5. 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:
normal |
How the menu icon should appear normally in the menus |
prelight |
How the menu icon should appear when the menu entry gets focus |
active |
How the menu icon should appear when the menu entry is active [ie. checked] |
selected |
How the menu icon should appear when the menu entry gets selected |
insensitive |
How the menu icon should appear when the menu entry is insensitive [ie. greyed out] |
1.3. Deepdown Internals
1.3.1. 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)
The frame geometry has a number of optional attributes that you may provide:
Argument |
Description |
Possible values |
Default value |
has_title |
Determines whether the title text height should be included in the height calculator. |
true, false |
true |
title_scale |
Uses Pango markup. This defaults to pick up your desktop font if not specified. |
xx-small, x-small, small, medium, large, x-large and xx-large |
system font settings |
parent |
Lets you nominate another frame_geometry to inherit values from |
a name of other object |
no default value |
rounded_top_left |
Determines whether the top left corner of the window should be rounded. |
true, false |
false |
rounded_top_right |
Determines whether the top right corner of the window should be rounded. |
true, false |
false |
rounded_bottom_left |
Determines whether the bottom left corner of the window should be rounded. |
true, false |
false |
rounded_bottom_right |
Determines whether the bottom right corner of the window should be rounded. |
true, false |
false |
<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"/>
1.3.2. 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>
1.3.2.1. 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 -
Operator |
Meaning |
Example |
+ |
Plus |
2 + 3 |
- |
Minus |
5 - 4 |
* |
Multiply |
3 * 2 |
/ |
Divide |
10 / 2 |
% |
Modulus |
34 % 3 |
max |
Maximum |
4 max 5 |
min |
Minimum |
7 min 3 |
() |
Parenthesis |
(5 * 3) + 5 |
It should be noted that normal precidence rules apply with all operators.
1.3.2.2. 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:
width |
Width of the target area |
height |
Height of the target area |
object_width |
Natural width of the object being drawn |
object_height |
Natural height of the object being drawn |
left_width |
Distance from left of frame to client window |
right_width |
Distance from right of frame to client window |
top_height |
Distance from top of frame to client window |
bottom_height |
Distance from bottom of frame to client window |
mini_icon_width |
Width of mini icon for window |
mini_icon_height |
Height of mini icon for window |
icon_width |
Width of large icon |
icon_height |
Height of large icon |
title_width |
Width of title text |
title_height |
Height of title text |
1.3.2.3. 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. All angles are counter clockwise on a scale from 0.0 to 1.0, starting with 0.0 at six o'clock. |
<arc color="yellow" x="0" y="0" width="width-1" height="height-1" start_angle="0.0" extent_angle="0.5"/>
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"/>
1.3.3. 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>
1.3.3.1. 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)
(Titlebar frame pieces)
(Title frame piece)
1.3.3.2. 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>
1.3.4. 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"/>
1.3.5. 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.
1.3.6. 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"/>
1.4. 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.
metacity-theme-viewer [theme_name]
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.
(Metacity theme viewer application - for testing themes)
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]
If you want to create theme preview images (same as we use on Art.Gnome.org), you can use ArtWeb Preview tool made by Thos. Visit Thomas' CVS site.
(ArtWeb Preview tool)
1.5. 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 originally written by Glynn Foster, who has zero artistic talent, is licensed under the GPL and copyright 2002, Sun Microsystems Inc. See original version of this document.
Modified by Gnome Art team (c) 2006