We have been making type design tool UIs for over 25 years. We have a lot of experience and examples to learn from. Our API for building UI hasn't changed much in 25 years. (We're still using the W API from RoboFont.) It works, but can we have something better?
Break it into a design problem...
We have four different levels of where interfaces are needed:
- script
- extension
- app-like extension
- app
There are some common design patterns among all of these. Most of the UI is just a bunch of buttons, text fields, checkboxes, etc. lined up. These are pretty basic Web 1.0 forms, actually.
Merz and Subscriber introduced some new models that have been useful.
- Merz: The coder use a very simple descriptions to build things. Behind the scenes, these descriptions are converted to complex objects that use sophisticated optimization techniques.
- Subscriber: The coder subclasses a base class and automatically inherits a lot of potential behavior. As long as the design pattern is followed, everything just works and complex, tedious stuff is completely abstracted away.
iOS introduced the idea that, hey, maybe positioning things with absolute coordinates kind of sucks and maybe could just go away.
- First there was auto layout with constraints, hugging, priorities and other stuff. This was powerful, but pretty gnarly to do manually.
- Then there was an ASCII format for building these. This was great... as long as you understood the syntax. Sometimes it wasn't great even when you understood the syntax. I added this to vanilla several years ago.
- Then came stacks and grids. You simply put stuff in and the stuff is positioned automatically. You can put a stack inside of a stack inside of a grid inside of a grid inside of a stack and it just works. Most of the time.
Vanilla is a Pythonic wrapper around Cocoa. It's a pretty direct wrapper apart from List. It was built when absolute positioning was all that was available. (NSLayoutContraint was still 8+ years away!) Everything in vanilla is manual: you have to add the controls, wire up the callbacks and so on. Vanilla is verbose and fiddly, which sucks when trying to write a quick script. Once you have your controls in place, it's a pain to change the layout of or add new controls to the interface. Finally, since vanilla is just a wrapper around the Cocoa objects, it doesn't help with layout. This makes it very easy to make ugly interfaces that don't come close to following the macOS HIG.
- Make it super easy to use in scripts.
- Build all the magic stuff into the objects that each script had to implement each time.
- Follow the HIG automatically.
- The API should be memorable and predictable. Method and attribute names should follow some guidelines for tense, structure, etc.
- It must be VERY EASY to change the design of the interface.
I learned auto layout and built something that didn't work. I relearned auto layout and built something that didn't work. I relearned auto layout and built something that didn't work.
ezui is really ezui 5.0 or higher. It seems to have stabilized about a year ago so I'm considering it ready for use.
ezui is made of stuff. These stuff have names:
- item: a button, a field, a slider.
- container: an item that contains other items.
- window: a window.
Additionally, there are special things called forms that are containers of controls that are prebuilt for you.
There is no positioning geometry. You put your items in a container and they will be positioned automatically. There is very limited sizing geometry. The only time you need to give the size for something is when it doesn't have an intrinsic size. And, even then, there are fallbacks.
Each item may have an identifier. This identifier is used to automatically connect your callbacks to the item, to get/set values from/to the item and to get the item if you want to interact with it directly.
You don't build objects directly. You describe your containers and items with a Markdown and ASCII art inspired syntax called EZML (EZ Markup Language). You provide additional information for your items with simple dictionaries.
import ezui
class Demo(ezui.WindowController):
def build(self):
pass
def started(self):
pass
def destroy(self):
pass
Demo()
- Subclass
ezui.WindowController
. - Define
build
,started
methods. Optionally define adestroy
method.
To build your interface, define your items in build
. Open the window in started
.
import ezui
class Demo(ezui.WindowController):
def build(self):
content = """
(Hello World) @helloWorldButton
------X------ @slider
"""
descriptionData = dict(
silder1=dict(
value=0.5,
minValue=0,
maxValue=1.0,
tickMarks=3
)
)
self.w = ezui.EZWindow(
content=content,
descriptionData=descriptionData,
controller=self
)
def started(self):
self.w.open()
def destroy(self):
pass
def helloWorldButtonCallback(self, sender):
print("Hello world!")
def sliderCallback(self, sender):
value = sender.get()
print(f"slider: {value}")
Demo()
... and the syntax for creating them.
Label
(PushButton)
[ ] Checkbox
( ) RadioButtons
---X--- # Slider
[_TextField_]
|---| # Table
- group rows
- cell types
- popover at index
* WebView
(to show complex data)
If I see item combinations happening frequently, I'll try to add them as official items. For example, ---X--- [__] # SliderAndTextField
.
... and how nesting works.
* Box
* Tabs
* VerticalStack
* OneColumnForm
* TwoColumnForm
- Window
- Sheet
- PopUp
- File system interaction.
- Progress.
- Colors. (r, g, b, a) or NSColor
- Fonts.
- Images. (use SF Symbols)
- exercise 1
- exercise 2
- exercise 3
- migrating from vanilla
- splits
- working with Subscriber
- creating a custom item
- table pro
- views for rows
- getters and setters
- value converters
- drag and drop
- building without ezml (works but don't unless you really need to)
- maybe just build stuff upon request?