# Usage
## Basic XML/CSS Rendering with CompileContext and LifecycleManager
The `CompileContext` class is used to manage resources for controllers, including custom widgets, and styles/palettes:
```python
context = CompileContext("/path/to/base/dir")
```
A `LifecycleManager` is used to load layouts and switch between them:
```python
manager = LifecycleManager(context)
```
Then, layouts with XML and CSS can be created from a file path:
```python
manager.register(
"resources/layouts/layout.xml", # this will be resolved under the base path provided to CompileContext
"main" # name of the layout/controller
)
```
XML is used to create layouts. Note that attributes are passed as keyword arguments to urwid widgets. If an attribute's value starts with `@`, it will be treated as a resource and will be resolved from any loaded modules. String templates can also be used in a similar way by wrapping a value with brackets (`{}`).
Attributes in the MU namespace will be treated specially:
- `mu:id` - The ID of the widget. Used for styling and widget binding.
- `mu:class` - Any classes to apply to the widget. Used for styling.
- `mu:height` - Specify the height (or width in a horizontal container) of a widget.
- `mu:weight` - Specify the weight of a widget in its container. Overrides `mu:height`.
- `mu:pack` - Pack the widget in its container. Overrides `mu:weight`.
XML:
```xml
Hello, world
```
CSS:
```css
#root {
color: dark blue;
background: light gray;
}
.header {
color:
dark red,
bold,
italics;
}
.listbox-child {
background: dark gray;
}
```
Python modules can be used to define resources such as callbacks and various data, as well as the three layout lifecycle hooks: `on_load`, `on_enter`, and `on_exit`. The `on_load` hook and other callbacks are defined in the `tests.basic` module as referenced in the XML:
```python
import urwid
from modern_urwid import CompileContext, LayoutNode
from modern_urwid.compiler import create_wrapper
def on_load(ctx: CompileContext):
# get the widget with id "dynamic_listbox"
my_listbox: urwid.ListBox = ctx.get_local("main").get_widget_by_id( # "main" references the layout name
"dynamic_listbox"
)
# load the urwid palette names for the widgets we will create
_, palette, focus_palette = ctx.style_registry.get(
create_wrapper("button", classes="listbox-child")
)
# add children to the listbox
my_listbox.body.extend(
[
urwid.AttrMap(urwid.Button(f"This is custom button #{i}"), palette, focus_palette)
for i in range(10)
]
)
def on_edit_change(node: LayoutNode, ctx: CompileContext, w: urwid.Edit, full_text):
w.set_caption(f"Edit ({full_text}): ")
def quit_callback(node: LayoutNode, ctx: CompileContext, w):
raise urwid.ExitMainLoop()
```
Before the MainLoop can be run, a layout must be activated with `switch()`.
```python
manager.switch("main") # switch to the layout named "main"
manager.run() # calls urwid.MainLoop.run
```
## Rendering custom widgets
Custom widgets can be made by extending the `WidgetBuilder` class and registered with the `@context.widget_registry.register()` decorator:
```python
@context.widget_registry.register()
class CustomWidget(WidgetBuilder):
tag = "customwidget"
def build(self, **kwargs):
return urwid.Filler(
urwid.Text(f"This is a custom widget with tag <{self.node.tag}>")
)
```
Alternately, you can use the `` tag in `...`, which will automatically register all classes extending `WidgetBuilder` in the given module.
Custom widgets can also be created from XML with the `parse_xml_layout()` method:
```python
@context.widget_registry.register()
class AnotherCustomWidget(WidgetBuilder):
tag = "customwidgetfromxml"
def build(self, **kwargs):
return parse_xml_layout(
self.context.resolve_path("path/to/my/widget.xml"),
self.context,
)[0]
```
```{note}
Custom widgets must be registered **before** layouts are registered.
```