The script below demonstrates how interactive tools work. The tool doesn’t do anything useful – it just shows how to react to mouse and keyboard events, and prints out some information for each event that is triggered.

from import BaseEventTool
from mojo.drawingTools import *

class MyTool(BaseEventTool):

    def setup(self):
        self.position = None
        self.size = 20
        self.shape = oval
        self.color = 1, 0, 0, 0.5

    def becomeActive(self):
        print("tool became active")

    def becomeInactive(self):
        print("tool became inactive")

    def mouseDown(self, point, clickCount):
        # getGLyph returns the current glyph as an RGlyph object
        print("mouse down", self.getGlyph(), clickCount)
        self.size *= 4

    def rightMouseDown(self, point, event):
        print("right mouse down")

    def mouseDragged(self, point, delta):
        print("mouse dragged")
        self.position = point

    def rightMouseDragged(self, point, delta):
        print("right mouse dragged")

    def mouseUp(self, point):
        print("mouse up")
        self.size *= 0.25

    def keyDown(self, event):
        # getModifiers returns a dict with all modifiers:
        # Shift, Command, Alt, Option
        print("key down", self.getModifiers())

    def keyUp(self, event):
        print("key up")

    def mouseMoved(self, point):
        self.position = point

    def modifiersChanged(self):
        print("modifiers changed")

        # get modifier keys
        modifiers = self.getModifiers()

        # define shape based on 'shift' key:
        # > if 'shift' is pressed, shape is a rectangle
        if modifiers['shiftDown']:
            self.shape = rect
        # > otherwise, shape is an oval
            self.shape = oval

        # change color based on 'option' key:
        # > if 'option' is pressed, color is blue
        if modifiers['optionDown']:
            self.color = 0, 0, 1, 0.5
        # > otherwise, color is red
            self.color = 1, 0, 0, 0.5

        # tell the glyph view to update

    def draw(self, scale):
        # print("drawing...", self.isDragging())

        if self.position is not None:
            # calculate shape position from center
            x = self.position.x - self.size*0.5
            y = self.position.y - self.size*0.5

            # set shape color

            # draw shape
            self.shape(x, y, self.size, self.size)

    def drawBackground(self, scale):
        # print("drawing background...")

    # def getDefaultCursor(self):
    #     # sets the cursor. (default is an arrow)
    #     return a NSCursor

    # def getToolbarIcon(self):
    #     # sets the toolbar icon. (default is an arrow)
    #     return a NSImage

    def getToolbarTip(self):
        return "My Tool Tip"

    # notifications

    def viewDidChangeGlyph(self):
        print("view changed glyph")

    def preferencesChanged(self):
        print("preferences changed")

Installing a tool

Tools can be installed into the Glyph Editor’s toolbar with installTool:

from import installTool

To make your tool available when RoboFont restarts, create an installation script and save it as a start-up script.

Testing a tool during development

Below is a simple helper to use while working on your own interactive tools. It provides a dialog to install/uninstall a custom tool during development, without having to restart RoboFont.

from vanilla import FloatingWindow, CheckBox
from import BaseWindowController
from import installTool, uninstallTool, getToolOrder

class MyToolActivator(BaseWindowController):

    def __init__(self, tool):
        self.w = FloatingWindow((123, 44), 'MyTool')
        self.w.button = CheckBox((10, 10, -10, 24), 'activate', value=True, callback=self.activateToolCallback)
        self.tool = tool
        # install the tool
        # initialize the window

    def activateToolCallback(self, sender):
        # if the tool is not installed, install it
        if sender.get():
        # if the tool is installed, uninstall it

    def windowCloseCallback(self, sender):
        # if the tool is active, remove it
        tools = getToolOrder()
        if self.tool.__class__.__name__ in tools:
        super(MyToolActivator, self).windowCloseCallback(sender)

Last edited on 02/06/2020