Creating a simple font library browser ↩
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
-
When the user clicks on the get folder… button, a
getFolder
dialog is opened. -
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()