The ocepgui.widgets
module offers two
possibilities to change the provided widget appearance. This
section covers the necessary tasks, which have to be performed to
change create an own look and feel to match your personal needs.
The first part will give you a rough overview about how the
styling and drawing system of the
ocempgui.widgets
module words, while the
second will explain the style files and syntax, that deal with
basic attributes such as background colors or fonts. The last part
then gives an in-depth explanation, how to provide own drawing
methods for a widget to spend it a completely different look
and feel (such as rounded borders for buttons for example).
The rendering engine contains several methods to draw each widget and some generalized methods for some commonly used parts such as the basic widget surface. The widget to draw is passed to its matching drawing method, which will look up its style settings and then draw it.
The Style
supports cascading the widget
style settings. This means, that it will, when it retrieves the
widget style and does not find the style setting it needs, walk
through the inherited classes (the __mro__
attribute) of the widget until it finds the setting. The last
instance it looks the setting up in is the
"default" entry of its
styles attribute.
Theme files for OcempGUI define the most basic settings for the widgets, such as the background color or the font to use. They however do not cause the widget to look completely different. The basic widget layout will stay the same.
The theme files are read by the currently active
Style
class (which is set in
base.GlobalStyle
) and their information
will be stored in its styles dictionary.
The currently active theme information for a specific widget
class are stored in the styles attribute by
using its lowercase classname. This means that the specific
theme for a Button
widget is stored in
Style.styles["button"]
. For a
Label
widget it would be
Style.styles["label"]
and so on.
Such a theme entry is a dictionary, that contains various
information about the background and foreground color for the
different supported widget states, the font settings for that
widget and more. The complete list of possible settings can be
found in the documentation of the Style
,
so it will not be covered in detail here.
Setting up individual styles should be done using an own file for them, so you can easily change them without touching your program code. Let us create a separate theme file, that will cause the buttons to use a blue background color and the labels a different font and foreground color.
After creating the file for that (the author recommends using
the suffix ".rc" for theme files), we use
the python syntax for the contents. First we have to let the
file and its content know about the supported widget states,
thus we will import the Constants
of the
ocempgui.widgets
module.
from ocempgui.widgets import Constants
Do not use any from ... import *
within the theme
files to avoid a bloated Style
class.
The theme loader give you much freedom including the
possibility to make many mistakes. You also should not import
anything except the Constants
module
into the theme file, too.
Now we will set up our theme dictionary for the
Button
widget.
from ocempgui.widgets import Constants button = { "bgcolor" : { Constants.STATE_NORMAL : (18, 12, 135), Constants.STATE_ENTERED : (32, 25, 164), Constants.STATE_ACTIVE : (18, 12, 135), Constants.STATE_INSENSITIVE : (54, 51, 106) } }
Button
to
different types of a dark blue color. It also will affect any
inherited widget, that does not
implement and own style explicitly,
define the style setting in its own style.
You can easily see this by placing a
Button
and a
CheckButton
side by side.
Let us now set the styles for the Label
widget. The first part sets up the font, its size and if
antialiasing should be used, the second one sets up different
colors to use for the text according to the widget its state.
from ocempgui.widgets import Constants ... label = { "font" : { "name" : "Helvetica", "size" : 16, "alias" : True }, "fgcolor" : { Constants.STATE_NORMAL : (250, 250, 250), Constants.STATE_ENTERED : (150, 150, 150), Constants.STATE_ACTIVE : (0, 0, 0), Constants.STATE_INSENSITIVE : (235, 235, 235) } }
Typing in those Constants...
entries can
become a lot of work, especially with complex themes. Thus
OcempGUI allows you to define variables to ease your life. Any
entry, that is prefixed with an underscore,
"_", will be handled as variable, ignored
by the loading mechanisms and thus does not go into the
style dictionary.
With this in mind, let's substitute those lengthy constants with some variables.
from ocempgui.widgets import Constants # Variables, that will be used to ease the life. _normal = Constants.STATE_NORMAL _entered = Constants.STATE_ENTERED _active = Constants.STATE_ACTIVE _insensitive = Constants.STATE_INSENSITIVE button = { "bgcolor" : { _normal : (18, 12, 135), _entered : (32, 25, 164), _active : (18, 12, 135), _insensitive : (54, 51, 106) } } ...
_red = (255, 0, 0) __myfont = "path/to/font.ttf" ____________a_pretty_long_variable_that_is_completely_useless = None
As you see, setting up own themes for widgets is not a complex task. Let us now examine the complete code and a small example, that will make use of the just created theme.
You can find the code and theme as python scripts under
examples/theme_example.rc
and
examples/theme.py
.
from ocempgui.widgets import Constants # Variables, that will be used to ease the life. _normal = Constants.STATE_NORMAL _entered = Constants.STATE_ENTERED _active = Constants.STATE_ACTIVE _insensitive = Constants.STATE_INSENSITIVE button = { "bgcolor" : { _normal : (18, 12, 135), _entered : (32, 25, 164), _active : (18, 12, 135), _insensitive : (54, 51, 106) } } label = { "font" : { "name" : "Helvetica", "size" : 16, "alias" : True }, "fgcolor" : { _normal : (250, 250, 250), _entered : (150, 150, 150), _active : (0, 0, 0), _insensitive : (235, 235, 235) } }
# Theme usage example. from ocempgui.widgets import base, Renderer, Button, Label # Load the theme. base.GlobalStyle.load ("theme_example.rc") # Create screen. re = Renderer () re.create_screen (200, 100) re.title = "Theme example" re.color = (250, 250, 250) # Create widgets. button = Button ("Button") button.topleft = 5, 5 label = Label ("Label") label.topleft = 100, 5 re.add_widget (button, label) re.start ()
Example 49. Customizing the themes
Whenever you need an completely different layout, the creation of own theme files will not be enough. This section explains how to modify and/or override the drawing methods for the widgets.
First you should make yourself clear about if you need to provide an own layout only for specific instances of a widget or if those widgets always should be drawn that way. Dependant on your needs, you have several possibilites.
Subclass the widget and override its
draw()
and
draw_bg()
methods.
Bind the draw()
and
draw_bg()
methods of the instance
to your own methods or functions at runtime.
Provide an own drawing method for the widget type and bind it to the set drawing engine.
Implement an own drawing engine and bind it to the
Style
instance.
The first both entries should be self-explanatory. The only
thing you have to keep in mind when overriding the widget's
drawing methods are, that you must invoke
at least the BaseWidget.draw()
method.
This method updates the internals of the widget after drawing
the background, does surface conversions and more, so it is an
absolutely necessary method to be invoked.
Of course you can also invoke the parent's
draw()
method, if you (just) want to
add something to the widget's look instead of implementing all
drawing yourself.
class OwnButton (Button): ... def draw_bg (self): ... # Own background drawing implementation. def draw (self): # We want the drawing routines of the Button to be executed and # just add something to them. Button.draw (self) ... class OwnButton2 (Button): ... def draw_bg (self): ... # Own drawing implementation. def draw (self): # Do not process any drawing of the Button, but use the BaseWidget # directly to update the widget internals, because we do anything # ourselves BaseWidget.draw (self) ...
So far for the first both entries of the above list. Now let's look at manipulating the drawing engine by overriding it (binding a new method should not be any problem for you, not?).
We will create an own drawing engine subclass using the provided
DefaultEngine
and use an own drawing
method for Label
widgets, which adds a
dropshadow to them by default. After creating this subclass we
will use an instance of it for our own small test application.
The first we have to do is to inherit from the
DefaultEngine
subclass. The class is not
located in the module directly, but installed as additional data
and can be imported with help of the
DEFAULTDATADIR constant.
# Drawing engine usage example. from ocempgui.widgets import * from ocempgui.widgets.Constants import DEFAULTDATADIR # Get the path where the theme engines are usually installed. import sys sys.path.append (DEFAULTDATADIR) from themes import default # Override the DefaultEngine with its drawing definitions so that we can # implement our own draw_label() method. class DropShadowEngine (default.DefaultEngine): def __init__ (self, style): default.DefaultEngine.__init__ (self, style)
DefaultEngine
class contains. To provide
our own drawing method for the Label
, we
have to override the matching method, which is
draw_label()
.
As you can see from its signature, it receives one argument, the
Label
object, for which the surface
should be created.
All widget drawing methods follow the same naming scheme, which is draw_WIDGETTYPE (self, widget) with widgettype being the all-lowercase classname.
def draw_label (self, label): """Overrides the label drawing method and adds a dropshadow.""" # We do not care about multiline labels. Thus pass those to the # parent. if label.multiline: return DefaultEngine.draw_label (self, label)
Now we shorten some attributes, which we will need in our method more often.
def draw_label (self, label): ... cls = label.__class__ state = label.state # Peek the style of the label so we can get the colors later. st = label.style or self.create_style (cls)
DefaultEngine
class to look up the
theme information of the widget. The st
variable will be used to get and set the matching color for the
dropshadow of our Label
.
Now we have to get the foreground color to use for the text and
store it temporarily as we will modify it in the
st
variable later.
def draw_label (self, label): ... # Save the label color temporarily as we will change it for the # dropshadow. Because we are using references, not plain style # copies, we have to do this. fgcolor = self.get_style_entry (cls, st, "fgcolor", state) # The 2px added here are used for the dropshadow. width = 2 * label.padding + 2 height = 2 * label.padding + 2
Label
surface will occupy. Because the
Label
offers a
padding attribute, we have to take it into
account for the surface size.
The basics are set up, so that we can proceed with drawing the surfaces for our widget.
def draw_label (self, label): ... if label.mnemonic[0] != -1: front = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) # Swap colors. st["fgcolor"] = { state : (100, 100, 100) } drop = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) else: front = self.draw_string (label.text, state, cls, st) # Swap colors. st["fgcolor"] = { state : (100, 100, 100) } drop = self.draw_string (label.text, state, cls, st)
Label
and then use either the mnemonic
string drawing method or the simple string drawing method. We do
that twice, for the normal text and its dropshadow. In line
eleven and nineteen we swap the foreground color of the
st
variable with a bright grey one.
So we have our both surfaces now and are nearly done. But we
have to respect some definitions of the
BaseWidget
and we have to reset the
swapped colors.
def draw_label (self, label): ... # Surface creation done. Restore the colors. st["fgcolor"][state] = fgcolor # Get the size of the surface(s) and add it to the complete # width and height, the label will occupy. rect = front.get_rect () width += rect.width height += rect.height # Guarantee size. width, height = label.check_sizes (width, height)
def draw_label (self, label): ... # Blit all and return the label surface to the caller. surface = self.draw_rect (width, height, label.state, cls, st) surface.blit (drop, (label.padding + 2, label.padding + 2)) surface.blit (front, (label.padding, label.padding)) return surface
So far so good. We now have our own drawing engine, that will
draw Label
widgets with a dropshadow by
default. The very last thing to do is to make the
omcepgui.widgets
module use our own class
instead of the default. This is done by assigning the
base.GlobalStyle.engine attributes an
instance of our own class in the code. The drawing methods will
then make use of it.
# Set out own drawing engine in the Style class. base.GlobalStyle.engine = DropShadowEngine (base.GlobalStyle)
Label
widgets will use the just
created draw_label()
method.
The complete code of the previous example follows. It includes some test code, so you can easily evaluate the results.
You can find the code as python script under
examples/drawing_engine.py
.
# Drawing engine usage example. from ocempgui.widgets import * from ocempgui.widgets.Constants import DEFAULTDATADIR # Get the path where the theme engines are usually installed. import sys sys.path.append (DEFAULTDATADIR) from themes import default # Override the DefaultEngine with its drawing definitions so that we can # implement our own draw_label() method. class DropShadowEngine (default.DefaultEngine): def __init__ (self, style): default.DefaultEngine.__init__ (self, style) def draw_label (self, label): """Overrides the label drawing method and adds a dropshadow.""" # We do not care about multiline labels. Thus pass those to the # parent. if label.multiline: return DefaultEngine.draw_label (self, label) cls = label.__class__ state = label.state # Peek the style of the label so we can get the colors later. st = label.style or self.style.get_style (cls) # Save the label colour temporarily as we will change it for the # dropshadow. Because we are using references, not plain style # copies, we have to do this. fgcolor = self.style.get_style_entry (cls, st, "fgcolor", state) # The 2px added here are used for the dropshadow. width = 2 * label.padding + 2 height = 2 * label.padding + 2 # Backup the color. tmp = None if st.has_key ("fgcolor"): # Is fgcolor defined? tmp = st["fgcolor"] # Draw the text. front = None drop = None if label.mnemonic[0] != -1: front = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) # Swap colors. st["fgcolor"] = WidgetStyle ({ state : (100, 100, 100) }) drop = self.draw_string_with_mnemonic \ (label.text, state, label.mnemonic[0], cls, st) else: front = self.draw_string (label.text, state, cls, st) # Swap colors. st["fgcolor"] = WidgetStyle ({ state : (100, 100, 100) }) drop = self.draw_string (label.text, state, cls, st) # Surface creation done. Restore the colors. if not tmp: del st["fgcolor"] else: st["fgcolor"] = tmp # Get the size of the surface(s) and add it to the complete # width and height, the label will occupy. rect = front.get_rect () width += rect.width height += rect.height # Guarantee size. width, height = label.check_sizes (width, height) # Blit all and return the label surface to the caller. surface = self.draw_rect (width, height, label.state, cls, st) surface.blit (drop, (label.padding + 2, label.padding + 2)) surface.blit (front, (label.padding, label.padding)) return surface if __name__ == "__main__": re = Renderer () re.create_screen (200, 200) re.title = "Style example." re.color = (234, 228, 223) # Set out own drawing engine in the Style class. base.GlobalStyle.engine = DropShadowEngine (base.GlobalStyle) label = Label ("Example label") label.create_style ()["font"]["size"] = 30 label.topleft = 10, 10 label2 = Label ("#Mnemonic") label2.create_style ()["font"]["size"] = 30 label2.topleft = 10, 60 button = CheckButton ("Dropshadow") button.topleft = 10, 100 re.add_widget (label, label2, button) re.start ()
Example 50. Customizing the widget drawing routines