All docs
Example: a dockable panel
A complete @tool.view that returns a View — a param() field plus an @action button that calls self.ctx.ui.notify. Runs headless too.
A View with no kind is param() fields plus @action buttons (informally a panel when docked in the sidebar) — no GTK, so
it imports and tests headless. Save this as tool.py, point LSD at it
("Add local package…"), and a Greeter panel docks in the sidebar.
The code#
# tool.py
from lsdtools import Tool, Context, View, action, param
tool = Tool("greeter", label="Greeter")
class GreetPanel(View):
"""A View with no kind: a param() field plus an @action button."""
name = param(str, default="world", label="Name")
times = param(int, default=1, min=1, max=10, label="Repeat")
@action(label="Greet", style="suggested")
def greet(self):
# self.ctx is injected when the panel mounts, so the host is one hop away.
for _ in range(self.times):
self.ctx.ui.notify(f"Hello, {self.name}!")
@tool.view("greeter.hello", title="Greeter", location="sidebar")
def panel(ctx: Context):
return GreetPanel()
What each line does#
class GreetPanel(View)— a panel is aView. Its fields areparam()descriptors, so they get typing, coercion, and the min/max validation (timesis clamped to 1–10) for free.@action(label="Greet", style="suggested")— a button. The handler reads the fields as typed attributes (self.name,self.times) — there is no values dict.styleisNone,"suggested", or"destructive".self.ctx.ui.notify(...)—self.ctxis the hostContext, injected automatically when the panel mounts, so an action can reach the host UI with no wiring.@tool.view("greeter.hello", title=…, location="sidebar")— docks the View in the sidebar. The decorated function is(ctx) -> View; return aViewinstance (or its class).
Run it#
You don't need the desktop to exercise the logic. invoke is the one dispatch entry for every
front-end, so a test drives the action directly — stub ctx.ui to capture the notifications:
from lsdtools import Context
class FakeUI:
def notify(self, message, level="info"):
print(f"[{level}] {message}")
p = GreetPanel(name="Ada", times=2)
p.ctx = Context.testing(ui=FakeUI())
p.invoke("greet")
# → [info] Hello, Ada!
# [info] Hello, Ada!
Or present the whole panel as text without a display — show("text") renders the fields and
actions:
GreetPanel(name="Ada").show("text")
# ── GreetPanel ──
# Name: 'Ada'
# Repeat: 1
# actions: Greet
In the desktop, loading the package puts a Greeter panel in the sidebar: type a name, set the repeat count, and click Greet to fire the notifications.
Try changing it#
- Add a
param(bool, default=False, label="Shout")and upper-case the greeting when it is set. - Turn
nameinto a dropdown withparam(str, default="world", choices=["world", "team"]). - Show a result instead of taking input — build a data view (a
Viewwith akind) and return it from@tool.view.
Related#
Learn — Params & actions · Everything you show is a View · Commands & actions
Start — How an LSD package fits together · Naming: which is which
Examples — Your first tool · A complete package