*/}}
Browse Source

Input mapper basics

Yiming Wu 2 years ago
parent
commit
64e982a8cd

+ 55 - 20
source/lagui/la_controllers.c

@@ -8,6 +8,7 @@
 #include <sys/ioctl.h>
 #include <linux/input.h>
 #include <linux/joystick.h>
+#include <errno.h>
 
 extern LA MAIN;
 
@@ -25,6 +26,9 @@ STRUCTURE(laJoystickEvent){
 #define LA_JS_TYPE_X56_THROTTLE 1
 #define LA_JS_TYPE_X56_STICK 2
 
+char LA_JS_BTN_NAMES[LA_JS_MAX_BUTTONS][10];
+char LA_JS_AXIS_NAMES[LA_JS_MAX_AXES][10];
+
 int la_IdentifyControllerInternalType(char* name){
     if(strstr(name, "X-56") && strstr(name, "Throttle")){ return LA_JS_TYPE_X56_THROTTLE; }
     if(strstr(name, "X-56") && strstr(name, "Stick")){ return LA_JS_TYPE_X56_STICK; }
@@ -38,11 +42,16 @@ laController* la_NewController(char* name, char* path, int device, int NumAxes,
     c->NumAxes = NumAxes; c->NumButtons=NumButtons;
     c->InternalType = la_IdentifyControllerInternalType(name);
     lstAppendItem(&MAIN.Controllers,c);
+    c->UserAssignedID=MAIN.NextControllerID; MAIN.NextControllerID++;
     return c;
 }
 void la_DestroyController(laController* c){
-    free(c->ButtonValues); strSafeDestroy(&c->Name); strSafeDestroy(&c->Path);
-    memFree(c);
+    strSafeDestroy(&c->Name); strSafeDestroy(&c->Path);
+    memFree(c); laNotifyUsers("la.controllers");
+}
+
+laController* la_FindControllerWithID(int id){
+    for(laController* c=MAIN.Controllers.pFirst;c;c=c->Item.pNext){ if(c->UserAssignedID==id) return c; } return 0;
 }
 
 void la_InitControllers(){
@@ -67,8 +76,8 @@ void la_InitControllers(){
 
 void la_UpdateControllerStatus(){
     laJoystickEvent event; int HasEvent=0;
-    for(laController* c=MAIN.Controllers.pFirst;c;c=c->Item.pNext){
-        while(read(c->fd, &event, sizeof(laJoystickEvent))>0){
+    for(laController* c=MAIN.Controllers.pFirst;c;c=c->Item.pNext){ if(c->Error) continue;
+        int bytes; while((bytes=read(c->fd, &event, sizeof(laJoystickEvent)))>0){
             if(event.type&LA_JS_EVENT_BUTTON){
                 if(event.number>=c->NumButtons) continue;
                 c->ButtonValues[event.number]=event.value; HasEvent=1;
@@ -80,10 +89,20 @@ void la_UpdateControllerStatus(){
                 printf("a %d %d\n", event.number, event.value); HasEvent=1;
             }
         }
+        if(bytes<=0){ struct stat buffer; if(stat(c->Path->Ptr,&buffer)<0){ c->Error=1; HasEvent=1; } }
     }
-    if(HasEvent) laNotifyUsers("la.controllers");
+    if(HasEvent){ laNotifyUsers("la.controllers"); laMappingRequestEval(); }
 }
 
+void la_RefreshControllers(){
+    laController* c; while(c=lstPopItem(&MAIN.Controllers)){ la_DestroyController(c); } MAIN.NextControllerID=0;
+    la_InitControllers();
+}
+int OPINV_RefreshControllers(){
+    la_RefreshControllers(); return LA_FINISHED;
+}
+
+
 void la_AddButtonProp(laPropContainer* pc, char* id, char* name, char* desc, int i, int array_len, char* array_prefix){
     laProp* p=laAddEnumProperty(pc, id, name, desc, LA_WIDGET_ENUM_HIGHLIGHT,
         array_prefix,0,0,0,offsetof(laController, ButtonValues[i]),0,0,array_len,0,0,0,0,0,0,LA_READ_ONLY);
@@ -92,22 +111,14 @@ void la_AddButtonProp(laPropContainer* pc, char* id, char* name, char* desc, int
     p->ElementBytes=1;
 }
 void la_AddAxisProp(laPropContainer* pc, char* id, char* name, char* desc, int i, int array_len, char* array_prefix){
-    laProp* p=laAddIntProperty(pc,id,name,desc,array_len>1?LA_WIDGET_INT_METER_2D:LA_WIDGET_INT_METER,
+    laProp* p=laAddIntProperty(pc,id,name,desc,array_len>1?LA_WIDGET_VALUE_METER_2D:LA_WIDGET_VALUE_METER,
         array_prefix,0,32768,-32767,1,0,0,offsetof(laController, AxisValues[i]),0,0,array_len,0,0,0,0,0,0,0,LA_READ_ONLY);
 }
 void la_AddGenericButtonProps(laPropContainer* pc){
-    char id[16]=""; char name[16]=""; char Description[16]=""; laProp* p;
-    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){
-        sprintf(id,"b%d",i); sprintf(name ,"%d",i); sprintf(Description ,"Button %d",i);
-        la_AddButtonProp(pc,id,name,Description,i,0,0);
-    }
+    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){ char* name=LA_JS_BTN_NAMES[i]; la_AddButtonProp(pc,name,name,name,i,0,0); }
 }
 void la_AddGenericAxisProps(laPropContainer* pc){
-    char id[4]=""; char name[4]=""; char Description[16]=""; laProp* p;
-    for(int i=0;i<LA_JS_MAX_AXES;i++){
-        sprintf(id,"a%d",i); sprintf(name ,"%d",i); sprintf(Description ,"Axis %d",i);
-        la_AddAxisProp(pc,id,name,Description,i,0,0);
-    }
+    for(int i=0;i<LA_JS_MAX_AXES;i++){ char* name=LA_JS_AXIS_NAMES[i]; la_AddAxisProp(pc,name,name,name,i,0,0); }
 }
 
 void laui_X56Throttle(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context);
@@ -126,12 +137,19 @@ laPropContainer* laget_ControllerType(laController* c){
 }
 
 void la_RegisterControllerProps(){
+    for(int i=0;i<LA_JS_MAX_AXES;i++){ sprintf(LA_JS_AXIS_NAMES[i],"a%d",i); }
+    for(int i=0;i<LA_JS_MAX_BUTTONS;i++){ sprintf(LA_JS_BTN_NAMES[i],"b%d",i); }
+
+    laCreateOperatorType("LA_refresh_controllers", "Refresh Controllers", "Look for connected controllers",0,0,0,OPINV_RefreshControllers,0,L'🗘',0);
+
     laPropContainer* pc; laProp* p;
 
     pc=laAddPropertyContainer("la_controller", "Controller", "A joystick/gamepad controller", L'🕹', 0, sizeof(laController), 0,0,1);
     LA_PC_JS_GENERIC = pc;
-    laAddStringProperty(pc,"name","Name","Name of the controller", LA_WIDGET_STRING_PLAIN,0,0,0,1,offsetof(laController,Name),0,0,0,0,LA_READ_ONLY|LA_AS_IDENTIFIER);
+    laAddStringProperty(pc,"name","Name","Name of the controller", LA_WIDGET_STRING_PLAIN,0,0,0,1,offsetof(laController,Name),0,0,0,0,LA_READ_ONLY);
     laAddStringProperty(pc,"path","Path","Device path to the controller", LA_WIDGET_STRING_PLAIN,0,0,0,1,offsetof(laController,Path),0,0,0,0,LA_READ_ONLY);
+    laAddIntProperty(pc,"user_assigned_id", "ID", "User assigned ID", 0,0,0,0,0,0,0,0,offsetof(laController, UserAssignedID),0,0,0,0,0,0,0,0,0,0,LA_AS_IDENTIFIER);
+    laAddIntProperty(pc,"error", "Error", "Device error", 0,0,0,0,0,0,0,0,offsetof(laController, Error),0,0,0,0,0,0,0,0,0,0,0);
     la_AddGenericButtonProps(pc);
     la_AddGenericAxisProps(pc);
 
@@ -205,7 +223,15 @@ void laui_X56Throttle(laUiList *uil, laPropPack *This, laPropPack *Extra, laColu
     laUiItem* b,*ui,*g; laUiList*gu;
 
     laShowItem(uil,c,This,"base.name")->Flags|=LA_TEXT_ALIGN_CENTER;
-    laShowItem(uil,c,This,"base.path")->Flags|=LA_TEXT_ALIGN_CENTER;
+    b=laBeginRow(uil,c,0,0);
+    laShowItem(uil,c,This,"base.user_assigned_id");
+    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(This,"base.error"));{
+        laShowLabel(uil,c,"Device error.",0,0)->Expand=1;
+    }laElse(uil,b1);{
+        laShowItem(uil,c,This,"base.path")->Expand=1;
+    }laEndCondition(uil,b1);
+    laEndRow(uil,b);
+    laShowSeparator(uil,c);
 
     laShowItem(uil,cl,This,"pinky_up");
     laShowItem(uil,cl,This,"pinky_dn");
@@ -272,9 +298,18 @@ void laui_X56Stick(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn
     laColumn* c=laFirstColumn(uil),*cl, *cc, *cr;
     laSplitColumn(uil,c,0.2); cl=laLeftColumn(c,10); cr=laRightColumn(c,0);
     laSplitColumn(uil,cr,0.8); cc=laLeftColumn(cr,0); cr=laRightColumn(cr,10);
-    
+    laUiItem* b;
+
     laShowItem(uil,c,This,"base.name")->Flags|=LA_TEXT_ALIGN_CENTER;
-    laShowItem(uil,c,This,"base.path")->Flags|=LA_TEXT_ALIGN_CENTER;
+    b=laBeginRow(uil,c,0,0);
+    laShowItem(uil,c,This,"base.user_assigned_id");
+    laUiItem* b1=laOnConditionThat(uil,c,laPropExpression(This,"base.error"));{
+        laShowLabel(uil,c,"Device error.",0,0)->Expand=1;
+    }laElse(uil,b1);{
+        laShowItem(uil,c,This,"base.path")->Expand=1;
+    }laEndCondition(uil,b1);
+    laEndRow(uil,b);
+    laShowSeparator(uil,c);
 
     laShowItem(uil,cl,This,"ba");
     laShowItem(uil,cl,This,"pov");

+ 22 - 8
source/lagui/la_data.c

@@ -204,7 +204,6 @@ void *la_FindMatchingInstance(void *From, laProp *Sub, laProp *p, laPropStep *Va
         _buf[0] = 0;
         FakePs.UseInstance = Inst;
         laGetString(&Fake, _buf, &buf);
-
         while (!strSame(buf, Value->p)){
             Inst = laGetNextInstance(Sub, Inst, &pi);
             if (!Inst) return 0;
@@ -212,12 +211,12 @@ void *la_FindMatchingInstance(void *From, laProp *Sub, laProp *p, laPropStep *Va
             FakePs.UseInstance = Inst;
             laGetString(&Fake, _buf, &buf);
         }
-
         return Inst;
     }elif (p->PropertyType == LA_PROP_INT){
         FakePs.UseInstance = Inst;
-        val = laGetInt(&Fake);
-        while (val != Value->UseInstance){
+        val = laGetInt(&Fake); char buf[64]; int compareval=0;
+        int status=sscanf(buf,"%d",&compareval);
+        while (status && status!=EOF && val != compareval){
             Inst = laGetNextInstance(Sub, From, &pi);
             if (!Inst) return 0;
             FakePs.UseInstance = Inst;
@@ -286,7 +285,7 @@ int la_GetPropFromPath(laPropPack *Self, laPropPack *Base, const char *Path, voi
             lstB = p->SubExtra ? &p->SubExtra->Props : 0;
         }else{
             if (sp->Type == L'@' || sp->Type == L'=' || sp->Type == L'#'){ // || sp->Type== L'~'
-                la_NewPropStep(Self, sp->Content, sp->IntValue, sp->Type);
+                la_NewPropStep(Self, sp->Content, 0, sp->Type);
             }
         }
         sp = sp->Item.pNext;
@@ -419,7 +418,7 @@ void la_AddPropUserNode(laPropStep *ps){
 }
 void la_UsePropPack(laPropPack *pp, int ForceRecalc){
     laPropStep *ps = 0;
-    //return;
+    //if(pp->LastPs && pp->LastPs->p)printf("%s\n",pp->LastPs->p->Identifier);
     if((!pp->Go) && pp->LastPs){
         if (pp->LastPs->Type==L'.'){
             laUseDataBlock(pp->RawThis->LastPs->UseInstance, pp->LastPs->p, MAIN.PropMatcherContextP->FrameDistinguish, MAIN.PropMatcherContextP, la_PropPanelUserRemover, ForceRecalc);
@@ -491,6 +490,21 @@ void laNotifySubPropUsers(laProp *prop, void *Instance){
         }
     }
 }
+void laNotifyInstanceUsers(void *Instance){
+    void *hi = Instance;
+    laItemUserLinker *iul; laItemUserLinkerLocal *iull; int local; if (!hi) return;
+    laListHandle* users = laGetUserList(hi, 0, &local); if(!users) return;
+    for (iul = users->pFirst; iul; iul = iul->Pointer.pNext){
+        if(local && (iull=iul) && iull->Instance!=hi) continue;
+        if (iul->Remove == la_PropPanelUserRemover){
+            laPanel *p = iul->Pointer.p;
+            if (iul->FrameDistinguish == p->FrameDistinguish){
+                p->Refresh |= LA_TAG_REDRAW;
+                if(iul->Which&&iul->Which->PropertyType==LA_PROP_SUB){ p->Refresh|=LA_TAG_RECALC; }
+            }
+        }
+    }
+}
 void laNotifyUsers(char *Path){
     laPropPack PP = {0};
     la_GetPropFromPath(&PP, 0, Path, 0);
@@ -721,7 +735,7 @@ laProp *la_MakeDetachedProp(laPanel* p, const char *From, const char *Rename){
     result = la_GetPropFromPath(&TempPP, 0, From, 0);
     if(!result) return 0;
     
-    laProp *np = CreateNew_Size(la_GetPropertySize(TempPP.LastPs->p->PropertyType));
+    laProp *np = memAcquire(la_GetPropertySize(TempPP.LastPs->p->PropertyType));
     memcpy(np, TempPP.LastPs->p, la_GetPropertySize(TempPP.LastPs->p->PropertyType));
 
     np->Identifier = CreateNewBuffer(char, 128);
@@ -1839,7 +1853,7 @@ void laSetActiveInstance(laProp *sub, void *FromInstance, void *Instance){
     laSubProp *sp = sub;
     if (sub->PropertyType == LA_PROP_SUB){
         if (sp->Base.DetachedPP.LastPs){
-            sp->Detached = Instance;
+            memAssignRef(sp, &sp->Detached, Instance);
             laNotifySubPropUsers(sp, FromInstance);
             return;
         }

+ 1 - 0
source/lagui/la_data.h

@@ -678,6 +678,7 @@ void laNotifyUsersPP(laPropPack *pp);
 void laNotifyUsersPPPath(laPropPack *pp, char *path);
 void laNotifySubPropUsers(laProp *p, void *Instance);
 void laNotifyUsers(char *Path);
+void laNotifyInstanceUsers(void *Instance);
 void laThreadNotifyUsers(char *Path);
 
 int laIsPropertyReadOnly(laPropPack *pp);

+ 78 - 9
source/lagui/la_interface.h

@@ -204,13 +204,13 @@ STRUCTURE(laNodeOutSocket){
     void* Parent;
     void* Data;
     laSafeString* Label;
-    int DataType;
+    int DataType, ArrLen;
     int RuntimeX, RuntimeY, RuntimePX, RuntimePY;
 };
 STRUCTURE(laNodeInSocket){
     laNodeOutSocket* Source;
     laSafeString* Label;
-    int DataType; int ColorId;
+    int DataType, ArrLen; int ColorId;
     int RuntimeX, RuntimeY, RuntimePX, RuntimePY;
 };
 
@@ -218,7 +218,7 @@ STRUCTURE(LA){
     laListItem Hyper;
 
     laListHandle Logs;
-    laListHandle Controllers;
+    laListHandle Controllers; int NextControllerID;
 
     laListHandle Windows;
     laListHandle WastedPanels;
@@ -384,7 +384,11 @@ STRUCTURE(LA){
 
     laListHandle MediaFiles;
 
-    laHash256 RootNodes;
+    laListHandle InputMappingRacks;
+    laListHandle InputMappingEval;
+    int MappingNeedEval,MappingNeedRebuild;
+
+    //laHash256 RootNodes;
     //LLVMContextRef llvmContext;
     //LLVMModuleRef llvmModule;
 
@@ -579,6 +583,7 @@ STRUCTURE(laSocketRecord){
 
 extern laPropContainer* LA_PC_SOCKET_IN;
 extern laPropContainer* LA_PC_SOCKET_OUT;
+extern laProp* LA_PROP_CONTROLLER;
 
 #define LA_UI_NORMAL  0
 #define LA_UI_ACTIVE  (1<<0)
@@ -886,8 +891,8 @@ extern laWidget* LA_WIDGET_LABEL;
 extern laWidget* LA_WIDGET_INT;
 extern laWidget* LA_WIDGET_INT_PLAIN;
 extern laWidget* LA_WIDGET_INT_PLAIN_ICON;
-extern laWidget* LA_WIDGET_INT_METER;
-extern laWidget* LA_WIDGET_INT_METER_2D;
+extern laWidget* LA_WIDGET_VALUE_METER;
+extern laWidget* LA_WIDGET_VALUE_METER_2D;
 extern laWidget* LA_WIDGET_FLOAT;
 extern laWidget* LA_WIDGET_FLOAT_PLAIN;
 extern laWidget* LA_WIDGET_FLOAT_COLOR;
@@ -1153,7 +1158,7 @@ STRUCTURE(laController){
     laSafeString* Name;
     laSafeString* Path;
     int fd; //device;
-    int NumButtons; int NumAxes;
+    int NumButtons; int NumAxes; int UserAssignedID; int Error;
     int InternalType; // used to identify models and use specific props.
     int AxisValues[LA_JS_MAX_AXES];
     char ButtonValues[LA_JS_MAX_BUTTONS];
@@ -1163,6 +1168,68 @@ void la_InitControllers();
 void la_UpdateControllerStatus();
 void la_RegisterControllerProps();
 laPropContainer* laget_ControllerType(laController* c);
+laController* la_FindControllerWithID(int id);
+
+void la_RegisterInputMapperOperators();
+
+NEED_STRUCTURE(laInputMapperNode);
+typedef void (*laInputMapperNodeInitF)(laInputMapperNode*);
+typedef void (*laInputMapperNodeDestroyF)(laInputMapperNode*);
+typedef int (*laInputMapperNodeVisitF)(laInputMapperNode*, laListHandle*);
+typedef int (*laInputMapperNodeEvalF)(laInputMapperNode*);
+
+#define LA_DAG_FLAG_ERR  0
+#define LA_DAG_FLAG_TEMP 1
+#define LA_DAG_FLAG_PERM 2
+
+void laMappingRequestRebuild();
+void laMappingRequestEval();
+int la_RebuildInputMapping();
+int la_RunInputMapping();
+
+STRUCTURE(laInputRack){
+    laListItem Item;
+    laSafeString* Name;
+    laListHandle Nodes;
+};
+STRUCTURE(laInputMapperNodeType){
+    laInputMapperNodeInitF    Init;
+    laInputMapperNodeDestroyF Destroy;
+    laInputMapperNodeVisitF   Visit;
+    laInputMapperNodeEvalF    Eval;
+    int NodeSize;
+};
+STRUCTURE(laInputMapperNode){
+    laListItem Item;
+    laSafeString* Name;
+    laInputMapperNodeType* Type;
+    laInputRack* InRack;
+    int Gap;
+    int Eval;
+};
+NEED_STRUCTURE(laInputControllerNode);
+STRUCTURE(laInputControllerNodeSocket){
+    laInputControllerNode* Parent;
+    laSafeString* Which;
+    laNodeOutSocket* Out;
+    int Offset;
+    int IntVal[8]; real RealVal[8];
+};
+STRUCTURE(laInputControllerNode){
+    laInputMapperNode Base;
+    laInputControllerNodeSocket Sockets[8];
+    int UserID;
+    int Mode;// btn/axis;
+};
+STRUCTURE(laInputVisualizerNode){
+    laInputMapperNode Base;
+    laNodeInSocket* In;
+    int IntVal[8]; real RealVal[8];
+};
+
+#define LA_INPUT_CONTROLLER_NODE_MODE_BTN 0
+#define LA_INPUT_CONTROLLER_NODE_MODE_AXIS 1
+
 
 void logPrintT(int Type, char* format, ...);
 void logPrint(char* format, ...);
@@ -1521,6 +1588,8 @@ laPropContainer* laDefineOperatorProps(laOperatorType* ot, int HyperLevel);
 
 laNodeOutSocket* laCreateOutSocket(void* NodeParentOptional, char* label, int DataType);
 laNodeInSocket* laCreateInSocket(char* label, int DataType);
+void laDestroyInSocket(laNodeInSocket* s);
+void laDestroyOutSocket(laNodeOutSocket* s);
 
 
 void laFreeKeyMapItem(laKeyMapItem* kmi);
@@ -1604,8 +1673,8 @@ extern laUiType *_LA_UI_COLLECTION_SINGLE;
 extern laUiType *_LA_UI_BUTTON;
 extern laUiType *_LA_UI_LABEL;
 extern laUiType *_LA_UI_INT;
-extern laUiType *_LA_UI_INT_METER;
-extern laUiType *_LA_UI_INT_METER_2D;
+extern laUiType *_LA_UI_VALUE_METER;
+extern laUiType *_LA_UI_VALUE_METER_2D;
 extern laUiType *_LA_UI_FLOAT;
 extern laUiType *_LA_UI_FLOAT_COLOR;
 extern laUiType *_LA_UI_FLOAT_HCY;

+ 12 - 1
source/lagui/la_kernel.c

@@ -385,6 +385,7 @@ int laGetReady(){
 
     la_InitControllers();
     la_RegisterControllerProps();
+    la_RegisterInputMapperOperators();
 
     la_InitThreadEnviornment();
 
@@ -577,13 +578,14 @@ void la_SaveEvent(Window hwnd, laEvent *e, int use_last_pos){
     
     if(use_last_pos){
         laEvent* last_e=el->pLast;
-        if(last_e){ e->x= last_e->x; e->y= last_e->y; }else {
+        if(last_e){ e->x= last_e->x; e->y= last_e->y; }else{
             Window root_ret, win_ret; int rrx,rry,rx,ry,rmask;
             XQueryPointer(MAIN.dpy, hwnd, &root_ret,&win_ret,&rrx,&rry,&rx,&ry,&rmask);
             e->x = rx; e->y = ry;
         }
     }
     lstAppendItem(el, (laListItem *)e);
+    laMappingRequestEval();
 };
 void la_SendKeyboardEvent(Window hwnd, int type, int key){
     laEvent *e = CreateNew(laEvent);
@@ -5320,6 +5322,12 @@ laNodeInSocket* laCreateInSocket(char* label, int DataType){
     strSafeSet(&is->Label, label); is->DataType = DataType;
     return is;
 }
+void laDestroyInSocket(laNodeInSocket* s){
+    strSafeDestroy(&s->Label); memFree(s);
+}
+void laDestroyOutSocket(laNodeOutSocket* s){
+    strSafeDestroy(&s->Label); memFree(s);
+}
 
 //==================================================================================================
 
@@ -6388,6 +6396,9 @@ void laMainLoop(){
 
         la_UpdateControllerStatus();
 
+        if(MAIN.MappingNeedRebuild){ la_RebuildInputMapping(); }
+        if(MAIN.MappingNeedEval){ la_RunInputMapping(); }
+
         for (w=MAIN.Windows.pFirst;w;w = NextW){
             NextW = w->Item.pNext;
             if(!la_HandleEvents(w)){ laShutoff(); return; }

+ 2 - 2
source/lagui/la_resource.c

@@ -18,8 +18,8 @@ laUiType *_LA_UI_COLLECTION_SINGLE;
 laUiType *_LA_UI_BUTTON;
 laUiType *_LA_UI_LABEL;
 laUiType *_LA_UI_INT;
-laUiType *_LA_UI_INT_METER;
-laUiType *_LA_UI_INT_METER_2D;
+laUiType *_LA_UI_VALUE_METER;
+laUiType *_LA_UI_VALUE_METER_2D;
 laUiType *_LA_UI_FLOAT;
 laUiType *_LA_UI_FLOAT_COLOR;
 laUiType *_LA_UI_FLOAT_HCY;

+ 309 - 0
source/lagui/resources/la_input_mapping.c

@@ -0,0 +1,309 @@
+#include "../la_5.h"
+
+extern LA MAIN;
+extern struct _tnsMain *T;
+
+
+laInputMapperNodeType LA_IDN_KEYBOARD;
+laInputMapperNodeType LA_IDN_MOUSE;
+laInputMapperNodeType LA_IDN_CONTROLLER;
+laInputMapperNodeType LA_IDN_VISUALIZER;
+
+laPropContainer* LA_PC_IDN_GENERIC;
+laPropContainer* LA_PC_IDN_KEYBOARD;
+laPropContainer* LA_PC_IDN_MOUSE;
+laPropContainer* LA_PC_IDN_CONTROLLER;
+laPropContainer* LA_PC_IDN_VISUALIZER;
+
+#define LA_IDN_CONTROLLER_RESET_SOCKET(ns)\
+    {ns->IntVal[0]=0; ns->Out->DataType=LA_PROP_INT; ns->Offset=0; ns->Out->Data=&ns->IntVal;}
+
+void IDN_ControllerInit(laInputControllerNode* n){
+    for(int i=0;i<8;i++){ n->Sockets[i].Out=laCreateOutSocket(n, "out", 0); n->Sockets[i].Parent=n; }
+    strSafeSet(&n->Base.Name,"Controller Output");
+}
+void IDN_ControllerDestroy(laInputControllerNode* n){
+    for(int i=0;i<8;i++){ laDestroyOutSocket(n->Sockets[i].Out); }
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_ControllerVisit(laInputControllerNode* n, laListHandle* l){
+    laController* c=la_FindControllerWithID(n->UserID);
+    if(!c){ for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; LA_IDN_CONTROLLER_RESET_SOCKET(ns); } return LA_DAG_FLAG_PERM; }
+    else{
+        for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i];
+            if(!ns->Which || !ns->Which->Ptr){ LA_IDN_CONTROLLER_RESET_SOCKET(ns); continue; }
+            laPropContainer*pc=la_EnsureSubTarget(LA_PROP_CONTROLLER, c);
+            laProp* p=la_PropLookup(&pc->Props, n->Sockets[i].Which->Ptr);
+            if((!p)||(!p->Offset)||
+                ((p->PropertyType!=LA_PROP_INT)&&(p->PropertyType!=LA_PROP_ENUM)&&
+                 (p->PropertyType!=(LA_PROP_INT|LA_PROP_ARRAY))&&(p->PropertyType!=(LA_PROP_ENUM|LA_PROP_ARRAY)))){ LA_IDN_CONTROLLER_RESET_SOCKET(ns); continue; }
+            if(p->PropertyType==LA_PROP_INT){ ns->Out->DataType=LA_PROP_FLOAT; ns->Out->Data=&ns->RealVal; ns->Out->ArrLen=1; }
+            elif(p->PropertyType==(LA_PROP_INT|LA_PROP_ARRAY)){ ns->Out->DataType=(LA_PROP_FLOAT|LA_PROP_ARRAY); ns->Out->Data=&ns->RealVal; ns->Out->ArrLen=p->Len; }
+            elif(p->PropertyType==LA_PROP_ENUM){ ns->Out->DataType=LA_PROP_ENUM; ns->Out->Data=&ns->IntVal; ns->Out->ArrLen=1; }
+            elif(p->PropertyType==(LA_PROP_ENUM|LA_PROP_ARRAY)){ ns->Out->DataType=(LA_PROP_ENUM|LA_PROP_ARRAY); ns->Out->Data=&ns->IntVal; ns->Out->ArrLen=p->Len; }
+            ns->Offset=p->Offset;
+        }
+    }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_ControllerEval(laInputControllerNode* n){
+    laNotifyInstanceUsers(n);
+    laController* c=la_FindControllerWithID(n->UserID); if(!c){ 
+        for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; LA_IDN_CONTROLLER_RESET_SOCKET(ns); } return 1;
+    }
+    for(int i=0;i<8;i++){ laInputControllerNodeSocket* ns=&n->Sockets[i]; 
+        int *addr=((char*)c)+ns->Offset; char* addc=addr;
+        if(ns->Out->DataType==LA_PROP_FLOAT){ ns->RealVal[0]=(real)(*addr)/32767.0; }
+        if(ns->Out->DataType==(LA_PROP_FLOAT|LA_PROP_ARRAY)){ for(int a=0;a<ns->Out->ArrLen;a++) ns->RealVal[a]=((real)addr[a])/32767.0; }
+        elif(ns->Out->DataType==LA_PROP_ENUM){ ns->IntVal[0]=(*addc); }
+        elif(ns->Out->DataType==(LA_PROP_ENUM|LA_PROP_ARRAY)){ for(int a=0;a<ns->Out->ArrLen;a++) ns->IntVal[a]=addc[a]; }
+    }
+    return 1;
+}
+void laui_ControllerNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laInputControllerNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
+    laShowItem(uil,c,This,"base.name")->Expand=1;
+    laShowItem(uil,c,This,"user_id");
+    laEndRow(uil,b);
+
+    char* buf[128],buf2[128];
+    for(int i=0;i<8;i++){
+        sprintf(buf,"out%d.which",i); laShowItem(uil,cl,This,buf);
+        laUiItem* b=laBeginRow(uil,cr,0,0);
+        sprintf(buf2,"out%d.out.data_type",i);
+        laUiItem* b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,buf2),laIntExpression(LA_PROP_FLOAT)));{
+            sprintf(buf,"out%d.axis",i); laShowItem(uil,cr,This,buf)->Expand=1;
+        }laElse(uil,b2);{
+            laUiItem* b3=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,buf2),laIntExpression(LA_PROP_FLOAT|LA_PROP_ARRAY)));{
+                sprintf(buf,"out%d.axis2d",i); laUiItem* aui=laShowItem(uil,cr,This,buf);aui->Expand=1;aui->Flags|=LA_UI_FLAGS_TRANSPOSE;aui->Extra->HeightCoeff=1;
+            }laElse(uil,b3);{
+                sprintf(buf,"out%d.switch",i); laUiItem* sui=laShowItem(uil,cr,This,buf);sui->Expand=1;sui->Flags|=LA_UI_FLAGS_TRANSPOSE;
+            }laEndCondition(uil,b3);
+        }laEndCondition(uil,b2);
+        sprintf(buf,"out%d.out",i); laShowNodeSocket(uil,cr,This,buf,0);
+        laEndRow(uil,b);
+    }
+}
+
+void IDN_InputVisualizeInit(laInputVisualizerNode* n){
+    n->In=laCreateInSocket("in", 0);
+    strSafeSet(&n->Base.Name,"Input Visualizer");
+}
+void IDN_InputVisualizeDestroy(laInputVisualizerNode* n){
+    laDestroyInSocket(n->In);
+    strSafeDestroy(&n->Base.Name);
+}
+int IDN_InputVisualizeVisit(laInputVisualizerNode* n, laListHandle* l){
+    n->Base.Eval=LA_DAG_FLAG_TEMP;
+    if(n->In->Source){ int result;
+        laInputMapperNode* sn=n->In->Source->Parent; result=sn->Type->Visit(sn,l); if(result==LA_DAG_FLAG_TEMP) return LA_DAG_FLAG_ERR;
+    }
+    n->Base.Eval=LA_DAG_FLAG_PERM;
+    lstAppendPointer(l, n);
+    return LA_DAG_FLAG_PERM;
+}
+int IDN_InputVisualizerEval(laInputVisualizerNode* n){
+    if(!n->In->Source) return 0;
+    laNodeOutSocket* os=n->In->Source; int arrlen=1;
+    switch(os->DataType){
+    case LA_PROP_FLOAT|LA_PROP_ARRAY:
+    case LA_PROP_FLOAT: if(os->ArrLen)arrlen=os->ArrLen; memcpy(n->RealVal,os->Data,sizeof(real)*arrlen); n->In->ArrLen=arrlen; break;
+    case LA_PROP_ENUM|LA_PROP_ARRAY:
+    case LA_PROP_ENUM: if(os->ArrLen)arrlen=os->ArrLen; memcpy(n->IntVal,os->Data,sizeof(int)*arrlen); n->In->ArrLen=arrlen; break;
+    default: n->IntVal[0]=0; n->In->ArrLen=1; break;
+    }
+    n->In->DataType=os->DataType;
+    laNotifyInstanceUsers(n);
+    return 1;
+}
+void laui_InputVisualizeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil); laInputControllerNode*n=This->EndInstance;
+    laColumn* cl,*cr; laSplitColumn(uil,c,0.4); cl=laLeftColumn(c,1); cr=laRightColumn(c,0);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    laShowHeightAdjuster(uil,c,This,"base.__gap",0);
+    laShowItem(uil,c,This,"base.name");
+    laEndRow(uil,b);
+
+    laShowNodeSocket(uil,cl,This,"in",0);
+    laUiItem* b2=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,"in.data_type"),laIntExpression(LA_PROP_FLOAT)));{
+        laShowItem(uil,cr,This,"axis");
+    }laElse(uil,b2);{
+        laUiItem* b3=laOnConditionThat(uil,cr,laEqual(laPropExpression(This,"in.data_type"),laIntExpression(LA_PROP_FLOAT|LA_PROP_ARRAY)));{
+            laUiItem* aui=laShowItem(uil,cr,This,"axis2d");
+        }laElse(uil,b3);{
+            laUiItem* sui=laShowItem(uil,cr,This,"switch");
+        }laEndCondition(uil,b3);
+    }laEndCondition(uil,b2);
+}
+
+int OPINV_AddInputMapperRack(laOperator* a, laEvent *e){
+    laInputRack* pivot=a->This?a->This->EndInstance:0;
+    laInputRack* ir=memAcquire(sizeof(laInputRack));
+
+    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"before"),"true")){
+        if(pivot){ lstInsertItemBefore(&MAIN.InputMappingRacks,ir,pivot); }else{ lstPushItem(&MAIN.InputMappingRacks,ir); }
+    }else { if(pivot){ lstInsertItemAfter(&MAIN.InputMappingRacks,ir,pivot); }else{ lstAppendItem(&MAIN.InputMappingRacks,ir); } }
+    laNotifyUsers("la.input_racks");
+    
+    return LA_FINISHED;
+}
+
+laInputMapperNode* la_CreateInputMapperNode(laInputRack* ir, laInputMapperNodeType* NodeType){
+    laInputMapperNode* imn=memAcquire(NodeType->NodeSize);
+    imn->Type=NodeType; NodeType->Init(imn); lstAppendItem(&ir->Nodes, imn); imn->InRack=ir;
+    laNotifyUsers("la.input_racks");
+    return imn;
+}
+void la_DestroyInputMapperNode(laInputMapperNode* imn){
+    lstRemoveItem(imn->InRack, imn); imn->Type->Destroy(imn);
+    laNotifyUsers("la.input_racks");
+    memFree(imn);
+}
+
+int OPINV_AddInputMapperNode(laOperator* a, laEvent *e){
+    laInputRack* ir=a->This?a->This->EndInstance:0; if(!ir) return LA_CANCELED;
+
+    char* type=strGetArgumentString(a->ExtraInstructionsP,"type");
+    if(!type){ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
+    elif(strSame(type, "KEYBOARD")){}
+    elif(strSame(type, "MOUSE")){}
+    elif(strSame(type, "CONTROLLER")){ la_CreateInputMapperNode(ir, &LA_IDN_CONTROLLER); }
+    elif(strSame(type, "VISUALIZER")){ la_CreateInputMapperNode(ir, &LA_IDN_VISUALIZER); }
+
+    return LA_FINISHED;
+}
+void laui_AddInputMapperNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil);
+    laShowItemFull(uil,c,This,"add_node",0,"type=KEYBOARD;text=Keyboard",0,0);
+    laShowItemFull(uil,c,This,"add_node",0,"type=MOUSE;text=Mouse",0,0);
+    laShowItemFull(uil,c,This,"add_node",0,"type=CONTROLLER;text=Controller",0,0);
+    laShowSeparator(uil,c);
+    laShowItemFull(uil,c,This,"add_node",0,"type=VISUALIZER;text=Visualizer",0,0);
+}
+
+laPropContainer* laget_InputNodeType(laInputMapperNode* imn){
+    if(imn->Type==&LA_IDN_CONTROLLER) return LA_PC_IDN_CONTROLLER;
+    if(imn->Type==&LA_IDN_VISUALIZER) return LA_PC_IDN_VISUALIZER;
+    return LA_PC_IDN_GENERIC;
+}
+int laget_InputNodeGap(laInputRack* rack_unused, laInputMapperNode* n){
+    return n->Gap;
+}
+void laset_InputNodeGap(laInputMapperNode* n, int gap){
+    if(gap<0){
+        int done=0;
+        laInputMapperNode* nn=n; while(nn){ if(nn->Gap>0){ nn->Gap--; done=1; break; } nn=nn->Item.pPrev; }
+        if(done){ nn=n->Item.pNext; while(nn){ if(nn->Gap>0){ nn->Gap++; break; } nn=nn->Item.pNext; } }
+    }
+    if(gap>0){
+        n->Gap+=gap;
+        laInputMapperNode* nn=n->Item.pNext; while(nn){ if(nn->Gap>0){ nn->Gap--; break; } nn=nn->Item.pNext; }
+    }
+}
+void laset_InputNodeUserID(laInputControllerNode* n, int i){
+    laNotifyUsers("la.input_racks");
+}
+void laset_InputControllerNodeSocketWhich(laInputControllerNodeSocket* s, char* str){
+    strSafeSet(&s->Which, str);
+    laNotifyUsers("la.input_racks"); laMappingRequestRebuild();
+}
+int laget_SocketEnumArrayLength(laInputControllerNodeSocket* s){
+    return s->Out->ArrLen?s->Out->ArrLen:1;
+}
+int laget_VisualizerArrayLength(laInputVisualizerNode* s){
+    return s->In->ArrLen?s->In->ArrLen:1;
+}
+
+#define LA_IDN_REGISTER(a,init,destroy,visit,eval,type)\
+    {a.Init = init; a.Destroy = destroy; a.Visit=visit; a.Eval=eval; a.NodeSize=sizeof(type);}
+
+void la_RegisterInputMapperOperators(){
+    laPropContainer *pc; laProp *p;
+    laOperatorType *at;
+    laEnumProp *ep;
+
+    LA_IDN_REGISTER(LA_IDN_CONTROLLER, IDN_ControllerInit, IDN_ControllerDestroy, IDN_ControllerVisit, IDN_ControllerEval, laInputControllerNode);
+    LA_IDN_REGISTER(LA_IDN_VISUALIZER, IDN_InputVisualizeInit, IDN_InputVisualizeDestroy, IDN_InputVisualizeVisit, IDN_InputVisualizerEval, laInputVisualizerNode);
+
+    laCreateOperatorType("LA_add_input_mapper_rack", "Add Rack", "Add a rack for input mapper nodes", 0,0,0,OPINV_AddInputMapperRack,0,'+',0);
+    at=laCreateOperatorType("LA_add_input_mapper_node", "Add Node", "Add a input mapper node",0,0,0,OPINV_AddInputMapperNode,OPMOD_FinishOnData,'+',0);
+    at->UiDefine=laui_AddInputMapperNode;
+
+    pc=laAddPropertyContainer("la_input_rack", "Input Rack", "Input rack for putting input mapping nodes",0,0,sizeof(laInputRack),0,0,1);
+    laAddStringProperty(pc,"name","Name","Name of this rack",0,0,0,0,1,offsetof(laInputRack,Name),0,0,0,0,LA_AS_IDENTIFIER);
+    p=laAddSubGroup(pc,"nodes","Nodes","Nodes under this rack","la_input_node",laget_InputNodeType,0,0,-1,0,0,0,0,0,0,offsetof(laInputRack,Nodes),0);
+    laSubGroupExtraFunctions(p,0,0,laget_InputNodeGap);
+    laAddOperatorProperty(pc,"add_node","Add Node","Add a node into this rack","LA_add_input_mapper_node",'+',0);
+
+    pc=laAddPropertyContainer("la_input_node", "Input Node", "Input logic node",0,0,sizeof(laInputMapperNode),0,0,1);
+    LA_PC_IDN_GENERIC=pc;
+    laAddStringProperty(pc,"name","Name","Name of this input node",0,0,0,0,1,offsetof(laInputMapperNode,Name),0,0,0,0,LA_AS_IDENTIFIER);
+    laAddIntProperty(pc,"__gap", "Gap", "Gap of the node", 0,0,0,0,0,0,0,0,offsetof(laInputMapperNode,Gap),0,laset_InputNodeGap,0,0,0,0,0,0,0,0,0);
+
+    pc=laAddPropertyContainer("la_input_controller_node", "Controller output", "Output controller values",0,laui_ControllerNode,sizeof(laInputControllerNode),0,0,1);
+    LA_PC_IDN_CONTROLLER=pc;
+    laAddSubGroup(pc,"base","Base","Base node","la_input_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddIntProperty(pc,"user_id", "User ID", "Which controller should the data come from", 0,0,0,0,0,0,0,0,offsetof(laInputControllerNode,UserID),0,0,0,0,0,0,0,0,0,0,0);
+    laAddSubGroup(pc,"out0","Out 0","Output 0","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[0]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out1","Out 1","Output 1","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[1]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out2","Out 2","Output 2","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[2]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out3","Out 3","Output 3","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[3]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out4","Out 4","Output 4","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[4]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out5","Out 5","Output 5","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[5]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out6","Out 6","Output 6","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[6]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc,"out7","Out 7","Output 7","la_input_controller_node_socket",0,0,0,offsetof(laInputControllerNode, Sockets[7]),0,0,0,0,0,0,0,LA_UDF_LOCAL);
+
+    pc=laAddPropertyContainer("la_input_controller_node_socket", "Controller Socket", "One value from a controller output",0,0,sizeof(laInputControllerNodeSocket),0,0,1|LA_PROP_OTHER_ALLOC);
+    laAddStringProperty(pc,"which","Which","Select which output from the controller",0,0,0,0,1,offsetof(laInputControllerNodeSocket,Which),0,0,laset_InputControllerNodeSocketWhich,0,LA_AS_IDENTIFIER);
+    laAddFloatProperty(pc,"axis", "🡘", "Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputControllerNodeSocket,RealVal),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+    laAddFloatProperty(pc,"axis2d", "2D Axis", "2D Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputControllerNodeSocket,RealVal),0,0,2,0,0,0,0,0,0,0,LA_READ_ONLY);
+    p=laAddEnumProperty(pc,"switch", "SW", "Switch value", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laInputControllerNodeSocket,IntVal),0,0,0,laget_SocketEnumArrayLength,0,0,0,0,0,LA_READ_ONLY);
+    laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
+    laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
+    laAddSubGroup(pc, "out", "Out","Output value","la_out_socket",0,0,0,offsetof(laInputControllerNodeSocket,Out),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+
+    pc=laAddPropertyContainer("la_input_visualizer_node", "Visualizer", "Visualizer node",0,laui_InputVisualizeNode,sizeof(laInputVisualizerNode),0,0,1);
+    LA_PC_IDN_VISUALIZER=pc;
+    laAddSubGroup(pc,"base","Base","Base node","la_input_node",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
+    laAddSubGroup(pc, "in", "In","Input value","la_in_socket",0,0,0,offsetof(laInputVisualizerNode,In),0,0,0,0,0,0,0,LA_UDF_SINGLE);
+    laAddFloatProperty(pc,"axis", "🡘", "Axis value", LA_WIDGET_VALUE_METER,0,0,1,-1,0,0,0,offsetof(laInputVisualizerNode,RealVal),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+    laAddFloatProperty(pc,"axis2d", "2D Axis", "2D Axis value", LA_WIDGET_VALUE_METER_2D,0,0,1,-1,0,0,0,offsetof(laInputVisualizerNode,RealVal),0,0,2,0,0,0,0,0,0,0,LA_READ_ONLY);
+    p=laAddEnumProperty(pc,"switch", "SW", "Switch value", LA_WIDGET_ENUM_HIGHLIGHT,0,0,0,0,offsetof(laInputVisualizerNode,IntVal),0,0,0,laget_VisualizerArrayLength,0,0,0,0,0,LA_READ_ONLY);
+    laAddEnumItemAs(p,"IDLE", "Idle", "Button is not pressed", 0, 0);
+    laAddEnumItemAs(p,"ACTIVE", "Active", "Button is pressed", 1, 0);
+}
+
+
+
+void laMappingRequestRebuild(){ MAIN.MappingNeedRebuild=1; }
+void laMappingRequestEval(){ MAIN.MappingNeedEval=1; }
+
+int la_RunInputMapping(){
+    MAIN.MappingNeedEval = 0;
+    for(laListItemPointer*lip=MAIN.InputMappingEval.pFirst;lip;lip=lip->pNext){
+        laInputMapperNode* n=lip->p; n->Type->Eval(n);
+    }
+    return 1;
+}
+int la_RebuildInputMapping(){
+    MAIN.MappingNeedRebuild = 0;
+    while(lstPopPointer(&MAIN.InputMappingEval));
+    laListHandle pending={0};
+    for(laInputRack* ir=MAIN.InputMappingRacks.pFirst;ir;ir=ir->Item.pNext){
+        for(laInputMapperNode*imn=ir->Nodes.pFirst;imn;imn=imn->Item.pNext){ lstAppendPointer(&pending,imn); imn->Eval=0; }
+    }
+    laInputMapperNode*n;int result=LA_DAG_FLAG_PERM; laListItemPointer*NextLip;
+    for(laListItemPointer*lip=pending.pFirst;lip;lip=NextLip){ n=lip->p; NextLip=lip->pNext;
+        if(n->Eval&LA_DAG_FLAG_PERM) continue;
+        result=n->Type->Visit(n,&MAIN.InputMappingEval); if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&pending)); break; }
+    }
+    if(result==LA_DAG_FLAG_ERR){ while(lstPopPointer(&MAIN.InputMappingEval)); return LA_DAG_FLAG_ERR; }
+    return LA_DAG_FLAG_PERM;
+}

+ 5 - 0
source/lagui/resources/la_properties.c

@@ -738,6 +738,8 @@ laPropContainer* TNS_PC_OBJECT_CAMERA;
 laPropContainer* TNS_PC_OBJECT_LIGHT;
 laPropContainer* TNS_PC_OBJECT_MESH;
 
+laProp* LA_PROP_CONTROLLER;
+
 void la_RegisterGeneralProps(){
     laPropContainer *p, *ip, *p2, *ip2, *sp1, *sp2;
     laProp *ep;
@@ -882,6 +884,9 @@ void la_RegisterInternalProps(){
 
             sp=laAddSubGroup(p, "controllers", "Controllers", "Detected game controllers","la_controller",laget_ControllerType,0,0,-1,0,0,0,0,0,0,offsetof(LA,Controllers),0);
             laSubGroupDetachable(sp, laget_DetachedControllerFirst, laget_ListNext);
+            LA_PROP_CONTROLLER=sp;
+            
+            laAddSubGroup(p, "input_racks", "Input Racks", "Input Mapping Racks","la_input_rack",0,0,0,-1,0,0,0,0,0,0,offsetof(LA,InputMappingRacks),0);
             
             laAddStringProperty(p, "identifier", "Identifier", "Identifier", 0, 0, 0, 0, 0, 0, 0, laget_MainIdentifier, 0, 0, LA_AS_IDENTIFIER|LA_READ_ONLY);
             laAddSubGroup(p, "test_ucn", "TEST UCN", "---", "udf_content_node",0, 0, 0, offsetof(LA, TEST_Link), 0, 0, 0, 0, 0, 0, 0, 0);

+ 28 - 2
source/lagui/resources/la_templates.c

@@ -1397,13 +1397,38 @@ void lauidetached_TextureInspector(laPanel* p){
 void laui_GameController(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
     laColumn* c=laFirstColumn(uil),*cl, *cr;
     laSplitColumn(uil,c,0.5); cl=laLeftColumn(c,0); cr=laRightColumn(c,0);
-    laShowLabel(uil,cl,"Controller:",0,0)->Flags|=LA_TEXT_ALIGN_RIGHT;
+    
+    laUiItem* b=laBeginRow(uil,cl,0,0);
+    laUiItem* l=laShowLabel(uil,cl,"Controller:",0,0); l->Flags|=LA_TEXT_ALIGN_RIGHT; l->Expand=1;
+    laShowItem(uil,cl,0,"LA_refresh_controllers")->Flags|=LA_UI_FLAGS_ICON;
+    laEndRow(uil,b);
+
     laShowItemFull(uil,cr,Extra,"controllers",LA_WIDGET_COLLECTION_SELECTOR,0,laui_IdentifierOnly,0);
-    laShowItemFull(uil,c,Extra,"controllers",LA_WIDGET_COLLECTION_SINGLE,0,0,0);
+
+    b=laOnConditionThat(uil,c,laPropExpression(Extra,"controllers"));{
+        laShowItemFull(uil,c,Extra,"controllers",LA_WIDGET_COLLECTION_SINGLE,0,0,0);
+    }laElse(uil,b);{
+        laShowLabel(uil,c,"Please select a controller.",0,0)->Flags|=LA_TEXT_ALIGN_CENTER;
+    }
 }
 void lauidetached_GameController(laPanel* p){
     la_MakeDetachedProp(p, "la.controllers", "controllers");
 }
+void laui_InputMapperRack(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil),*cl, *cr;
+    laShowItem(uil,c,This,"name")->Flags|=LA_UI_FLAGS_NO_DECAL|LA_TEXT_ALIGN_CENTER;
+    laShowItem(uil,c,This,"nodes")->Flags|=LA_UI_FLAGS_NO_DECAL;
+    laShowItem(uil,c,This,"add_node");
+}
+void laui_InputMapper(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+    laColumn* c=laFirstColumn(uil),*cl, *cr;
+
+    laUiItem* g=laMakeGroup(uil,c,"Racks",0);{ g->Flags|=/*LA_UI_FLAGS_NO_GAP|LA_UI_FLAGS_NO_DECAL|*/LA_UI_FLAGS_NODE_CONTAINER;
+        laUiList* gu=g->Page; laColumn* gc=laFirstColumn(gu); gu->AllowScale=1; gu->HeightCoeff=-3; g->State=LA_UI_ACTIVE;
+        laUiItem* hui=laShowItemFull(gu,gc,0,"la.input_racks",0,0,laui_InputMapperRack,0); hui->Expand=15; hui->Flags|=LA_UI_FLAGS_NO_DECAL;
+    }
+    laShowItem(uil,c,0,"LA_add_input_mapper_rack");
+}
 
 
 void la_RegisterBuiltinTemplates(){
@@ -1415,4 +1440,5 @@ void la_RegisterBuiltinTemplates(){
     laRegisterUiTemplate("LAUI_data_manager", "Data Manager", laui_IdleDataManager, lauidetached_IdleDataManager, 0);
     laRegisterUiTemplate("LAUI_terminal", "Terminal", laui_terminal, 0, 0);
     laRegisterUiTemplate("LAUI_controllers", "Controllers", laui_GameController, lauidetached_GameController, 0);
+    laRegisterUiTemplate("LAUI_input_mapper","Input Mapper",laui_InputMapper,0,0);
 }

+ 36 - 25
source/lagui/resources/la_widgets.c

@@ -17,8 +17,8 @@ laWidget _LA_WIDGET_LABEL={0};
 laWidget _LA_WIDGET_INT={0};
 laWidget _LA_WIDGET_INT_PLAIN={0, LA_UI_FLAGS_PLAIN};
 laWidget _LA_WIDGET_INT_PLAIN_ICON={0, LA_UI_FLAGS_INT_ICON};
-laWidget _LA_WIDGET_INT_METER={0,LA_TEXT_ALIGN_CENTER};
-laWidget _LA_WIDGET_INT_METER_2D={0,LA_TEXT_ALIGN_CENTER};
+laWidget _LA_WIDGET_VALUE_METER={0,LA_TEXT_ALIGN_CENTER};
+laWidget _LA_WIDGET_VALUE_METER_2D={0,LA_TEXT_ALIGN_CENTER};
 laWidget _LA_WIDGET_FLOAT={0};
 laWidget _LA_WIDGET_FLOAT_PLAIN={0, LA_UI_FLAGS_PLAIN};
 laWidget _LA_WIDGET_FLOAT_COLOR={0};
@@ -57,8 +57,8 @@ laWidget *LA_WIDGET_LABEL=&_LA_WIDGET_LABEL;
 laWidget *LA_WIDGET_INT=&_LA_WIDGET_INT;
 laWidget *LA_WIDGET_INT_PLAIN=&_LA_WIDGET_INT_PLAIN;
 laWidget *LA_WIDGET_INT_PLAIN_ICON=&_LA_WIDGET_INT_PLAIN_ICON;
-laWidget *LA_WIDGET_INT_METER=&_LA_WIDGET_INT_METER;
-laWidget *LA_WIDGET_INT_METER_2D=&_LA_WIDGET_INT_METER_2D;
+laWidget *LA_WIDGET_VALUE_METER=&_LA_WIDGET_VALUE_METER;
+laWidget *LA_WIDGET_VALUE_METER_2D=&_LA_WIDGET_VALUE_METER_2D;
 laWidget *LA_WIDGET_FLOAT=&_LA_WIDGET_FLOAT;
 laWidget *LA_WIDGET_FLOAT_PLAIN=&_LA_WIDGET_FLOAT_PLAIN;
 laWidget *LA_WIDGET_FLOAT_COLOR=&_LA_WIDGET_FLOAT_COLOR;
@@ -91,11 +91,11 @@ int la_ValueGetHeight(laUiItem*ui){
     if(ui->Flags&LA_UI_FLAGS_TRANSPOSE){ return la_ArrayGetHeight(ui); }
     return 1;
 }
-int la_IntMeterGetHeight(laUiItem*ui){
+int la_ValueMeterGetHeight(laUiItem*ui){
     if(ui->Flags&LA_UI_FLAGS_TRANSPOSE){ return ui->Extra->HeightCoeff?ui->Extra->HeightCoeff:6; }
     return laGetArrayLength(&ui->PP);
 }
-int la_IntMeter2DGetHeight(laUiItem*ui){
+int la_ValueMeter2DGetHeight(laUiItem*ui){
     return ui->Extra->HeightCoeff?ui->Extra->HeightCoeff:(ui->Extra->HeightCoeff=(ui->TR-ui->TL)/LA_RH);
 }
 int la_CanvasGetHeight(laUiItem *ui){
@@ -716,7 +716,7 @@ void la_EnumSelectorDraw(laUiItem *ui, int h){
     }
     for(int i=0;i<ArrLen;i++){
         _L=ui->L; _R=ui->R; _U=ui->U; _B=ui->B; ei = ep->Items.pFirst;
-        for(int j=0;j<EnumLen;j++){
+        for(int j=0;j<EnumLen;j++){ buf[0]=0;
             if(IsVertical){
                 if(IsExpand){ _U=ui->U+j*LA_RH; _L=ui->L+i*_W; }
                 else{ _U=ui->U+i*LA_RH;  _L=ui->L; }
@@ -1243,23 +1243,28 @@ void la_RawPropDraw(laUiItem *ui, int h){
     tnsDrawStringAuto("RAW DATA", laThemeColor(bt,LA_BT_TEXT), ui->L, ui->R, ui->U, ui->Flags|LA_TEXT_MONO);
 }
 
-void la_IntMeterDraw(laUiItem *ui, int h){
+void la_ValueMeterDraw(laUiItem *ui, int h){
     laBoxedTheme *bt = (*ui->Type->Theme);
-    int Data[32];
+    int DataI[8]; real Data[8];
     int Len, i, W; real Seg;
     char buf[48] = {0};
     char buf2[48] = {0};
     char prefix[8][64] = {0};
     int Original;
-    int min=-100, max=100;
-    int s, State;
-    int IsVertical=ui->Flags&LA_UI_FLAGS_TRANSPOSE;
+    int Imin=-100, Imax=100;
+    real min=-100, max=100;
+    int s, State; int IsVertical=ui->Flags&LA_UI_FLAGS_TRANSPOSE;
 
-    laGetIntArray(&ui->PP, Data);
+    int IsInt=(ui->PP.LastPs->p->PropertyType&LA_PROP_INT)?1:0;
     Len = laGetArrayLength(&ui->PP);
-
     laGetPrefixP(&ui->PP, prefix);
-    laGetIntRange(&ui->PP, &min, &max);
+    if(IsInt){
+        laGetIntArray(&ui->PP, DataI); for(int i=0;i<Len;i++){ Data[i]=DataI[i]; }
+        laGetIntRange(&ui->PP, &Imin, &Imax); min=Imin; max=Imax;
+    }else{
+        laGetFloatArray(&ui->PP, Data);
+        laGetFloatRange(&ui->PP,&min,&max);
+    }
 
     for (i = 0; i < Len; i++){
         int _L=ui->L, _R=ui->R, _U=ui->U, _B=ui->B;
@@ -1298,21 +1303,27 @@ void la_IntMeterDraw(laUiItem *ui, int h){
 
     tnsFlush();
 }
-void la_IntMeter2DDraw(laUiItem *ui, int h){
+void la_ValueMeter2DDraw(laUiItem *ui, int h){
     laBoxedTheme *bt = (*ui->Type->Theme);
-    int Data[32];
+    int DataI[8]; real Data[8];
     int Len, i, W; real Seg;
     char buf[48] = {0};
     char buf2[48] = {0};
     char prefix[8][64] = {0};
     int Original;
-    int min=-100, max=100;
+    int Imin=-100, Imax=100;
+    real min=-100, max=100;
 
-    laGetIntArray(&ui->PP, Data);
+    int IsInt=(ui->PP.LastPs->p->PropertyType&LA_PROP_INT)?1:0;
     Len = laGetArrayLength(&ui->PP);
-
     laGetPrefixP(&ui->PP, prefix);
-    laGetIntRange(&ui->PP, &min, &max);
+    if(IsInt){
+        laGetIntArray(&ui->PP, DataI); for(int i=0;i<Len;i++){ Data[i]=DataI[i]; }
+        laGetIntRange(&ui->PP, &Imin, &Imax); min=Imin; max=Imax;
+    }else{
+        laGetFloatArray(&ui->PP, Data);
+        laGetFloatRange(&ui->PP,&min,&max);
+    }
 
     int _L=ui->L, _R=ui->R, _U=ui->U, _B=ui->B;
 
@@ -1391,11 +1402,11 @@ void la_RegisterUiTypesBasic(){
                         &_LA_THEME_VALUATOR, la_IntDraw, la_ValueGetHeight, la_GeneralUiInit, la_GeneralUiDestroy);
     _LA_UI_INT->GetMinWidth=la_ValueGetMinWidth;
 
-    LA_WIDGET_INT_METER->Type=
-    _LA_UI_INT_METER = la_RegisterUiType("LA_int_meter", LA_PROP_INT | LA_PROP_ARRAY, 0, &_LA_THEME_VALUATOR, la_IntMeterDraw, la_IntMeterGetHeight, la_GeneralUiInit, la_GeneralUiDestroy);
+    LA_WIDGET_VALUE_METER->Type=
+    _LA_UI_VALUE_METER = la_RegisterUiType("LA_value_meter", LA_PROP_INT | LA_PROP_ARRAY, 0, &_LA_THEME_VALUATOR, la_ValueMeterDraw, la_ValueMeterGetHeight, la_GeneralUiInit, la_GeneralUiDestroy);
 
-    LA_WIDGET_INT_METER_2D->Type=
-    _LA_UI_INT_METER_2D = la_RegisterUiType("LA_int_meter_2d", LA_PROP_INT | LA_PROP_ARRAY, 0, &_LA_THEME_VALUATOR, la_IntMeter2DDraw, la_IntMeter2DGetHeight, la_GeneralUiInit, la_GeneralUiDestroy);
+    LA_WIDGET_VALUE_METER_2D->Type=
+    _LA_UI_VALUE_METER_2D = la_RegisterUiType("LA_value_meter_2d", LA_PROP_INT | LA_PROP_ARRAY, 0, &_LA_THEME_VALUATOR, la_ValueMeter2DDraw, la_ValueMeter2DGetHeight, la_GeneralUiInit, la_GeneralUiDestroy);
 
     LA_WIDGET_FLOAT->Type=LA_WIDGET_FLOAT_PLAIN->Type=
     _LA_UI_FLOAT = la_RegisterUiType("LA_real_array_horizon", LA_PROP_FLOAT | LA_PROP_ARRAY, "LA_real_array_h_operator",