v0.1.0 with spark
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Extension System
|
||||
|
||||
OSUI's extension system is a powerful and flexible mechanism for adding global, cross-cutting concerns to your application without cluttering individual widget implementations. It allows you to inject custom logic into the `Screen`'s lifecycle and rendering pipeline.
|
||||
|
||||
## Why an Extension System?
|
||||
|
||||
In UI development, certain functionalities are not specific to a single widget but affect the entire application or many widgets. Examples include:
|
||||
|
||||
* **Global Input Handling**: Capturing keyboard events and routing them to relevant widgets.
|
||||
* **Periodic Updates**: Driving animations or time-based logic across the UI.
|
||||
* **Custom Debugging/Logging**: Observing the rendering process or widget tree.
|
||||
* **Theming/Styling Overrides**: Applying consistent visual modifications dynamically.
|
||||
* **Data Persistence/Loading**: Interacting with external systems at application startup/shutdown.
|
||||
|
||||
Instead of scattering this logic throughout your `main` function or within every widget, the extension system provides a centralized, modular approach.
|
||||
|
||||
## The `Extension` Trait
|
||||
|
||||
The core of the system is the `Extension` trait, which defines a set of lifecycle hooks that the `Screen` will call at specific points.
|
||||
|
||||
```rust
|
||||
pub trait Extension {
|
||||
/// Called once when the screen starts running.
|
||||
fn init(&mut self, _screen: Arc<Screen>) {}
|
||||
|
||||
/// Called when the screen is being closed.
|
||||
fn on_close(&mut self, _screen: Arc<Screen>) {}
|
||||
|
||||
/// Called for each widget before its `render` method is invoked.
|
||||
fn render_widget(&mut self, _scope: &mut RenderScope, _widget: &Arc<Widget>) {}
|
||||
}
|
||||
```
|
||||
|
||||
* **`init(&mut self, screen: Arc<Screen>)`**:
|
||||
* **When**: Called exactly once when `Screen::run()` is invoked, before the main rendering loop begins.
|
||||
* **Purpose**: Ideal for one-time setup tasks like spawning background threads (e.g., for input polling or tick generation), initializing external resources, or setting up global state the extension will manage. The `Arc<Screen>` allows the extension to interact back with the screen (e.g., closing it, adding new widgets).
|
||||
* **`on_close(&mut self, screen: Arc<Screen>)`**:
|
||||
* **When**: Called exactly once when `Screen::close()` is invoked and the main rendering loop has exited.
|
||||
* **Purpose**: Cleanup. Restore terminal settings (like `InputExtension` disabling raw mode), release resources, save data, or perform final logging.
|
||||
* **`render_widget(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>)`**:
|
||||
* **When**: Called for every top-level `Arc<Widget>` in the `Screen`'s `widgets` list, during each frame's `Screen::render()` cycle. It's called *before* the widget's `Element::render` method.
|
||||
* **Purpose**: This is a powerful hook for inspecting or modifying the rendering context (`RenderScope`) or the `Widget` itself.
|
||||
* **Inspection**: You can use `widget.get::<C>()` to check a widget's components (e.g., its `Transform` or `Style`).
|
||||
* **Modification**: You can use `widget.set_component(c)` to dynamically add or change components (e.g., `VelocityExtension` modifies `Transform`). You can also directly modify the `RenderScope` (e.g., adding an offset, changing its style, or drawing overlay content).
|
||||
* **Filtering/Debugging**: Skip rendering certain widgets based on custom logic, or log their state.
|
||||
|
||||
## How Extensions are Integrated
|
||||
|
||||
1. **Instantiation**: You create an instance of your struct that implements `Extension`.
|
||||
2. **Registration**: You register the instance with your `Screen` using `screen.extension(my_extension_instance)`. This typically happens at the start of your `main` function.
|
||||
* The `Screen` stores `Arc<Mutex<Box<dyn Extension>>>` to allow multiple extensions, shared access, and dynamic dispatch.
|
||||
3. **Execution**: The `Screen`'s main `run()` and `render()` methods are hardwired to call the respective `Extension` trait methods at the appropriate times.
|
||||
|
||||
## Example: Custom Logging Extension
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CustomLoggingExtension;
|
||||
|
||||
impl Extension for CustomLoggingExtension {
|
||||
fn init(&mut self, screen: Arc<Screen>) {
|
||||
println!("[LOG] CustomLoggingExtension initialized for screen {:p}", Arc::as_ptr(&screen));
|
||||
}
|
||||
|
||||
fn on_close(&mut self, screen: Arc<Screen>) {
|
||||
println!("[LOG] CustomLoggingExtension closing for screen {:p}", Arc::as_ptr(&screen));
|
||||
}
|
||||
|
||||
fn render_widget(&mut self, scope: &mut RenderScope, widget: &Arc<Widget>) {
|
||||
// Log the coordinates and size of every widget about to be rendered
|
||||
let raw_transform = scope.get_transform(); // Get the already resolved raw transform
|
||||
println!(
|
||||
"[LOG] Rendering widget {:p} at ({}, {}) size ({}, {})",
|
||||
Arc::as_ptr(widget),
|
||||
raw_transform.x, raw_transform.y,
|
||||
raw_transform.width, raw_transform.height
|
||||
);
|
||||
|
||||
// Example: Apply a global offset for debugging
|
||||
// let mut current_transform = scope.get_transform_mut();
|
||||
// current_transform.x += 1;
|
||||
// current_transform.y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let screen = Screen::new();
|
||||
screen.extension(InputExtension); // Always useful for interaction
|
||||
screen.extension(CustomLoggingExtension); // Register our custom extension
|
||||
|
||||
rsx! {
|
||||
Div { "Hello" }
|
||||
@Transform::new().x(10).y(5);
|
||||
Div { "World" }
|
||||
}.draw(&screen);
|
||||
|
||||
screen.run()
|
||||
}
|
||||
```
|
||||
|
||||
When you run this, you'll see console output from `CustomLoggingExtension` as the screen initializes, renders each widget, and closes.
|
||||
|
||||
## Benefits of the Extension System
|
||||
|
||||
* **Modularity**: Keeps distinct functionalities separate, improving code organization.
|
||||
* **Reusability**: Extensions can be easily reused across different OSUI applications.
|
||||
* **Flexibility**: Allows injection of custom behavior without modifying OSUI's core library code.
|
||||
* **Separation of Concerns**: UI rendering logic is in `Element`s, state is in `State`s, and cross-cutting behaviors are in `Extension`s.
|
||||
|
||||
By leveraging the extension system, developers can build highly customized and feature-rich terminal applications with a clean and maintainable codebase.
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user