Your First App¶
A counter app, built up one piece at a time. The complete file is at the end.
The entry point¶
import nib
def main(app: nib.App):
...
nib.run(main)
nib.run(main) creates the App, calls your function with it, then starts the runtime and blocks until the app quits. Everything you configure happens inside main.
Configure the app¶
def main(app: nib.App):
app.title = "Counter"
app.icon = nib.SFSymbol("number.circle.fill")
app.width = 280
app.height = 200
title is shown next to the menu bar icon, icon is any SF Symbols name, width and height size the popover.
Add views¶
counter_display = nib.Text("0", font=nib.Font.TITLE)
app.build(
nib.VStack(
controls=[
counter_display,
nib.Button("Add +1"),
],
spacing=12,
padding=24,
)
)
app.build() sets the root view. Containers take their children in controls=; styling such as font, spacing and padding is passed as constructor arguments, never as chained method calls.
React to the button¶
count = 0
def increment():
nonlocal count
count += 1
counter_display.content = str(count)
nib.Button("Add +1", action=increment)
Assigning counter_display.content is the whole reactivity model: Nib diffs the view tree and sends the runtime only what changed. Every public property of a view works this way, including styling (counter_display.foreground_color = nib.Color.RED).
Add a context menu¶
def reset():
nonlocal count
count = 0
counter_display.content = "0"
app.menu = [
nib.MenuItem("Reset", action=reset, icon="arrow.counterclockwise"),
nib.MenuDivider(),
nib.MenuItem("Quit", action=app.quit, shortcut="cmd+q"),
]
The menu opens on a right click of the menu bar icon.
Run it¶
nib run main.py
Click the icon, press the button, right-click the icon. Edit the file and save: the app reloads with the runtime still running.
Complete code¶
import nib
def main(app: nib.App):
app.title = "Counter"
app.icon = nib.SFSymbol("number.circle.fill")
app.width = 280
app.height = 200
count = 0
counter_display = nib.Text("0", font=nib.Font.TITLE)
def increment():
nonlocal count
count += 1
counter_display.content = str(count)
def reset():
nonlocal count
count = 0
counter_display.content = "0"
app.menu = [
nib.MenuItem("Reset", action=reset, icon="arrow.counterclockwise"),
nib.MenuDivider(),
nib.MenuItem("Quit", action=app.quit, shortcut="cmd+q"),
]
app.build(
nib.VStack(
controls=[
counter_display,
nib.Button("Add +1", action=increment),
],
spacing=12,
padding=24,
)
)
nib.run(main)
Class-based variant¶
You can also subclass nib.App and override body(). It is re-evaluated on every render, so state lives in nib.State attributes rather than in view instances:
import nib
class Counter(nib.App):
count = nib.State(0)
def body(self) -> nib.View:
return nib.VStack(
controls=[
nib.Text(str(self.count), font=nib.Font.TITLE),
nib.Button("Add +1", action=self.increment),
],
spacing=12,
padding=24,
)
def increment(self):
self.count += 1
Counter(title="Counter", icon="number.circle.fill").run()
Both styles work with nib run and nib build. The function style is used throughout these docs.
Next¶
- Concepts: architecture and reactivity
- Guides: how-to pages
- Counter App: counter with a context menu
- App: properties and callbacks
- run()