This is a Clutter and Mx introduction for Python3

Contents:

Introduction

The purpose is to have a small tutorial about the use of Clutter and Mx under Python3.

Clutter

Quotting the Clutter web page: “Clutter is an open source (LGPL 2.1) software library for creating fast, compelling, portable, and dynamic graphical user interfaces. It is a core part of MeeGo, and is supported by the open source community. Its development is sponsored by Intel.”

It uses OpenGL.

Mx

It is a toolkit developed on top of Clutter. Originally developped for Maemo/Moblin then merged into Moblin (now dead and evolved into Tizen), but as for now is an independent project.

Other technologies

It seems that this can be used with other technologies such as:

  • Gtk
  • Gstreamer
  • Cairo

Basic Usage

Clutter

In Clutter we work in a Stage where we would put a number of Actors. The typical Hello World example would be:

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter
import sys

if __name__ == '__main__':
    Clutter.init( sys.argv )

    # Create Stage
    _stage = Clutter.Stage()
    _stage.set_title( "Basic Usage" )
    _stage.set_size( 400, 200 )

    # Create Actor
    _red = Clutter.Color().new(255, 0, 0, 255) # R,G,B,alpha
    _actor = Clutter.Text().new_full(
                  "Mono 10",
                  "Hello World!",
                  _red )
    _actor.set_position( 100,100 )
    # Add Actor to the Stage
    _stage.add_actor( _actor )
    _stage.connect("destroy", lambda w: Clutter.main_quit() )
    _stage.show_all()

    Clutter.main()

where:

  • Clutter is initialized.
  • A Stage is created.
  • An actor is created (just some formatted text) and its position is set.
  • The actor is added to the stage.
  • The signal “destroy” in the stage is connected to a function

being the result:

_images/basic_usage_clutter.png

Mx

Mx is a toolbox based on Clutter. We could say it creates a number of actor that can be used in a Clutter stage:

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter, Mx
import sys

if __name__ == '__main__':
    Clutter.init( sys.argv )

    # Create Stage
    _stage = Clutter.Stage()
    _stage.set_title( "Basic Usage" )
    _stage.set_size( 400, 200 )

    # Create Actor
    _label = Mx.Label()
    _label.set_text("Hello world")

    # Add Actor to the Stage
    _stage.add_actor( _label )
    _stage.connect("destroy", lambda w: Clutter.main_quit() )
    _stage.show_all()

    Clutter.main()

where:

  • Clutter is initialized.
  • A Stage is created.
  • An actor (a label from the Mx toolkit) is created.
  • The actor is added to the stage.
  • The signal “destroy” in the stage is connected to a function

being the result:

_images/basic_usage_mx.png

Clutter: capturing key events

The following example shows how to capture key presses in Clutter. The example if heavily based on snippet from Nick Veitch:

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter
import sys

# define some colours (takes RGBA values)
red = Clutter.Color().new(255,0,0,255)
green = Clutter.Color().new(0,255,0,255)
blue = Clutter.Color().new(0,0,255,255)
black = Clutter.Color().new(0,0,0,255)
white = Clutter.Color().new(255,255,255,255)

# Define a callback method for key presses
def keyPress(self, event):
    # Parses the keyboard input generated by a callback from the Stage object
    # The event object has the property 'keyval'
    # clutter also defines constants for keyvals
    # so we can use these to compare
    if event.keyval == Clutter.q:
        #if the user pressed "q" quit the test
        Clutter.main_quit()
    elif event.keyval == Clutter.r:
        #if the user pressed "r" make the actor red
        text_actor.set_color(red)
        text_actor.set_text("This is Red")
    elif event.keyval == Clutter.g:
        #if the user pressed "g" make the actor green
        text_actor.set_color(green)
        text_actor.set_text("This is Green")
    elif event.keyval == Clutter.b:
        #if the user pressed "b" make the actor blue
        text_actor.set_color(blue)
        text_actor.set_text("This is Blue")
    elif event.keyval == Clutter.Up:
        # 'Up' is equal to the cursor up key
        stage.set_color(black)
    elif event.keyval == Clutter.Down:
        # 'Down' is equal to the cursor down key
        stage.set_color(white)
    #event feedback, useful for working out values of tricky keys
    print('event processed',  event.keyval)

if __name__ == '__main__':
   Clutter.init( sys.argv )

   # Stage creation
   stage = Clutter.Stage()
   stage.set_size(450, 180)
   stage.set_color(black)

   # Text Actor
   text_actor=Clutter.Text()
   text_actor.set_color(red)
   text_actor.set_position(20,70)
   text_actor.set_font_name('Sans 24')
   text_actor.set_text('Press a Key:\nUp, Down, r, g, b, q')

   # Add the actor to the stage
   stage.add_actor( text_actor )

   # Show the stage
   stage.show_all()

   # Connect signals
   stage.connect("destroy",lambda w: Clutter.main_quit() )
   stage.connect('key-press-event', keyPress)

   # Pass execution to the main clutter loop
   Clutter.main()

Clutter: JSON

The GUI can be defined by mean of a JSON file, which is kind of low fat XML file.

ui.json

The description of the GUI is done by mean of the following file

[
  {
    "id" : "stage",
    "type" : "ClutterStage",
    "width" : 400,
    "height" : 400,
    "color" : "#333355ff",
    "title" : "Scripting example",
    "children" : [ "box" ],
    "signals" : [
      { "name" : "destroy", "handler" : "clutter_main_quit" }
    ]
  },

  {
    "id" : "box",
    "type" : "ClutterBox",
    "width" : 400,
    "height" : 400,

    "layout-manager" : {
      "type" : "ClutterBinLayout",
      "x-align" : "center",
      "y-align" : "center"
    },

    "children" : [
      {
        "id" : "rectangle",
        "type" : "ClutterRectangle",
        "width" : 200,
        "height" : 200,
        "color" : "red"
      }
    ]
  }

]

main.py

The GUI is created from that file by mean of:

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter
import sys

if __name__ == '__main__':
    Clutter.init(sys.argv)

    _script = Clutter.Script()
    _script.load_from_file( "ui.json")
    stage = _script.get_object("stage")
    _script.connect_signals( stage )

    stage.show_all()
    Clutter.main()

The result looks like:

_images/clutter_json.png

Clutter: animations

Simple implicit animations

It is very easy to get something animated. For an actor we might use something like:

_actor_anim = _actor.animatev(
                 Clutter.AnimationMode.EASE_OUT_BOUNCE,
                 1500,
                 ["x"],
                 [20] )

where:

  • EASE_OUT_BOUNCE: this is the effect of the animation
  • 1500 represents how long will be animated in milliseconds.
  • [“x”]: this is a list with the elements that will be animated.
  • [20]: means that at the end of the animation, “x” will be equal to 20.

Full example

Using this base we could write:

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter
import sys

if __name__ == '__main__':
    Clutter.init( sys.argv )

    # Create Stage
    _stage = Clutter.Stage()
    _stage.set_title( "Basic Usage" )
    _stage.set_size( 400, 200 )

    # Create Actor
    _red = Clutter.Color().new(255, 0, 0, 255) # R,G,B,alpha
    _actor = Clutter.Text().new_full(
                  "Mono 10",
                  "Hello World!",
                  _red )

    _actor.set_position( 100,100 )
    _actor_anim = _actor.animatev(
                     Clutter.AnimationMode.EASE_OUT_BOUNCE,
                     1500,
                     ["x"],
                     [20] )

    # Add Actor to the Stage
    _stage.add_actor( _actor )
    _stage.connect("destroy", lambda w: Clutter.main_quit() )
    _stage.show_all()

    Clutter.main()

more information about Implicit animations.

Animation based on state

The following code is failing for some reason (any help is welcomed):

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter
import sys

def keyPress(self, event, _transitions):
    Clutter.State.set_state(_transitions, "move-down")

if __name__ == '__main__':
    Clutter.init( sys.argv )

    # Create Stage
    _stage = Clutter.Stage()
    _stage.set_title( "Animation using states" )
    _stage.set_size( 400, 400 )

    # Create Actor
    _red = Clutter.Color().new(255, 0, 0, 255) # R,G,B,alpha
    _actor = Clutter.Text().new_full(
                  "Mono 10",
                  "Press any key...",
                  _red )

    _actor.set_position( 100.0,100.0 )

    # Transition
    # - State creation
    _transitions = Clutter.State()



    # - Defines de behaviour of a number of actors
    _transitions.set_key( None, "move-down",  # source_state, target_state
          _actor, "x", Clutter.AnimationMode.EASE_OUT_CUBIC, 300,
          pre_delay=0.0, post_delay=0.0)

    # - All state transitions take 250ms
    _transitions.set_duration(None,None,3000)

    # Add Actor to the Stage
    _stage.add_actor( _actor )
    _stage.connect("destroy", lambda w: Clutter.main_quit() )
    _stage.connect('key-press-event', keyPress, _transitions)

    _stage.show_all()

    # Create animation


    Clutter.main()

Currently this code fails complaining of:

(anim_states.py:12376): Clutter-CRITICAL **: clutter_interval_set_final_value: assertion `G_VALUE_TYPE (value) == priv->value_type' failed
/usr/lib/python3.2/site-packages/gi/types.py:43: Warning: g_value_set_float: assertion `G_VALUE_HOLDS_FLOAT (value)' failed
  return info.invoke(*args, **kwargs)
/usr/lib/python3.2/site-packages/gi/types.py:43: Warning: g_value_get_float: assertion `G_VALUE_HOLDS_FLOAT (value)' failed
  return info.invoke(*args, **kwargs)

Animator

The reference documentation for the animator.

Not working for me yet:

#!/usr/bin/env python
#! -*- coding: utf-8 -*-

from gi.repository import Clutter
import sys

def keyPress(self, event, _anim):
    _anim.start()

if __name__ == '__main__':
    Clutter.init( sys.argv )

    # Create Stage
    _stage = Clutter.Stage()
    _stage.set_title( "Animation using states" )
    _stage.set_size( 400, 400 )
    _stage.set_reactive( True )

    # Create Actor
    _red = Clutter.Color().new(255, 0, 0, 255) # R,G,B,alpha
    _actor = Clutter.Text().new_full(
                  "Mono 10",
                  "Press any key...",
                  _red )

    _actor.set_position( 100.0,100.0 )

    # Transition
    # - State creation
    _anim = Clutter.Animator()
    _anim.set_duration( 4000 )
    #_anim.set_properties(
       # start of the animation
    #   _actor, "x", Clutter.AnimationMode.LINEAR, 0.0, 0.0,

       # half-way
    #   _actor, "x", Clutter.Animation.LINEAR, 0.5, 100.0,

       # end of animation
    #   _actor, "x", Clutter.Animation.LINEAR, 0.5, 100.0,
    #   )

    _anim.set_key( _actor, "x", Clutter.AnimationMode.LINEAR, 0.0, 50.0)

       # half-way
       #     _actor, "x", Clutter.Animation.LINEAR, 0.5, 100.0,

       # end of animation
       #    _actor, "x", Clutter.Animation.LINEAR, 0.5, 100.0,
       #    )
    # - Defines de behaviour of a number of actors

       #          pre_delay=0.0, post_delay=0.0)

    # - All state transitions take 250ms
    #    _transitions.set_duration(None,None,3000)

    # Add Actor to the Stage
    _stage.add_actor( _actor )
    _stage.connect("destroy", lambda w: Clutter.main_quit() )
    _stage.connect('key-press-event', keyPress, _anim)

    _stage.show_all()

    # Create animation


    Clutter.main()

Indices and tables