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/.

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

art-tutorial-mcity-im0.png (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


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]


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:

art-tutorial-mcity-im1.png (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"/>


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 -

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.

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

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"/>


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:

art-tutorial-mcity-im2.png (Non-title frame pieces)

art-tutorial-mcity-im3.png (Titlebar frame pieces)

art-tutorial-mcity-im4.png (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>


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"/>


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.
art-tutorial-mcity-im5.png (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.
art-tutorial-mcity-im6.png (ArtWeb Preview tool)


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

Previous chapter: GDM Themes

Back to Tutorial start page

Next chapter: Gtk+ Engines

Attic/GnomeArt/Tutorials/MetacityThemes (last edited 2013-11-27 14:33:53 by WilliamJonMcCann)