125 lines
4.2 KiB
Markdown
125 lines
4.2 KiB
Markdown
## How Canvas Library Custom Functionality Works
|
|
|
|
### 1. **The Canvas Library Calls YOUR Custom Code First**
|
|
|
|
When you call `ActionDispatcher::dispatch()`, here's what happens:
|
|
|
|
```rust
|
|
// Inside canvas library (canvas/src/actions/edit.rs):
|
|
pub async fn execute_canvas_action<S: CanvasState>(
|
|
action: CanvasAction,
|
|
state: &mut S,
|
|
ideal_cursor_column: &mut usize,
|
|
) -> Result<ActionResult> {
|
|
// 1. FIRST: Canvas library calls YOUR custom handler
|
|
if let Some(result) = state.handle_feature_action(&action, &context) {
|
|
return Ok(ActionResult::HandledByFeature(result)); // YOUR code handled it
|
|
}
|
|
|
|
// 2. ONLY IF your code returns None: Canvas handles generic actions
|
|
handle_generic_canvas_action(action, state, ideal_cursor_column).await
|
|
}
|
|
```
|
|
|
|
### 2. **Your Extension Point: `handle_feature_action`**
|
|
|
|
You add custom functionality by implementing `handle_feature_action` in your states:
|
|
|
|
```rust
|
|
// In src/state/pages/auth.rs
|
|
impl CanvasState for LoginState {
|
|
// ... other methods ...
|
|
|
|
fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option<String> {
|
|
match action {
|
|
// Custom login-specific actions
|
|
CanvasAction::Custom(action_str) if action_str == "submit_login" => {
|
|
if self.username.is_empty() || self.password.is_empty() {
|
|
Some("Please fill in all required fields".to_string())
|
|
} else {
|
|
// Trigger login process
|
|
Some(format!("Logging in user: {}", self.username))
|
|
}
|
|
}
|
|
|
|
CanvasAction::Custom(action_str) if action_str == "clear_form" => {
|
|
self.username.clear();
|
|
self.password.clear();
|
|
self.set_has_unsaved_changes(false);
|
|
Some("Login form cleared".to_string())
|
|
}
|
|
|
|
// Custom behavior for standard actions
|
|
CanvasAction::NextField => {
|
|
// Custom validation when moving between fields
|
|
if self.current_field == 0 && self.username.is_empty() {
|
|
Some("Username cannot be empty".to_string())
|
|
} else {
|
|
None // Let canvas library handle the normal field movement
|
|
}
|
|
}
|
|
|
|
// Let canvas library handle everything else
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. **Multiple Ways to Add Custom Functionality**
|
|
|
|
#### A) **Custom Actions via Config**
|
|
```toml
|
|
# In config.toml
|
|
[keybindings.edit]
|
|
submit_login = ["ctrl+enter"]
|
|
clear_form = ["ctrl+r"]
|
|
```
|
|
|
|
#### B) **Override Standard Actions**
|
|
```rust
|
|
fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option<String> {
|
|
match action {
|
|
CanvasAction::InsertChar('p') if self.current_field == 1 => {
|
|
// Custom behavior when typing 'p' in password field
|
|
Some("Password field - use secure input".to_string())
|
|
}
|
|
_ => None, // Let canvas handle normally
|
|
}
|
|
}
|
|
```
|
|
|
|
#### C) **Context-Aware Logic**
|
|
```rust
|
|
fn handle_feature_action(&mut self, action: &CanvasAction, context: &ActionContext) -> Option<String> {
|
|
match action {
|
|
CanvasAction::MoveDown => {
|
|
// Custom logic based on current state
|
|
if context.current_field == 1 && context.current_input.len() < 8 {
|
|
Some("Password should be at least 8 characters".to_string())
|
|
} else {
|
|
None // Normal field movement
|
|
}
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
```
|
|
|
|
## The Canvas Library Philosophy
|
|
|
|
**Canvas Library = Generic behavior + Your extension points**
|
|
|
|
- ✅ **Canvas handles**: Character insertion, cursor movement, field navigation, etc.
|
|
- ✅ **You handle**: Validation, submission, clearing, app-specific logic
|
|
- ✅ **You decide**: Return `Some(message)` to override, `None` to use canvas default
|
|
|
|
## Summary
|
|
|
|
You **don't communicate with the library elsewhere**. Instead:
|
|
|
|
1. **Canvas library calls your code first** via `handle_feature_action`
|
|
2. **Your code decides** whether to handle the action or let canvas handle it
|
|
3. **Canvas library handles** generic form behavior when you return `None`
|
|
|