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 (see the section called “Using own drawing routines” for details about the currently
active class) 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 an theme entry is a dictionary, that contains various
information such as 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
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) } }
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 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) } } 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) } }
# Theme usage example. from ocempgui.widgets import * # Load the theme. base.GlobalStyle.load ("theme_example.rc") # Create screen. re = Renderer () re.create_screen (200, 100) re.title = "Theme example" # Create widgets. button = Button ("Button") button.position = 5, 5 label = Label ("Label") label.position = 100, 5 re.add_widget (button, label) re.start ()
Example 40. 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 and 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()
method.
Bind the draw()
method of the
instance to your own method or function at runtime.
Provide an own Style
subclass with
that particular drawing method for the widget type.
While the first both entries should not need any explanation, the last one includes a bit more work, so let us take a closer look at it.
We will create an own Style
subclass 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 Style subclass.
from ocempgui.widgets import Style class OwnStyle (Style): def __init__ (self): Style.__init__ (self)
Style
class contains, too. 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).
We will use the existing draw_label()
method as template for our own one and modify it, so it matches
our needs.
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 Style.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.get_style (cls)
Style
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 will 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): ... # 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, label.style) # Swap colors. st["fgcolor"] = { state : self.get_style_entry (cls, st, "lightcolor", state) } drop = self.draw_string_with_mnemonic (label.text, state, label.mnemonic[0], cls, st) else: front = self.draw_string (label.text, state, cls, label.style) # Swap colors. st["fgcolor"] = { state : self.get_style_entry (cls, st, "lightcolor", state) } 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 the brighter one, that is
set in st
dictionary.
You might wonder, why we use the
get_style_entry()
method here, as we
could simply assign their both variables. If you remember
correctly, what was stated in the section called “Changing the widget appearance”, the
style information are cascaded, thus it is not guaranteed, that
the lightcolor values are set. Therefore we
will use this (more or less) failsafe method to retrieve a valid
style entry for the lightcolor.
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. if width < label.size[0]: width = label.size[0] if height < label.size[1]: height = label.size[1]
def draw_label (self, label): ... # Blit all and return the label surface to the caller. surface = self.draw_rect (width, height, label.state, cls, label.style) 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 Style
subclass, 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
variable an instance of our
own class in the code, it should make use of it.
base.GlobalStyle = OwnStyle ()
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/style.py
.
# Style usage example. from ocempgui.widgets import * class OwnStyle (Style): def __init__ (self): Style.__init__ (self) 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 Style.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.get_style (cls) # 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 # 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, label.style) # Swap colors. st["fgcolor"] = { state : self.get_style_entry (cls, st, "lightcolor", state) } drop = self.draw_string_with_mnemonic (label.text, state, label.mnemonic[0], cls, st) else: front = self.draw_string (label.text, state, cls, label.style) # Swap colors. st["fgcolor"] = { state : self.get_style_entry (cls, st, "lightcolor", state) } drop = self.draw_string (label.text, state, cls, st) # 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. if width < label.size[0]: width = label.size[0] if height < label.size[1]: height = label.size[1] # Blit all and return the label surface to the caller. surface = self.draw_rect (width, height, label.state, cls, label.style) surface.blit (drop, (label.padding + 2, label.padding + 2)) surface.blit (front, (label.padding, label.padding)) return surface if __name__ == "__main__": base.GlobalStyle = OwnStyle () re = Renderer () re.create_screen (200, 200) re.title = "Style example." re.color = (234, 228, 223) label = Label ("Example label") label.get_style ()["font"]["size"] = 30 label.position = 10, 10 label2 = Label ("#Mnemonic") label2.get_style ()["font"]["size"] = 30 label2.position = 10, 60 button = CheckButton ("Dropshadow") button.position = 10, 100 re.add_widget (label, label2, button) re.start ()
Example 41. Customizing the widget drawing routines