This tutorial shows how to create a simple tool to browse through font folders and open fonts.

Introduction

Imagine that you have several UFO projects sitting side-by-side in a parent folder:

myLibrary ├── myFamily1 │ ├── Bold.ufo │ ├── Condensed.ufo │ ├── Italic.ufo │ └── Regular.ufo ├── myFamily2 │ └── … └── myFamily3 └── …

We’ll create a simple tool to browse through the folders, showing a list of all UFOs in each folder, and to open selected fonts.

The code can be extended to do more than just opening the fonts – for example, setting font infos, building accents, generating fonts etc.

This example will illustrate the following concepts and patterns:

Layout variables

The buttons and lists are placed and sized using layout variables (padding, button height, column widths, etc). This makes it easier to change dimensions during development.

Get Folder dialog

  1. When the user clicks on the get folder… button, a getFolder dialog is opened.

  2. After the folder is selected, a list of subfolders is displayed in the left column.

List selection

Items selection works differently in each list:

  • The list of families allows only one item to be selected at once.
  • The list of fonts allows selection of multiple items (default List behavior).

Show fonts in folder

As the selection in the left column changes, the right column is updated to show a list of UFOs in the selected family folder.

The code

Read the comments and docstrings for explanations about what the code does.

import os
from vanilla import FloatingWindow, Button, List
from vanilla.dialogs import getFolder

class FontLibraryBrowser:

    # root folder for families
    rootFolder = None

    # current family
    family = None

    def __init__(self):

        # UI layout attributes
        padding = 10
        buttonHeight = 20
        columnFamilies = 120
        columnFonts = 200
        listHeight = 240
        width = columnFamilies + columnFonts + padding*3
        height = listHeight + buttonHeight*2 + padding*4

        self.w = FloatingWindow((width, height), "myFontLibrary")

        # a button to get the root folder
        x = y = padding
        self.w.getFolderButton = Button(
                (x, y, -padding, buttonHeight),
                "get root folder...",
                callback=self.getFolderCallback)

        # list of folders in root folder
        y += buttonHeight + padding
        self.w.families = List(
                (x, y, columnFamilies, listHeight),
                [],
                selectionCallback=self.getFontsCallback,
                allowsMultipleSelection=False)

        # list of fonts in folder
        x += columnFamilies + padding
        self.w.fonts = List(
                (x, y, columnFonts, listHeight),
                [])

        # open selected fonts
        x = padding
        y += listHeight + padding
        self.w.openFontsButton = Button(
                (x, y, -padding, buttonHeight),
                "open selected fonts",
                callback=self.openFontsCallback)

        # open the dialog
        self.w.open()

    # ---------
    # callbacks
    # ---------

    def getFolderCallback(self, sender):
        '''Get the main folder where all family folders live.'''

        # open a dialog to select the root folder
        folder = getFolder()

        # no folder selected
        if folder is None:
            return

        # get folder
        else:
            self.rootFolder = folder[0]

        # update families list
        self.updateFamiliesList()

    def getFontsCallback(self, sender):
        '''Get UFOs in current family folder.'''

        # get families
        families = sender.get()

        if not len(families):
            return

        # get selected family
        selection = sender.getSelection()

        if not len(selection):
            return

        self.family = families[selection[0]]

        # list all ufos in family folder
        self.updateFontsList()

    def openFontsCallback(self, sender):
        '''Open selected fonts in current family.'''

        # get fonts
        allFonts = self.w.fonts.get()

        if not len(allFonts):
            return

        # get selection
        fontsSelection = self.w.fonts.getSelection()

        if not len(fontsSelection):
            return

        # get selected fonts
        selectedFonts = [f for i, f in enumerate(allFonts) if i in fontsSelection]

        # open selected fonts
        familyFolder = os.path.join(self.rootFolder, self.family)
        for font in selectedFonts:
            # get ufo path for font
            ufoPath = os.path.join(familyFolder, '%s.ufo' % font)
            # open ufo
            print('opening %s/%s.ufo...' % (self.family, font))
            OpenFont(ufoPath)

    # ---------
    # functions
    # ---------

    def updateFamiliesList(self):
        '''Update the list of families in the UI.'''

        # get subfolders
        subFolders = []
        for f in os.listdir(self.rootFolder):
            fileOrFolder = os.path.join(self.rootFolder, f)
            if os.path.isdir(fileOrFolder):
                subFolders.append(f)

        # update families list
        self.w.families.set(subFolders)

    def updateFontsList(self):
        '''Update the list of fonts in the UI.'''

        # get folder for family
        familyFolder = os.path.join(self.rootFolder, self.family)

        # get ufos in folder
        ufos = [os.path.splitext(f)[0] for f in os.listdir(familyFolder) if os.path.splitext(f)[-1] == '.ufo']

        # update fonts list
        self.w.fonts.set(ufos)

# ---------------
# open the dialog
# ---------------

FontLibraryBrowser()
Last edited on 15/07/2021