// // File: foldhaus_node_interface.cpp // Author: Peter Slattery // Creation Date: 2020-01-01 // #ifndef FOLHAUS_NODE_INTERFACE_CPP //////////////////////////////////////// // // Node Lister // /////////////////////////////////////// struct node_lister_operation_state { search_lister SearchLister; v2 ListPosition; }; internal void RenderNodeLister(panel Panel, rect PanelBounds, render_command_buffer* RenderBufer, app_state* State, context Context, mouse_state Mouse) { node_lister_operation_state* OpState = (node_lister_operation_state*)Operation.OpStateMemory; v2 TopLeft = OpState->ListPosition; v2 Dimension = v2{300, 30}; // Filter the lister OpState->SearchLister.Filter = State->ActiveTextEntry.Buffer; FilterSearchLister(&OpState->SearchLister); // Display Search Lister search_lister_result NodeListerResult = EvaluateSearchLister (&State->Interface_, TopLeft, Dimension, MakeStringLiteral("Nodes List"), OpState->SearchLister.SourceList, OpState->SearchLister.FilteredIndexLUT, OpState->SearchLister.FilteredListCount, OpState->SearchLister.HotItem, &State->ActiveTextEntry.Buffer, State->ActiveTextEntry.CursorPosition); } FOLDHAUS_INPUT_COMMAND_PROC(NodeListerNextItem) { node_lister_operation_state* OpState = GetCurrentOperationState(State->Modes, node_lister_operation_state); OpState->SearchLister.HotItem = GetNextFilteredItem(OpState->SearchLister); } FOLDHAUS_INPUT_COMMAND_PROC(NodeListerPrevItem) { node_lister_operation_state* OpState = GetCurrentOperationState(State->Modes, node_lister_operation_state); OpState->SearchLister.HotItem = GetPrevFilteredItem(OpState->SearchLister); } FOLDHAUS_INPUT_COMMAND_PROC(CloseNodeLister) { DeactivateCurrentOperationMode(&State->Modes); } FOLDHAUS_INPUT_COMMAND_PROC(SelectAndCloseNodeLister) { node_lister_operation_state* OpState = GetCurrentOperationState(State->Modes, node_lister_operation_state); s32 FilteredNodeIndex = OpState->SearchLister.HotItem; if (FilteredNodeIndex >= 0) { s32 NodeIndex = OpState->SearchLister.FilteredIndexLUT[FilteredNodeIndex]; PushNodeOnListFromSpecification(State->NodeList, (node_type)NodeIndex, Mouse.Pos, State->Permanent); } CloseNodeLister(State, Event, Mouse); } input_command UniverseViewCommads [] = { { KeyCode_DownArrow, KeyCode_Invalid, Command_Began, NodeListerNextItem }, { KeyCode_UpArrow, KeyCode_Invalid, Command_Began, NodeListerPrevItem }, { KeyCode_Enter, KeyCode_Invalid, Command_Began, SelectAndCloseNodeLister }, { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Began, CloseNodeLister }, { KeyCode_Esc, KeyCode_Invalid, Command_Began, CloseNodeLister }, DEFAULT_TEXT_ENTRY_INPUT_COMMANDS_ARRAY_ENTRY, }; FOLDHAUS_INPUT_COMMAND_PROC(OpenNodeLister) { operation_mode* AddNodeOperation = ActivateOperationModeWithCommands(&State->Modes, UniverseViewCommads); AddNodeOperation->Render = RenderNodeLister; node_lister_operation_state* OpState = CreateOperationState(AddNodeOperation, &State->Modes, node_lister_operation_state); { OpState->SearchLister.SourceListCount = NodeSpecificationsCount; OpState->SearchLister.SourceList = PushArray(&State->Modes.Arena, string, OpState->SearchLister.SourceListCount); { for (s32 i = 0; i < OpState->SearchLister.SourceListCount; i++) { OpState->SearchLister.SourceList[i] = MakeString( NodeSpecifications[i].Name, NodeSpecifications[i].NameLength); } } OpState->SearchLister.Filter = MakeString(PushArray(&State->Modes.Arena, char, 64), 0, 64); OpState->SearchLister.FilteredListMax = OpState->SearchLister.SourceListCount; OpState->SearchLister.FilteredListCount = 0; OpState->SearchLister.FilteredIndexLUT = PushArray(&State->Modes.Arena, s32, OpState->SearchLister.SourceListCount); } OpState->ListPosition = Mouse.Pos; SetTextInputDestinationToString(&State->ActiveTextEntry, &OpState->SearchLister.Filter); } //////////////////////////////////////// // // Node Color Picker // /////////////////////////////////////// struct color_picker_operation_state { v4* ValueAddr; }; internal void CloseColorPicker(app_state* State) { DeactivateCurrentOperationMode(&State->Modes); } FOLDHAUS_INPUT_COMMAND_PROC(CloseColorPickerCommand) { CloseColorPicker(State); } internal void RenderColorPicker(panel Panel, rect PanelBounds, render_command_buffer* RenderBufer, app_state* State, context Context, mouse_state Mouse) { color_picker_operation_state* OpState = (color_picker_operation_state*)Operation.OpStateMemory; b32 ShouldClose = EvaluateColorPicker(RenderBuffer, OpState->ValueAddr, v2{200, 200}, State->Interface, Mouse); if (ShouldClose) { CloseColorPicker(State); } } input_command ColorPickerCommands [] = { { KeyCode_Esc, KeyCode_Invalid, Command_Began, CloseColorPickerCommand }, }; internal void OpenColorPicker(app_state* State, node_connection* Connection) { operation_mode* ColorPickerMode = ActivateOperationModeWithCommands(&State->Modes, ColorPickerCommands); ColorPickerMode->Render = RenderColorPicker; color_picker_operation_state* OpState = CreateOperationState(ColorPickerMode, &State->Modes, color_picker_operation_state); OpState->ValueAddr = Connection->V4ValuePtr; } //////////////////////////////////////// // // Node Field Text Edit // /////////////////////////////////////// FOLDHAUS_INPUT_COMMAND_PROC(EndNodeFieldTextEdit) { DeactivateCurrentOperationMode(&State->Modes); } input_command NodeFieldTextEditCommands [] = { { KeyCode_Enter, KeyCode_Invalid, Command_Began, EndNodeFieldTextEdit }, { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Began, EndNodeFieldTextEdit }, DEFAULT_TEXT_ENTRY_INPUT_COMMANDS_ARRAY_ENTRY, }; internal void BeginNodeFieldTextEdit(app_state* State, node_connection* Connection) { operation_mode* NodeFieldTextEditMode = ActivateOperationModeWithCommands(&State->Modes, NodeFieldTextEditCommands); SetTextInputDestinationToFloat(&State->ActiveTextEntry, Connection->R32ValuePtr); } //////////////////////////////////////// // // Node Port Mouse Drag // /////////////////////////////////////// struct drag_node_port_operation_state { node_interaction Interaction; }; internal void RenderDraggingNodePort(panel Panel, rect PanelBounds, render_command_buffer* RenderBufer, app_state* State, context Context, mouse_state Mouse) { drag_node_port_operation_state* OpState = (drag_node_port_operation_state*)Operation.OpStateMemory; UpdateDraggingNodePort(Mouse.Pos, OpState->Interaction, State->NodeList, State->NodeRenderSettings, RenderBuffer); } FOLDHAUS_INPUT_COMMAND_PROC(EndDraggingNodePort) { drag_node_port_operation_state* OpState = GetCurrentOperationState(State->Modes, drag_node_port_operation_state); TryConnectNodes(OpState->Interaction, Mouse.Pos, State->NodeList, State->NodeRenderSettings); DeactivateCurrentOperationMode(&State->Modes); } input_command DragNodePortInputCommands[] = { { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Ended, EndDraggingNodePort }, }; internal void BeginDraggingNodePort(app_state* State, node_interaction Interaction) { operation_mode* DragNodePortMode = ActivateOperationModeWithCommands( &State->Modes, DragNodePortInputCommands); DragNodePortMode->Render = RenderDraggingNodePort; drag_node_port_operation_state* OpState = CreateOperationState(DragNodePortMode, &State->Modes, drag_node_port_operation_state); OpState->Interaction = Interaction; } //////////////////////////////////////// // // Node Field Mouse Drag // /////////////////////////////////////// internal void RenderDragNodeField(panel Panel, rect PanelBounds, render_command_buffer* RenderBufer, app_state* State, context Context, mouse_state Mouse) { // TODO(Peter): //UpdateDraggingNodeValue(Mouse.Pos, Mouse.OldPos, OpState->Interaction, State->NodeList, State->NodeRenderSettings, State); } internal void BeginInteractWithNodeField(app_state* State, node_interaction Interaction) { // TODO(Peter): } //////////////////////////////////////// // // Node Mouse Drag // /////////////////////////////////////// struct drag_node_operation_state { node_interaction Interaction; }; internal void RenderDraggingNode(panel Panel, rect PanelBounds, render_command_buffer* RenderBufer, app_state* State, context Context, mouse_state Mouse) { drag_node_operation_state* OpState = GetCurrentOperationState(State->Modes, drag_node_operation_state); UpdateDraggingNode(Mouse.Pos, OpState->Interaction, State->NodeList, State->NodeRenderSettings); } FOLDHAUS_INPUT_COMMAND_PROC(EndDraggingNode) { DeactivateCurrentOperationMode(&State->Modes); } input_command DragNodeInputCommands[] = { { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Ended, EndDraggingNode }, }; internal void BeginDraggingNode(app_state* State, node_interaction Interaction) { operation_mode* DragNodeMode = ActivateOperationModeWithCommands( &State->Modes, DragNodeInputCommands); DragNodeMode->Render = RenderDraggingNode; drag_node_operation_state* OpState = CreateOperationState(DragNodeMode, &State->Modes, drag_node_operation_state); OpState->Interaction = Interaction; } //////////////////////////////////////// // // Node View // /////////////////////////////////////// struct node_view_operation_state { s32 SelectedNodeHandle; }; FOLDHAUS_INPUT_COMMAND_PROC(NodeViewBeginMouseDragInteraction) { node_view_operation_state* OpState = GetCurrentOperationState(State->Modes, node_view_operation_state); node_header* Node = GetNodeUnderPoint(State->NodeList, Mouse.DownPos, State->NodeRenderSettings); if (Node) { node_interaction NewInteraction = GetNodeInteractionType(Node, Mouse.Pos, State->NodeRenderSettings); if (IsDraggingNodePort(NewInteraction)) { BeginDraggingNodePort(State, NewInteraction); } else if(IsDraggingNodeValue(NewInteraction)) { // TODO(Peter): This probably wants to live in a mouse held action // the first frame we realize we're held over a field, just transition to // drag node field //BeginInteractWithNodeField(State, NewInteraction, State->NodeRenderSettings); } else // IsDraggingNode { OpState->SelectedNodeHandle = Node->Handle; BeginDraggingNode(State, NewInteraction); } } else { OpState->SelectedNodeHandle = 0; } } FOLDHAUS_INPUT_COMMAND_PROC(NodeViewBeginMouseSelectInteraction) { node_view_operation_state* OpState = GetCurrentOperationState(State->Modes, node_view_operation_state); node_header* Node = GetNodeUnderPoint(State->NodeList, Mouse.Pos, State->NodeRenderSettings); if (Node) { node_interaction NewInteraction = GetNodeInteractionType(Node, Mouse.Pos, State->NodeRenderSettings); if(IsDraggingNodeValue(NewInteraction)) { node_connection* Connection = Node->Connections + NewInteraction.InputValue; struct_member_type InputType = Connection->Type; if (InputType == MemberType_r32) { BeginNodeFieldTextEdit(State, Connection); } else if (InputType == MemberType_v4) { OpenColorPicker(State, Connection); } } } } internal void RenderNodeView(panel Panel, rect PanelBounds, render_command_buffer* RenderBufer, app_state* State, context Context, mouse_state Mouse) { node_view_operation_state* OpState = (node_view_operation_state*)Operation.OpStateMemory; DEBUG_TRACK_FUNCTION; MakeStringBuffer(NodeHeaderBuffer, 128); node_header* SelectedNode = GetNodeWithHandle(State->NodeList, OpState->SelectedNodeHandle); node_list_iterator NodeIter = GetNodeListIterator(*State->NodeList); while (NodeIteratorIsValid(NodeIter)) { node_header* Node = NodeIter.At; rect NodeBounds = CalculateNodeBounds(Node, State->NodeRenderSettings); b32 DrawFields = PointIsInRect(Mouse.Pos, NodeBounds); if (Node == SelectedNode) { PushRenderQuad2D(RenderBuffer, NodeBounds.Min - v2{2, 2}, NodeBounds.Max + v2{2, 2}, WhiteV4); } PushRenderQuad2D(RenderBuffer, NodeBounds.Min, NodeBounds.Max, v4{.5f, .5f, .5f, 1.f}); // TODO(Peter): This is just for debug purposes. We can remove and go back to just having // Node->Name in DrawString string NodeName = GetNodeName(*Node); PrintF(&NodeHeaderBuffer, "%.*s: %d", NodeName.Length, NodeName.Memory, Node->Handle); DrawString(RenderBuffer, NodeHeaderBuffer, State->NodeRenderSettings.Font, v2{NodeBounds.Min.x + 5, NodeBounds.Max.y - (State->NodeRenderSettings.Font->PixelHeight + NODE_HEADER_HEIGHT + 5)}, WhiteV4); for (s32 Connection = 0; Connection < Node->ConnectionsCount; Connection++) { v4 PortColor = State->NodeRenderSettings.PortColors[Node->Connections[Connection].Type]; // Inputs if (ConnectionIsInput(Node, Connection)) { rect PortBounds = CalculateNodeInputPortBounds(Node, Connection, State->NodeRenderSettings); DrawPort(RenderBuffer, PortBounds, PortColor); // // TODO(Peter): I don't like excluding OutputNode, feels too much like a special case // but I don't want to get in to the meta programming right now. // We should just generate a spec and struct member types for NodeType_OutputNode // // :ExcludingOutputNodeSpecialCase // if (Node->Type != NodeType_OutputNode && DrawFields) { node_specification Spec = NodeSpecifications[Node->Type]; node_struct_member Member = Spec.MemberList[Connection]; DrawString(RenderBuffer, MakeString(Member.Name), State->NodeRenderSettings.Font, v2{PortBounds.Min.x - 8, PortBounds.Min.y}, WhiteV4, Align_Right); } rect ValueBounds = CalculateNodeInputValueBounds(Node, Connection, State->NodeRenderSettings); DrawValueDisplay(RenderBuffer, ValueBounds, Node->Connections[Connection], State->NodeRenderSettings.Font); // NOTE(Peter): its way easier to draw the connection on the input port b/c its a 1:1 relationship, // whereas output ports might have many connections, they really only know about the most recent one // Not sure if this is a problem. We mostly do everything backwards here, starting at the // most downstream node and working back up to find dependencies. if (ConnectionHasUpstreamConnection(Node, Connection)) { rect ConnectedPortBounds = GetBoundsOfPortConnectedToInput(Node, Connection, State->NodeList, State->NodeRenderSettings); v2 InputCenter = CalculateRectCenter(PortBounds); v2 OutputCenter = CalculateRectCenter(ConnectedPortBounds); PushRenderLine2D(RenderBuffer, OutputCenter, InputCenter, 1, WhiteV4); } } // Outputs if (ConnectionIsOutput(Node, Connection)) { rect PortBounds = CalculateNodeOutputPortBounds(Node, Connection, State->NodeRenderSettings); DrawPort(RenderBuffer, PortBounds, PortColor); if (DrawFields) { node_specification Spec = NodeSpecifications[Node->Type]; node_struct_member Member = Spec.MemberList[Connection]; DrawString(RenderBuffer, MakeString(Member.Name), State->NodeRenderSettings.Font, v2{PortBounds.Max.x + 8, PortBounds.Min.y}, WhiteV4); } rect ValueBounds = CalculateNodeOutputValueBounds(Node, Connection, State->NodeRenderSettings); DrawValueDisplay(RenderBuffer, ValueBounds, Node->Connections[Connection], State->NodeRenderSettings.Font); } for (s32 Button = 0; Button < 3; Button++) { rect ButtonRect = CalculateNodeDragHandleBounds(NodeBounds, Button, State->NodeRenderSettings); PushRenderQuad2D(RenderBuffer, ButtonRect.Min, ButtonRect.Max, DragButtonColors[Button]); } } Next(&NodeIter); } } FOLDHAUS_INPUT_COMMAND_PROC(NodeViewDeleteNode) { node_view_operation_state* OpState = GetCurrentOperationState(State->Modes, node_view_operation_state); if (OpState->SelectedNodeHandle > 0) { node_header* SelectedNode = GetNodeWithHandle(State->NodeList, OpState->SelectedNodeHandle); FreeNodeOnList(State->NodeList, SelectedNode); } } FOLDHAUS_INPUT_COMMAND_PROC(CloseNodeView) { DeactivateCurrentOperationMode(&State->Modes); } input_command NodeViewCommands [] = { { KeyCode_Tab, KeyCode_Invalid, Command_Began, CloseNodeView}, { KeyCode_A, KeyCode_Invalid, Command_Began, OpenNodeLister}, { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Began, NodeViewBeginMouseDragInteraction}, { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Ended, NodeViewBeginMouseSelectInteraction}, { KeyCode_X, KeyCode_Invalid, Command_Began, NodeViewDeleteNode}, }; FOLDHAUS_INPUT_COMMAND_PROC(OpenNodeView) { operation_mode* NodeViewMode = ActivateOperationModeWithCommands(&State->Modes, NodeViewCommands); NodeViewMode->Render = RenderNodeView; node_view_operation_state* OpState = CreateOperationState(NodeViewMode, &State->Modes, node_view_operation_state); OpState->SelectedNodeHandle = 0; } #define FOLHAUS_NODE_INTERFACE_CPP #endif // FOLHAUS_NODE_INTERFACE_CPP