Files
komp_ac/client/docs/canvas_add_functionality.md

4.2 KiB

How Canvas Library Custom Functionality Works

1. The Canvas Library Calls YOUR Custom Code First

When you call ActionDispatcher::dispatch(), here's what happens:

// 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:

// 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

# In config.toml
[keybindings.edit]
submit_login = ["ctrl+enter"]
clear_form = ["ctrl+r"]

B) Override Standard Actions

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

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