Development/Tutorials/Plasma4/Ruby/Blinker: Difference between revisions

    From KDE TechBase
    (Drawing the intial digits in the new plasmoid)
    (Added a fully working example of a plasmoid with blinking digits)
    Line 108: Line 108:
    lcd_background
    lcd_background
    </code>
    </code>


    == A messed up monochrome LCD plasmoid ==
    == A messed up monochrome LCD plasmoid ==
    Line 188: Line 187:


    <code ruby>
    <code ruby>
    # ?
    module Blinker
      class Main < PlasmaScripting::Applet
      slots 'dataUpdated(QString, Plasma::DataEngine::Data)'
     
      def initialize parent
          super parent
        end
     
        def init
          @counter = -1
          @svg = Plasma::Svg.new(self)
          @svg.imagePath = package.filePath("images", "Lcd_panel_08.svg")
              connectToEngine()
        end
           
        def connectToEngine
          # Use 'dataEngine("ruby-time")' for the ruby version of the engine
          timeEngine = dataEngine("time")
          # timeEngine.connectSource("Local", self, 500, Plasma::AlignToMinute)
          timeEngine.connectSource("Local", self, 500, Plasma::NoAlignment)
        end
           
        def dataUpdated(source, data)
          update()
          @counter = @counter - 1
          @counter = @counter.abs
          @y = @counter * 200
        end
        def paintInterface(painter, option, contentsRect)
              puts "ENTER paintInterface, painthight is " + @y.to_s
          @svg.resize(size())
          @svg.paint(painter, 0, 0, "lcd_background")
         
          @svg.paint(painter, 0, @y, "temperature:0:A")
          @svg.paint(painter, 0, @y, "temperature:0:B")
          @svg.paint(painter, 0, @y, "temperature:0:C")
          @svg.paint(painter, 0, @y, "temperature:0:D")
          @svg.paint(painter, 0, @y, "temperature:0:E")
          @svg.paint(painter, 0, @y, "temperature:0:F")
     
          @svg.paint(painter, 0, @y, "temperature:1:A")
          @svg.paint(painter, 0, @y, "temperature:1:B")
          @svg.paint(painter, 0, @y, "temperature:1:C")
          @svg.paint(painter, 0, @y, "temperature:1:D")
          @svg.paint(painter, 0, @y, "temperature:1:E")
          @svg.paint(painter, 0, @y, "temperature:1:F")
     
          @svg.paint(painter, 0, @y, "temperature:2:A")
          @svg.paint(painter, 0, @y, "temperature:2:B")
          @svg.paint(painter, 0, @y, "temperature:2:C")
          @svg.paint(painter, 0, @y, "temperature:2:D")
          @svg.paint(painter, 0, @y, "temperature:2:E")
          @svg.paint(painter, 0, @y, "temperature:2:F")
        end
       
      end
    end
    </code>
    </code>
    Yeah it works. In the next update of this page I'll explain the what and why for this code. In the mean time resize your plasmoid to huge while it runs (ouch) and play with other multiplication values to get to @y to see how that affects the code.
    ...


    This is all we need to program our own monochrome LCD plasmoids. In a next tutorial I'll explain how to create them with an SVG drawing application, as I've already played with SVG drawing.
    This is all we need to program our own monochrome LCD plasmoids. In a next tutorial I'll explain how to create them with an SVG drawing application, as I've already played with SVG drawing.

    Revision as of 22:35, 22 February 2010

    Abstract

    This tutorial explains how to use SVG artwork in a Ruby plasmoid and how to interact with it in the simplest way possible.

    Introduction

    Perhaps the simplest SVG animation in Plasma is the visual emulation of a monochrome LCD panel as its elements do not move but are simply turned on and off to compose an understandable visual effect. And the simplest effect is blinking.

    As we both do not yet know how to import and use an SVG picture we are going to build a very simple plasmoid and reuse an SVG from a non Ruby plasmoid. Then we'll ask a more experienced Ruby user to fill in the blanks, without forcing them to write a time consuming complete Plasma Ruby SVG tutorial.

    To understand the initial set-up you should read an introductory tutorial like Simple Paste Applet.

    Layout

    We'll be reusing some simple number display from the C++ coded plasma Weather Station, more specifically the SVGZ LCD temp. panel. And the plain plasma lay-out from the Simple Paste Applet:

    • contents/
      • code/
        • main.rb
      • images/
        • lcd_panel.svgz
    • metadata.desktop

    Notice that we downloaded the zipped svg file and placed it in the contents/images folder as described on the Plasma Package convention, to make sure we also (double) click the svgz file and see an actual picture, if you see semi random text you mistakenly downloaded a web page of the KDE archive instead of the picture file.

    The metadata.desktop file looks like this:

    [Desktop Entry] Name=Monochrome LCD demo Comment=This is a very simplified applet written in Ruby Icon=chronometer Type=Service ServiceTypes=Plasma/Applet

    X-Plasma-API=ruby-script X-Plasma-MainScript=code/main.rb

    X-KDE-PluginInfo-Author=Me [email protected] X-KDE-PluginInfo-Name=blinker X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-Website=http://plasma.kde.org/ X-KDE-PluginInfo-Category=Examples X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true

    The main.rb initially looks like this:

    require 'plasma_applet'

    module Blinker

     class Main < PlasmaScripting::Applet
       def initialize parent
         super parent
       end
    
       def init
         set_minimum_size 128, 128
       end
     end
    

    end

    Inside the svg

    The naming convention for the SVG elements is similar to that of the Qt API, but still each element has a unique ID. The three digits grouped as one is named:

    temperature

    And the ID of the three digits are, from left to right:

    temperature:2 temperature:1 temperature:0

    To create three zeros we use the elements named: temperature:2:A temperature:1:A temperature:0:A temperature:2:B temperature:1:B temperature:0:B temperature:2:C temperature:1:C temperature:0:C temperature:2:D temperature:1:D temperature:0:D temperature:2:E temperature:1:E temperature:0:E temperature:2:F temperature:1:F temperature:0:F

    The list starts with the top one named A and goes clockwise. The elements we don't use, because that would create the figure eight, are the ones in the middle which are named: temperature:2:G temperature:1:G temperature:0:G

    There are also two usable decimal points in this SVG, which we don't use in this tutorial named: temperature:2:DP temperature:1:DP

    But we start by importing the grey background with the rounded corners and using that as the shape of our plasmoid, it's ID has the name: lcd_background

    A messed up monochrome LCD plasmoid

    When we ask Qt to paint the entire svg things would look quite right, but when we try to paint a number zero all the elements are painted on top of each other at the same location, oops what a mess. Qt sees each IDed element as a tiny svg to be drawn at our specified location, so all start at location 0, 0. Perhaps we could look up the location of a full digit 8 and tell Qt were to draw it, but then whenever an artists redesigns the elements they would have to change the codebase. Besides the individual elements don't have logically picked locations, they are cut from the digit at good looking locations. And when we resize the plasmoid we find out that the SVG rendering engine is not the same as the painting engine and digits move apart or together. Solving this would require lots of code.

    So we conclude the SVG itself is only usable as a complete image stamp (like an icon or a picture button) but the SVG is laid out wrong for easy Qt painting. So before we move on we first go back one hundred years in the history of animation and place each element we want to paint on its own transparent sheet, each sheet starts at 0, 0 and all are the same size as the entire svg.

    In this example I use the application in which the SVG was designed, Inkscape. I resize the contents of the SVG to make it fit on a plasmoid on its own, then I duplicate as many transparent fullframe rectangles as needed.

    Then I open it in Karbon, as it has a nice hierarchy list, and place each rectangle next to each named ID, and join each into a group. This breaks up the existing hierarchy, as the new shape is larger then the old shape and so does no longer fit inside it anymore. I add _i to each individual ID name and give the new groups the old ID names.

    A working monochrome LCD plasmoid

    Now download Media:Lcd_panel_08.svg‎, the new svg, and place it inside the images folder of your plasma directory. The render engine will now render each element at its original location and the paint instructions are simple and reliable. In fact the following code looks a lot like the ruby-tiger example code, only now we paint multiple times to compose the wanted result:

    require 'plasma_applet'

    module Blinker

     class Main < PlasmaScripting::Applet
     
       def initialize parent
         super parent
       end
    
       def init
         set_minimum_size 128, 128
       end
       
       def init
         @svg = Plasma::Svg.new(self)
         @svg.imagePath = package.filePath("images", "Lcd_panel_08.svg")
       end
    
       def paintInterface(painter, option, contentsRect)
         @svg.resize(size())
         
         @svg.paint(painter, 0, 0, "lcd_background")
         sleep 0.5
         
         @svg.paint(painter, 0, 0, "temperature:0:A")
         @svg.paint(painter, 0, 0, "temperature:0:B")
         @svg.paint(painter, 0, 0, "temperature:0:C")
         @svg.paint(painter, 0, 0, "temperature:0:D")
         @svg.paint(painter, 0, 0, "temperature:0:E")
         @svg.paint(painter, 0, 0, "temperature:0:F")
         
         @svg.paint(painter, 0, 0, "temperature:1:A")
         @svg.paint(painter, 0, 0, "temperature:1:B")
         @svg.paint(painter, 0, 0, "temperature:1:C")
         @svg.paint(painter, 0, 0, "temperature:1:D")
         @svg.paint(painter, 0, 0, "temperature:1:E")
         @svg.paint(painter, 0, 0, "temperature:1:F")
         
         @svg.paint(painter, 0, 0, "temperature:2:A")
         @svg.paint(painter, 0, 0, "temperature:2:B")
         @svg.paint(painter, 0, 0, "temperature:2:C")
         @svg.paint(painter, 0, 0, "temperature:2:D")
         @svg.paint(painter, 0, 0, "temperature:2:E")
         @svg.paint(painter, 0, 0, "temperature:2:F")      
       end
       
     end
    

    end

    That looks nice, but did you notice the plasmoid did not update the graphics after half a second? It seems all the code we write is part of the initialization of the plasmoid. The paint engine renders the lcd background, waits half a second, renders the 3 digits and only when completely finished does it paint everything onto the screen.

    Rewriting this into a looping animation would only cause a never ending loop which makes sure our plasmoid never gets painted at all.

    So how do we paint after the creation of the plasmoid?

    The idea for our plasmoid is as follows: once created we repeat the following steps:

    • clear the screen by drawing lcd_background again.
    • wait 0.5 second
    • draw the elements for three zeros
    • wait 0.5 second

    After a more experienced Ruby programmer has expanded the code to include the SVG and to make the three zero digits blink on for half a second and then off for half a second, like an unset alarm clock, the code looks like this:

    module Blinker

     class Main < PlasmaScripting::Applet
    
     slots 'dataUpdated(QString, Plasma::DataEngine::Data)'
     
     def initialize parent
         super parent
       end
     
       def init
         @counter = -1
         @svg = Plasma::Svg.new(self)
         @svg.imagePath = package.filePath("images", "Lcd_panel_08.svg")
             connectToEngine()
       end
           
       def connectToEngine
         # Use 'dataEngine("ruby-time")' for the ruby version of the engine
         timeEngine = dataEngine("time")
         # timeEngine.connectSource("Local", self, 500, Plasma::AlignToMinute)
         timeEngine.connectSource("Local", self, 500, Plasma::NoAlignment)
       end
           
       def dataUpdated(source, data)
         update()
         @counter = @counter - 1
         @counter = @counter.abs
         @y = @counter * 200
       end
    
       def paintInterface(painter, option, contentsRect)
             puts "ENTER paintInterface, painthight is " + @y.to_s
         @svg.resize(size())
    
         @svg.paint(painter, 0, 0, "lcd_background")
         
         @svg.paint(painter, 0, @y, "temperature:0:A")
         @svg.paint(painter, 0, @y, "temperature:0:B")
         @svg.paint(painter, 0, @y, "temperature:0:C")
         @svg.paint(painter, 0, @y, "temperature:0:D")
         @svg.paint(painter, 0, @y, "temperature:0:E")
         @svg.paint(painter, 0, @y, "temperature:0:F")
    
         @svg.paint(painter, 0, @y, "temperature:1:A")
         @svg.paint(painter, 0, @y, "temperature:1:B")
         @svg.paint(painter, 0, @y, "temperature:1:C")
         @svg.paint(painter, 0, @y, "temperature:1:D")
         @svg.paint(painter, 0, @y, "temperature:1:E")
         @svg.paint(painter, 0, @y, "temperature:1:F")
    
         @svg.paint(painter, 0, @y, "temperature:2:A")
         @svg.paint(painter, 0, @y, "temperature:2:B")
         @svg.paint(painter, 0, @y, "temperature:2:C")
         @svg.paint(painter, 0, @y, "temperature:2:D")
         @svg.paint(painter, 0, @y, "temperature:2:E")
         @svg.paint(painter, 0, @y, "temperature:2:F")
       end
       
     end
    

    end

    Yeah it works. In the next update of this page I'll explain the what and why for this code. In the mean time resize your plasmoid to huge while it runs (ouch) and play with other multiplication values to get to @y to see how that affects the code.

    ...

    This is all we need to program our own monochrome LCD plasmoids. In a next tutorial I'll explain how to create them with an SVG drawing application, as I've already played with SVG drawing.


    Interaction

    Perhaps the experienced programmer has some more time to spare and can show us how our plasmoid can be made to respond to mouse clicks on the SVG elements. Well only if such interactions are even possible in a plain plasmoid. For our second applet we want to know how to interact with SVG so we want the blinker to blink only the most right digit when we click on any element of the last digit. And to blink the right two digits when we click the middle one and again all three digits when we press the left one. The code now looks like this:

    ?

    Speed optimization

    SVG rendering is considered slow when compared to rendering PNG's. That is why during initiation or resizing of an SVG application the rendered SVG is sometimes cached as a bitmap or as multiple bitmaps, or sprites, which contains rendered elements as plain pixels which are kept and copied to a canvas which, once composition is complete, is painted over the bitmap being displayed in one go, making for an instant looking update of everything on the screen. Does plasma require some sort of canvas to compose elements on? And if so. Can you show us one or some versions of the blinking applet which uses bitmap caching?

    ?

    Don't forget that I'm not asking you to write the tutorial, commenting your code should be sufficient for me to write a story about it.