This example shows a very simple tool with a dialog.

The dialog contains two buttons, and each button triggers an action:

  • the fist button prints the names of the selected glyphs
  • the second button paints the glyph cells of the selected glyphs

This example is useful to show some common coding patterns:

  1. Callbacks

    A callback is a function that is called by another function which takes the first function as a parameter. Usually, a callback is a function that is called when something happens. That something can be called an event in programmer-speak. (source)

    In our example, the event is the clicking of a button. When that event is triggered, the dialog will run the specified function (the callback).

    Callbacks are used extensively in vanilla.

  2. Defensive programming

    The tool does something to the selected glyphs in the current font. But what if no glyph is selected, or there is no font open? A well-written tool should handle such situations elegantly, avoiding errors and providing informative messages to the user.

    Notice how, in the code below, the program exits the function if these two conditions – a font is open, and at least one glyph is selected – are not met.

  3. Prepare Undo & Perform Undo

    If a tool makes changes to user data, it should also make it possible for the user to revert these changes if necessary.

    In RoboFont, this is accomplished by recording the state of the data before and after the change is made – look for glyph.prepareUndo() and glyph.performUndo() in the code below.

from vanilla import *

class ToolDemo(object):

    def __init__(self):
        # first, create a floating window
        # (different than Window, FloatingWindow stays of top of other windows)
        self.w = FloatingWindow((123, 70), "myTool")

        # second, define some variables to use in the UI layout
        x, y = 10, 10
        padding = 10
        buttonHeight = 20

        # add a button for printing the selected glyphs
        self.w.printButton = Button(
                (x, y, -padding, buttonHeight), # position & size
                "print", # button label
                callback=self.printGlyphsCallback # button callback
            )

        # update the y-position before placing the next button
        y += buttonHeight + padding

        # add another button for paiting the selected glyphs
        self.w.paintButton = Button(
                (x, y, -padding, buttonHeight),
                "paint",
                callback=self.paintGlyphsCallback
            )

        # done creating the dialog - open the window
        self.w.open()

    def printGlyphsCallback(self, sender):
        '''Print the names of all selected glyphs.'''

        # get the current font
        f = CurrentFont()

        # if there is no current font:
        if f is None:
            # print a message to the Output Window
            print('please open a font first!')
            # and exit the function (rest of the code will not be executed)
            return

        # if the program gets here: we have a font!

        # if no glyphs are selected:
        if not len(f.selection):
            # print a message
            print('please select one or more glyphs first!')
            # and exit the function
            return

        # if the program gets here: we have selected glyph(s)!

        # loop over all selected glyphs
        for glyphName in f.selection:
            # print glyph name
            print(glyphName)

        # done! (print a blank line)
        print()

    def paintGlyphsCallback(self, sender):
        '''Paint all selected glyphs.'''

        f = CurrentFont()

        if f is None:
            print('please open a font first!')
            return

        if not len(f.selection):
            print('please select one or more glyphs first!')
            return

        for glyphName in f.selection:
            # save undo state
            f[glyphName].prepareUndo('painting glyph')
            # paint glyph
            f[glyphName].markColor = 1, 0, 0, 0.35
            # end undo state
            f[glyphName].performUndo()

# open the dialog
ToolDemo()


In RoboFont 1.8, use glyph.mark instead of glyph.markColor.

Last edited on 27/03/2018