deargui-vpl/docs/shortcuts.md

10 KiB

Parameter Node Shortcuts Feature Plan

Overview

Parameter nodes can now act as "sources" and have "shortcut" clones that reference them. This allows creating multiple convenient access points to the same parameter value across the graph without physical links.

Core Concepts

  • Source Node: A parameter node marked as a source (m_IsSource = true) that acts as the master copy
  • Shortcut/Clone: A parameter node that references a source node (m_SourceID set) and syncs its value from the source
  • Value Synchronization: When a shortcut's value/name/type is edited, it updates the source, and all shortcuts automatically receive updates from the source

Feature Requirements

1. Source Node Toggle

  • Context menu option "As Source" toggles m_IsSource flag
  • Only visible for parameter nodes
  • When enabled, shows additional menu item "Create Shortcut"

2. Create Shortcut

  • Only visible when node is a source (m_IsSource == true)
  • Creates a new parameter node (clone) with:
    • Same type as source
    • Same value as source (retrieved via Run())
    • Same name as source (with optional suffix for uniqueness)
    • m_SourceID set to source node's ID
    • Positioned next to source node (offset right by ~200px)

3. Visual Differentiation

  • Source nodes: Render with different border color/style (e.g., gold/bright border, or "★" indicator)
  • Shortcut nodes: Render with different background color/style (e.g., slightly dimmed, or "📋" indicator)
  • Visual cues should be subtle but clear

4. Value Synchronization

  • Shortcut → Source: When shortcut's value/name/type is edited, update source immediately
  • Source → Shortcuts: When source's value/name/type is edited, all shortcuts sync (via Run() mechanism)
  • Use existing Run() infrastructure for value propagation

5. Source Deletion Handling

  • When source node is deleted, shortcuts remain but become independent
  • Set m_SourceID = 0 on all affected shortcuts
  • Clear m_IsSource = false if somehow a shortcut was marked as source

6. Save/Load

  • Save m_IsSource flag
  • Save m_SourceID (0 if not a shortcut)
  • On load, validate source still exists; if not, clear m_SourceID and m_IsSource

Affected Files & Classes

Core Parameter Node Files

  1. examples/blueprints-example/blocks/parameter_node.h

    • Add m_IsSource member (bool)
    • Add m_SourceID member (int, 0 if not a shortcut)
    • Add getter/setter methods for source management
    • Add method to find source node via App*
    • Add method to create shortcut from this node
  2. examples/blueprints-example/blocks/parameter_node.cpp

    • Modify OnMenu() to add "As Source" toggle
    • Modify OnMenu() to add "Create Shortcut" (conditional on m_IsSource)
    • Modify Render*() methods to show visual differentiation
    • Modify SetBool/Int/Float/String() to sync to source if this is a shortcut
    • Modify SetName() to sync to source if this is a shortcut
    • Modify Run() to check if source exists, sync from source if shortcut
    • Modify SaveState() to save m_IsSource and m_SourceID
    • Modify LoadState() to load m_IsSource and m_SourceID, validate source exists
    • Add helper method GetSourceNode() to find source via App*
    • Add helper method SyncValueToSource() for shortcut→source updates
    • Add helper method SyncValueFromSource() for source→shortcut updates
    • Add helper method CreateShortcut() to spawn clone at offset position

App Logic Files

  1. examples/blueprints-example/app-logic.cpp
    • Modify LoadGraph() to validate source nodes exist when loading shortcuts
    • Add helper method FindAllShortcutsForSource() (optional, for cleanup)
    • May need to handle source deletion in node removal code

App Render Files

  1. examples/blueprints-example/app-render.cpp
    • Context menu already calls ParameterInstance->OnMenu(), no changes needed
    • Node deletion already handled, but may want to add cleanup hook

Node Types/Structure

  1. examples/blueprints-example/types.h (if needed)
    • May need to add source-related fields to Node struct (optional, prefer keeping in ParameterNode)

Save/Load JSON Format

  1. JSON Structure Addition:
    {
      "node_type": "parameter",
      "param_type": 2,
      "is_source": true,        // NEW
      "source_id": 0,            // NEW (0 = not a shortcut, >0 = source node ID)
      "value": 42,
      "display_mode": 1
    }
    

Implementation Tasks

Phase 1: Core Data Structures

  • Add m_IsSource boolean member to ParameterNode class
  • Add m_SourceID int member to ParameterNode class (default 0)
  • Initialize both members in constructor
  • Add getter/setter methods: IsSource(), SetIsSource(), GetSourceID(), SetSourceID()

Phase 2: Context Menu Integration

  • Modify ParameterNode::OnMenu() to add "As Source" menu item (checkbox style)
  • Toggle m_IsSource when menu item clicked
  • Add conditional "Create Shortcut" menu item (only if m_IsSource == true)
  • Implement shortcut creation logic when menu item clicked

Phase 3: Shortcut Creation

  • Create ParameterNode::CreateShortcut() helper method
  • Get source node's position using ed::GetNodePosition()
  • Calculate offset position (source.x + 200, source.y)
  • Call App::SpawnParameterNode() with source's type and calculated position
  • Set new node's m_SourceID to source node's ID
  • Copy source's value to new node via Set*() methods
  • Set new node's name to source's name (or add suffix like "Copy")
  • Set new node's position using ed::SetNodePosition()

Phase 4: Value Synchronization (Shortcut → Source)

  • Modify ParameterNode::SetBool() to check if m_SourceID > 0
  • If shortcut, find source node via App::FindNode()
  • Update source's value and sync to source's ParameterInstance
  • Similarly modify SetInt(), SetFloat(), SetString()
  • Modify SetName() to sync to source if shortcut
  • Note: Type changes should propagate too, but may require node recreation (advanced)

Phase 5: Value Synchronization (Source → Shortcuts)

  • Add helper method SyncAllShortcuts() in ParameterNode (called from source)
  • In source's SetBool/Int/Float/String(), if m_IsSource == true, find all shortcuts
  • Iterate through all nodes in container, find ones with m_SourceID == this->m_ID
  • Update each shortcut's value via Set*() methods (avoid recursion by checking source)
  • Alternative: Use existing Run() mechanism - shortcuts call Run() which reads from source

Phase 6: Visual Differentiation

  • Modify RenderNameOnly() to show indicator for source/shortcut
  • Modify RenderNameAndValue() to show indicator and/or different border color
  • Modify RenderSmallBox() to show indicator
  • Source nodes: Gold/bright border or "★" icon
  • Shortcut nodes: Dimmed background or "📋" icon or different border style

Phase 7: Save/Load

  • Modify ParameterNode::SaveState() to save m_IsSource as boolean
  • Modify ParameterNode::SaveState() to save m_SourceID (0 if not a shortcut)
  • Modify ParameterNode::LoadState() to load m_IsSource
  • Modify ParameterNode::LoadState() to load m_SourceID
  • In LoadState(), validate source node exists: app->FindNode(ed::NodeId(m_SourceID))
  • If source doesn't exist, clear m_SourceID = 0 and m_IsSource = false (orphaned shortcut)

Phase 8: Source Deletion Handling

  • When node is deleted, check if it's a source (m_IsSource == true)
  • Find all shortcuts: iterate nodes, find ones with m_SourceID == deleted_node_id
  • Clear m_SourceID = 0 and m_IsSource = false on all found shortcuts
  • Implementation: Hook into node deletion in app-render.cpp or `app-logic.cpp
  • Alternative: Check in ParameterNode::Run() - if source not found, clear m_SourceID

Phase 9: Edge Cases & Polish

  • Handle circular references (source→shortcut→source, prevent infinite loops)
  • Handle source being a shortcut itself (prevent chains, keep flat)
  • Update tooltips or help text to explain source/shortcut concept
  • Test save/load with source nodes
  • Test save/load with shortcuts
  • Test source deletion with active shortcuts
  • Test creating shortcuts from shortcuts (should reference original source)

Technical Notes

Source ID Management

  • m_SourceID = 0 means "not a shortcut"
  • m_SourceID > 0 means "shortcut, source node ID is m_SourceID"
  • Source nodes have m_IsSource = true and m_SourceID = 0 (they are not shortcuts of themselves)

Value Sync Direction

  • Shortcut edits → Source: Direct update via Set*() methods
  • Source edits → Shortcuts: Can use existing Run() mechanism or direct updates
  • Recommendation: Use Run() for source→shortcut (already handles connected inputs), direct update for shortcut→source

Position Calculation

  • Offset new shortcut to the right of source: sourcePos.x + 200.0f, sourcePos.y
  • Use ed::GetNodePosition() and ed::SetNodePosition() for positioning
  • Consider node size for better spacing

Finding Shortcuts

  • When source updates, need to find all shortcuts
  • Iterate through container->GetNodes(app) or app->GetActiveRootContainer()->GetNodes(app)
  • Check each node: node->Type == NodeType::Parameter && node->ParameterInstance && node->ParameterInstance->GetSourceID() == source_id

Testing Checklist

  • Create parameter node, mark as source
  • Create shortcut from source
  • Edit source value, verify shortcut updates
  • Edit shortcut value, verify source updates
  • Create multiple shortcuts from same source
  • Edit source name, verify shortcuts sync (if implemented)
  • Delete source, verify shortcuts become independent
  • Save graph with source and shortcuts
  • Load graph with source and shortcuts
  • Load graph where source no longer exists (orphaned shortcuts)
  • Visual indicators show correctly for source/shortcut
  • Context menu shows/hides correctly

Future Enhancements (Out of Scope)

  • Type change propagation (requires node recreation)
  • Name sync from source to shortcuts
  • Batch operations (create multiple shortcuts at once)
  • Shortcut manager UI (list all shortcuts for a source)
  • Shortcut-to-source navigation (jump to source)
  • Breaking shortcut connection (make shortcut independent)