*/}}

3 Commity d83300778b ... c30ee6e413

Autor SHA1 Wiadomość Data
  YimingWu c30ee6e413 evdev joystick and x56 thr mapping 2 lat temu
  YimingWu 0f45a0ef85 Knob viewer 2 lat temu
  YimingWu 6771a08c33 Fix EndInstance on redraw 2 lat temu
8 zmienionych plików z 344 dodań i 117 usunięć
  1. 2 2
      la_audio.c
  2. 72 44
      la_controllers.c
  3. 16 0
      la_interface.h
  4. 26 1
      la_kernel.c
  5. 64 0
      la_util.c
  6. 25 0
      la_util.h
  7. 9 4
      resources/la_nodes_basic.c
  8. 130 66
      resources/la_widgets.c

+ 2 - 2
la_audio.c

@@ -396,14 +396,14 @@ void laui_EnvelopeNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laCol
     b=laBeginRow(uil,c,0,0); \
     laShowNodeSocket(uil,c,This,"in_" what,0)->Flags|=LA_UI_SOCKET_LABEL_E; \
     b1=laOnConditionThat(uil,c,laNot(laPropExpression(This,"in_" what ".source")));{ \
-    laShowItem(uil,c,This,what)->Expand=1; \
+        laUiItem* value=laShowItem(uil,c,This,what);value->Expand=1; value->Flags|=LA_UI_FLAGS_KNOB;\
     } laEndCondition(uil,b1); \
     laEndRow(uil,b);
 
     ADSR_ROW("attack") ADSR_ROW("delay") ADSR_ROW("sustain") ADSR_ROW("release")
     
     b=laBeginRow(uil,c,0,0);
-    laShowItem(uil,c,This,"in_trigger"); laShowItem(uil,c,This,"trigger")->Flags|=LA_UI_FLAGS_CYCLE;
+    laShowItem(uil,c,This,"in_trigger"); laShowItem(uil,c,This,"trigger")->Flags|=LA_UI_FLAGS_MOMENTARY|LA_UI_FLAGS_HIGHLIGHT;
     laShowSeparator(uil,c)->Expand=1; laShowItem(uil,c,This,"out")->Flags|=LA_UI_SOCKET_LABEL_W;
     laEndRow(uil,b);
 }

+ 72 - 44
la_controllers.c

@@ -22,22 +22,20 @@
 
 #ifdef __linux__
 #include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <sys/ioctl.h>
+#include <sys/inotify.h>
 #include <linux/input.h>
 #include <linux/joystick.h>
+#include <fcntl.h>
+#include <unistd.h>
 #include <errno.h>
 #include <X11/extensions/XInput2.h>
 #endif
 
 extern LA MAIN;
 
-STRUCTURE(laJoystickEvent){
-  unsigned int time;
-  short value;
-  unsigned char type;
-  unsigned char number;
-};
-
 #define LA_JS_EVENT_BUTTON 0x01 // button pressed/released
 #define LA_JS_EVENT_AXIS   0x02 // joystick moved
 #define LA_JS_EVENT_INIT   0x80 // initial state of device
@@ -73,42 +71,72 @@ 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_ReadControllerAxisLimit(laController* c, int i, int Min, int Max){
+    c->AxisMins[i] = Min;
+    c->AxisMaxes[i] = Max;
+}
 void la_InitControllers(){
 #ifdef __linux__
     char path[32]="/dev/input/js";
     char name[128]={0};
     int numpos=strlen(path);
-    for(int i=0;i<16;i++){
-        int fd;
-        int version; uint8_t axes, buttons;
-        int btnmapok = 1;
-        sprintf(&path[numpos],"%d",i);
-        if ((fd=open(path, O_RDONLY|O_NONBLOCK))<0) { continue; }
-
-        ioctl(fd, JSIOCGVERSION, &version);
-        ioctl(fd, JSIOCGAXES, &axes);
-        ioctl(fd, JSIOCGBUTTONS, &buttons);
-        ioctl(fd, JSIOCGNAME(128), name);
-
-        laController* c=la_NewController(name, path, fd, axes, buttons);
-    }
+
+    char fileName[32];
+	for (int i=0; i<32; ++i) {
+		sprintf(fileName, "/dev/input/event%d", i);
+		int file = open(fileName, O_RDWR | O_NONBLOCK);
+		if (file != -1){
+			// Get name
+			ioctl(file, EVIOCGNAME(128), name);
+            printf("%s ",name);
+
+            barray_t *abs_barray = barray_init(ABS_CNT);
+            ioctl(file, EVIOCGBIT(EV_ABS, ABS_CNT), barray_data(abs_barray));
+            size_t abs_count = barray_count_set(abs_barray);
+
+            barray_t *key_barray = barray_init(KEY_CNT);
+            ioctl(file, EVIOCGBIT(EV_KEY, KEY_CNT), barray_data(key_barray));
+            size_t key_count = barray_count_set(key_barray);
+
+            if(!abs_count && !key_count){ close(file); continue; }
+
+            laController* c=la_NewController(name, fileName, file, abs_count, key_count);
+
+			for (unsigned int j=0; j<abs_count; j++){ struct input_absinfo axisInfo;
+				if (ioctl(file, EVIOCGABS(j), &axisInfo) != -1){
+                    la_ReadControllerAxisLimit(c,j,axisInfo.minimum,axisInfo.maximum);
+				}
+			}
+
+            barray_free(abs_barray);
+            barray_free(key_barray);
+		}
+	}
 #endif
 }
 
 void la_UpdateControllerStatus(){
 #ifdef __linux__
-    laJoystickEvent event; int HasEvent=0;
+    struct input_event event; int HasEvent=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;
-                //printf("b %d %d\n", event.number, event.value);
+        int bytes; while((bytes=read(c->fd, &event, sizeof(struct input_event)))>0){
+            if(event.type == EV_KEY){
+                printf("b %d %d", event.code, event.value);
+                if(event.code>=BTN_JOYSTICK && event.code<=BTN_THUMBR){
+                    event.code -= BTN_JOYSTICK;
+                }elif(event.code>=BTN_TRIGGER_HAPPY && event.code<=BTN_TRIGGER_HAPPY40){
+                    event.code=event.code-BTN_TRIGGER_HAPPY+BTN_THUMBR-BTN_JOYSTICK+1;
+                }
+                printf(" %d \n", event.code, event.value);
+                //if(event.code>=c->NumButtons+BTN_THUMBR) continue;
+                c->ButtonValues[event.code]=event.value; HasEvent=1;
             }
-            if(event.type&LA_JS_EVENT_AXIS){
-                if(event.number>=c->NumAxes) continue;
-                c->AxisValues[event.number]=event.value; HasEvent=1;
-                //printf("a %d %d\n", event.number, event.value);
+            else if(event.type == EV_ABS){
+                if(event.code>=c->NumAxes+ABS_X) continue;
+                int axis=event.code-ABS_X;
+                c->AxisValues[axis]=rint(tnsLinearItp(-32768.0f,32767.0f,((real)event.value/(c->AxisMaxes[axis]-c->AxisMins[axis]))));
+                HasEvent=1;
+                //printf("a %d %d\n", event.code, event.value);
             }
         }
         if(bytes<=0){ struct stat buffer; if(stat(c->Path->Ptr,&buffer)<0){ c->Error=1; HasEvent=1; } }
@@ -143,7 +171,7 @@ void la_AddButtonProp(laPropContainer* pc, char* id, char* name, char* desc, int
 }
 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_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);
+        array_prefix,0,32767,-32768,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){
     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); }
@@ -211,18 +239,18 @@ void la_RegisterControllerProps(){
     la_AddButtonProp(pc,"t2_up","T2+","Toggle 2+", 13,0,0);
     la_AddButtonProp(pc,"t2_dn","T2-","Toggle 2-", 14,0,0);
     la_AddButtonProp(pc,"t3_up","T3+","Toggle 3+", 15,0,0);
-    la_AddButtonProp(pc,"t3_dn","T3-","Toggle 3-", 16,0,0);
-    la_AddButtonProp(pc,"t4_up","T4+","Toggle 4+", 17,0,0);
-    la_AddButtonProp(pc,"t4_dn","T4-","Toggle 4-", 18,0,0);
-    la_AddButtonProp(pc,"h3","H3","Hat 3 (Upper round hat)", 19,4,"N,E,S,W");
-    la_AddButtonProp(pc,"h4","H4","Hat 4 (lower jagged hat)", 23,4,"N,E,S,W");
-    la_AddButtonProp(pc,"pinky_up","P+","Pinky up", 27,0,0);
-    la_AddButtonProp(pc,"pinky_dn","P-","Pinky down", 28,0,0);
-    la_AddButtonProp(pc,"dial_fwd","D+","Dial forward", 29,0,0);
-    la_AddButtonProp(pc,"dial_back","D-","Dial backward", 30,0,0);
-    la_AddButtonProp(pc,"bball","BP","Ball push", 31,0,0);
-    la_AddButtonProp(pc,"slider","SLD","Slider", 32,0,0);
-    la_AddButtonProp(pc,"mode","Mode","Mode switch", 33,3,"M1,M2,S1");
+    la_AddButtonProp(pc,"t3_dn","T3-","Toggle 3-", 31,0,0);
+    la_AddButtonProp(pc,"t4_up","T4+","Toggle 4+", 32,0,0);
+    la_AddButtonProp(pc,"t4_dn","T4-","Toggle 4-", 33,0,0);
+    la_AddButtonProp(pc,"h3","H3","Hat 3 (Upper round hat)", 34,4,"N,E,S,W");
+    la_AddButtonProp(pc,"h4","H4","Hat 4 (lower jagged hat)", 38,4,"N,E,S,W");
+    la_AddButtonProp(pc,"pinky_up","P+","Pinky up", 42,0,0);
+    la_AddButtonProp(pc,"pinky_dn","P-","Pinky down", 43,0,0);
+    la_AddButtonProp(pc,"dial_fwd","D+","Dial forward", 44,0,0);
+    la_AddButtonProp(pc,"dial_back","D-","Dial backward", 45,0,0);
+    la_AddButtonProp(pc,"bball","BP","Ball push", 46,0,0);
+    la_AddButtonProp(pc,"slider","SLD","Slider", 47,0,0);
+    la_AddButtonProp(pc,"mode","Mode","Mode switch", 48,3,"M1,M2,S1");
 
 
     pc=laAddPropertyContainer("la_controller_x56_stick", "X56 Stick", "X56 Stick", 0,laui_X56Stick,sizeof(laController),0,0,1);

+ 16 - 0
la_interface.h

@@ -1029,6 +1029,12 @@ STRUCTURE(laUiTemplate){
     laKeyMapper KeyMap;
 };
 
+STRUCTURE(laCompoundPP){
+    laListItem Item;
+    int Slot;
+    laPropPack PP;
+};
+
 STRUCTURE(laUiItem){
     laListItem Item;
 
@@ -1045,6 +1051,7 @@ STRUCTURE(laUiItem){
     short Expand;
     short SymbolID;
     laPropPack PP;
+    laListHandle CompoundPPs;
     laSafeString *Display;
     laSafeString *ExtraInstructions;
 
@@ -1113,6 +1120,9 @@ STRUCTURE(laWidget){
 
 #define LA_UI_FLAGS_TERMINAL_INPUT LA_UI_FLAGS_COLOR_SPACE_CLAY
 
+#define LA_UI_FLAGS_MOMENTARY (LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_NODE_CONTAINER)
+#define LA_UI_FLAGS_KNOB LA_UI_FLAGS_NODE_CONTAINER
+
 #define LA_UI_SOCKET_LABEL_N LA_TEXT_ALIGN_LEFT
 #define LA_UI_SOCKET_LABEL_S LA_TEXT_ALIGN_RIGHT
 #define LA_UI_SOCKET_LABEL_W LA_TEXT_ALIGN_CENTER
@@ -1407,6 +1417,12 @@ STRUCTURE(laController){
     int NumButtons; int NumAxes; int UserAssignedID; int Error;
     int InternalType; // used to identify models and use specific props.
     int AxisValues[LA_JS_MAX_AXES];
+    int AxisMaxes[LA_JS_MAX_AXES]; // original max scaled to 32767;
+    int AxisMins[LA_JS_MAX_AXES];
+    int AxisLimitMaxes[LA_JS_MAX_AXES]; // calibrated max;
+    int AxisLimitMins[LA_JS_MAX_AXES];
+    int AxisCenterMaxes[LA_JS_MAX_AXES]; // calibrated center;
+    int AxisCenterMins[LA_JS_MAX_AXES];
     char ButtonValues[LA_JS_MAX_BUTTONS];
 };
 

+ 26 - 1
la_kernel.c

@@ -4226,6 +4226,20 @@ laUiItem *laShowSymbol(laUiList *uil, laColumn *c, int SymbolID, int Height){
 
     return ui;
 }
+laPropPack* laShowCompoundValue(laUiItem* ui, int slot, laPropPack *Base, const char *Path){
+    laPropPack PP={0}; int result;
+    if (Path){
+        result = la_GetPropFromPath(&PP, Base, Path, 0);
+        if (!result){ return 0; }
+        if (Base){ PP.RawThis = Base; }
+    }else if (Base){
+        PP.LastPs = Base->LastPs; PP.RawThis = Base; //HACK! Not Unified For Prop Access!!!<<<----------??????????
+    }
+    laCompoundPP* CPP = memAcquire(sizeof(laCompoundPP));
+    CPP->Slot = slot; memcpy(&CPP->PP, &PP, sizeof(laPropPack));
+    lstAppendItem(&ui->CompoundPPs,CPP);
+    return &CPP->PP;
+}
 
 laUiItem *laBeginRow(laUiList *uil, laColumn *c, int Expand, int Even){
     laUiItem *ui = memAcquireSimple(sizeof(laUiItem));
@@ -4881,6 +4895,10 @@ void la_DestroyUiItem(laUiItem *ui, int RemoveUsers){
     //    { /*la_StopUsingPropPack(&ui->PP);*/ }
         //laStopUsingDataBlock(ui->PP.LastPs->UseInstance,ui->PP.LastPs->p,MAIN.PropMatcherContextP);
     la_FreePropStepCache(ui->PP.Go); //-------[Up Here], instance already been freed.XXXXXXXXXXXX!!!!!!!!!!1
+
+    if (ui->CompoundPPs.pFirst){laCompoundPP* CPP;
+        while(CPP = lstPopItem(&ui->CompoundPPs)){ la_FreePropStepCache(CPP->PP.Go); memFree(CPP); }
+    }
     if (ui->Type->Destroy) ui->Type->Destroy(ui);
     memFree(ui);
 }
@@ -5204,6 +5222,11 @@ int la_UpdateUiListRecursive(laUiList *uil, int U, int L, int R, int B, int Fast
 
         if (ui->PP.LastPs){ la_StepPropPack(&ui->PP); }
         la_UsePropPack(&ui->PP, 0);
+        if (ui->CompoundPPs.pFirst){
+            for(laCompoundPP* CPP=ui->CompoundPPs.pFirst;CPP;CPP=CPP->Item.pNext){
+                la_StepPropPack(&CPP->PP); la_UsePropPack(&CPP->PP, 0);
+            }
+        }
 
         if(ui->Type==&_LA_UI_INVISIBLE){ ui=ui->Item.pNext; continue; }
 
@@ -5991,6 +6014,7 @@ int la_DrawUiListRecursive(laUiListDraw *uild, laUiList *uil, int L, int R, int
                 int CanGetState = laCanGetState(ui->PP.LastPs->p);
                 int CanGetTheme = laCanGetTheme(ui->PP.LastPs->p);laTheme* OriginalTheme=MAIN.CurrentTheme;
                 void *Active = laGetActiveInstanceStrict(ui->PP.LastPs->p, ui->PP.LastPs->UseInstance);
+                void* SaveInstance = ui->PP.EndInstance;
                 for (sub = ui->Subs.pFirst; sub; sub = sub->Item.pNext){
                     int State;
                     ui->PP.EndInstance = sub->Instance;
@@ -6025,7 +6049,8 @@ int la_DrawUiListRecursive(laUiListDraw *uild, laUiList *uil, int L, int R, int
                     
                     if(CanGetTheme){ la_SwitchThemeQuick(0, OriginalTheme); }
                 }
-                ui->PP.EndInstance = Active;
+                //ui->PP.EndInstance = Active;
+                ui->PP.EndInstance = SaveInstance;
             }elif (ui->Type == _LA_UI_CANVAS){
                 if(!(ui->Flags&LA_UI_FLAGS_NO_OVERLAY)){
                     for (sub = ui->Subs.pFirst; sub; sub = sub->Item.pNext){

+ 64 - 0
la_util.c

@@ -151,6 +151,70 @@ int nutSameAddress(void *l, void *r){
     return (l == r);
 }
 
+barray_t *barray_init(size_t num_bits)
+{
+    size_t num_longs = BITS_TO_LONGS(num_bits);
+    barray_t *barray = malloc(sizeof(unsigned long) * num_longs + sizeof(barray_t));
+    barray->num_bits = num_bits;
+    barray->num_longs = num_longs;
+    return barray;
+}
+void barray_free(barray_t *barray)
+{
+    free(barray);
+}
+unsigned long *barray_data(barray_t *barray)
+{
+    return barray->data;
+}
+size_t barray_count_set(barray_t *barray)
+{
+    size_t count = 0;
+    for (int i = 0; i < barray->num_longs; i++)
+        count += __builtin_popcountl(barray->data[i]);
+    return count;
+}
+void barray_set(barray_t *barray, bit_t bit)
+{
+    if (bit >= barray->num_bits)
+        return;
+
+    int index = bit / BITS_PER_LONG;
+    int shift = bit % BITS_PER_LONG;
+
+    barray->data[index] |= (1 << shift);
+}
+void barray_clear(barray_t *barray, bit_t bit)
+{
+    if (bit >= barray->num_bits)
+        return;
+
+    int index = bit / BITS_PER_LONG;
+    int shift = bit % BITS_PER_LONG;
+
+    barray->data[index] &= ~(1 << shift);
+}
+bool barray_is_set(barray_t *barray, bit_t bit)
+{
+    if (bit >= barray->num_bits)
+        return false;
+
+    int index = bit / BITS_PER_LONG;
+    int shift = bit % BITS_PER_LONG;
+    return (barray->data[index] & (1 << shift)) != 0;
+}
+void barray_foreach_set(barray_t *barray, barray_callback_t callback, void *arg)
+{
+    for (int i = 0; i < barray->num_longs; i++)
+    {
+        unsigned long bits = barray->data[i];
+        while (bits != 0)
+        {
+            callback(i * BITS_PER_LONG + __builtin_ctzl(bits), arg);
+            bits ^= (bits & -bits);
+        }
+    }    
+}
 //===================================================================[list]
 
 void* arrElement(void* head, int i, int size){

+ 25 - 0
la_util.h

@@ -739,6 +739,31 @@ char * transLate(char * Target);
 void transState(void* UNUSED, int val);
 void transInitTranslation_zh_cn();
 
+#include <stdlib.h>
+#include <stdbool.h>
+
+typedef struct barray barray_t;
+typedef unsigned bit_t;
+
+barray_t *barray_init(size_t num_bits);
+void barray_free(barray_t *barray);
+unsigned long *barray_data(barray_t *barray);
+size_t barray_count_set(barray_t *barray);
+void barray_set(barray_t *barray, bit_t bit);
+void barray_clear(barray_t *barray, bit_t bit);
+bool barray_is_set(barray_t *barray, bit_t bit);
+typedef void (*barray_callback_t)(bit_t bit, void *arg);
+void barray_foreach_set(barray_t *barray, barray_callback_t callback, void *arg);
+
+#define BITS_PER_LONG        (sizeof(unsigned long) * 8)
+#define BITS_TO_LONGS(n)     (((n) + BITS_PER_LONG - 1) / BITS_PER_LONG)
+
+struct barray
+{
+    size_t num_bits;
+    size_t num_longs;
+    unsigned long data[0];
+};
 
 void laOpenInternetLink(char* url);
 

+ 9 - 4
resources/la_nodes_basic.c

@@ -1063,13 +1063,20 @@ void laui_AddNode(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *
     laShowItemFull(gu,gc,0,"la.node_categories",LA_WIDGET_COLLECTION_SINGLE,0,laui_NodeCategory,0)->Flags|=LA_UI_FLAGS_NO_DECAL;
 }
 
+laNodeRack* la_NewRackAfter(laRackPage* parent,laNodeRack* rr){
+    laNodeRack* r=memAcquire(sizeof(laNodeRack));
+    strSafeSet(&r->Name,"New Rack");
+    if(rr) lstInsertItemAfter(&parent->Racks,r,rr); else lstPushItem(&parent->Racks, r);
+    r->ParentPage=parent; r->RackType=parent->RackType;
+}
 int OPINV_MoveNodeToRack(laOperator* a, laEvent *e){
     laBaseNode* n=a->This?a->This->EndInstance:0; if(!n||!n->InRack) return LA_CANCELED;
     laBaseNodeType* bnt=0; laNodeRack* target;
 
     char* direction=strGetArgumentString(a->ExtraInstructionsP,"direction");
     
-    if(strSame(direction,"left")) target=n->InRack->Item.pPrev; else target=n->InRack->Item.pNext;
+    if(strSame(direction,"left")){ target=n->InRack->Item.pPrev; if(!target){ target=la_NewRackAfter(n->InRack->ParentPage,0); } }
+    else{ target=n->InRack->Item.pNext; if(!target){ target=la_NewRackAfter(n->InRack->ParentPage,n->InRack->ParentPage->Racks.pLast); } }
     if(!target) return LA_CANCELED;
 
     lstRemoveItem(&n->InRack->Nodes, n); lstAppendItem(&target->Nodes,n); n->InRack=target;
@@ -1100,9 +1107,7 @@ int OPINV_MoveRack(laOperator* a, laEvent *e){
 }
 int OPINV_InsertRack(laOperator* a, laEvent *e){
     laNodeRack* rr=a->This?a->This->EndInstance:0; if(!rr) return LA_CANCELED;
-    laNodeRack* r=memAcquire(sizeof(laNodeRack));
-    strSafeSet(&r->Name,"New Rack");
-    lstInsertItemAfter(&rr->ParentPage->Racks,r,rr); r->ParentPage=rr->ParentPage; r->RackType=rr->RackType;
+    laNodeRack* r = la_NewRackAfter(rr->ParentPage,rr);
     laNotifyInstanceUsers(r->ParentPage); laRecordInstanceDifferences(r->ParentPage,"la_rack_page"); laPushDifferences("Insert Rack", 0);
     return LA_FINISHED;
 }

+ 130 - 66
resources/la_widgets.c

@@ -113,8 +113,9 @@ int la_ArrayGetHeight(laUiItem *ui){
     return laGetArrayLength(&ui->PP);
 }
 int la_ValueGetHeight(laUiItem*ui){
-    if(ui->Flags&LA_UI_FLAGS_TRANSPOSE){ return la_ArrayGetHeight(ui); }
-    return 1;
+    int rows = ui->Extra->HeightCoeff; TNS_CLAMP(rows,1,4);
+    if(ui->Flags&LA_UI_FLAGS_TRANSPOSE){ return la_ArrayGetHeight(ui)*rows; }
+    return rows;
 }
 int la_ValueMeterGetHeight(laUiItem*ui){
     if(ui->Flags&LA_UI_FLAGS_TRANSPOSE){ return ui->Extra->HeightCoeff?ui->Extra->HeightCoeff:6; }
@@ -568,6 +569,7 @@ void la_IntDraw(laUiItem *ui, int h){
     int NoDecal=ui->Flags&LA_UI_FLAGS_NO_DECAL;
     int NoLabel=ui->Flags&LA_UI_FLAGS_NO_LABEL;
     int IsDisabled=ui->Flags&LA_UI_FLAGS_DISABLED;
+    int IsKnob = ui->Flags&LA_UI_FLAGS_NODE_CONTAINER;
 
     if (laIsPropertyReadOnly(&ui->PP) && !NoDecal) ui->State = LA_BT_DISABLED;
 
@@ -602,40 +604,68 @@ void la_IntDraw(laUiItem *ui, int h){
             ui->State = LA_UI_ACTIVE;
         }
 
-        if(!NoDecal){
+        if(IsKnob && !ui->Extra->Edit){
+            real ctrx = (real)(_L+_R)/2.0f, ctry=(real)(_U+_B)/2.0f, radius=(real)(_B-_U)/2.0f;
             tnsUseNoTexture();
+            real verts[52];//24*2+ctr+overlap
+            tnsMakeCircle2d(&verts[2],24,ctrx,ctry,radius,0);
+            tnsVectorCopy2d(&verts[2],&verts[50]);
+            verts[0]=ctrx; verts[1]=ctry;
             tnsColor4dv(laThemeColor(bt,ui->State));
-            tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
-            tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
-            tnsPackAs(GL_TRIANGLE_FAN);
-
-            if (Ranged){
-                int L1 = _L;
-                int R1 = (real)(Data[i] - min) / (real)(max - min) * (real)(Seg) + L1;
-                tnsUseNoTexture(); real* color=laThemeColor(bt,LA_BT_TEXT);
-                tnsColor4d(LA_COLOR3(color),0.3);
-                tnsVertex2d(L1, _U); tnsVertex2d(R1, _U);
-                tnsVertex2d(R1, _B); tnsVertex2d(L1, _B);
-                tnsPackAs(GL_TRIANGLE_FAN);
+            tnsVertexArray2d(verts,26); tnsPackAs(GL_TRIANGLE_FAN);
+            tnsMakeCircle2d(verts,24,ctrx,ctry,radius,0);
+            tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+            tnsVertexArray2d(verts,24); tnsPackAs(GL_LINE_LOOP);
+            if(Ranged){
+                real arc[52];  int arcindex[26]; // 12 slices 13 ends
+                real range = (real)(Data[i] - min) / (real)(max - min) * TNS_PI * 1.5;
+                tnsMakeArc2d(arc,12,ctrx,ctry,radius,TNS_PI*2.75, TNS_PI*2.75 + range);
+                tnsMakeArc2d(&arc[26],12,ctrx,ctry,radius*0.6,TNS_PI*2.75, TNS_PI*2.75 + range);
+                tnsMakeBridgedIndex(arcindex, 13, 0, 0);
+                real *color = laThemeColor(bt,LA_BT_TEXT|ui->State); //tnsColor4d(LA_COLOR3(color),0.3);
+                tnsColor4dv(color);
+                tnsVertexArray2d(arc,26); tnsIndexArray(arcindex,26); tnsPackAs(GL_TRIANGLE_STRIP);
+                tnsMakeArc2d(&arc[2],12,ctrx,ctry,radius*0.6,TNS_PI*2.75, TNS_PI*4.25);
+                tnsVertexArray2d(&arc[2],13);
+                tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+                tnsPackAs(GL_LINE_STRIP);
             }
+        }else{
+            if(!NoDecal){
+                tnsUseNoTexture();
+                tnsColor4dv(laThemeColor(bt,ui->State));
+                tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
+                tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
+                tnsPackAs(GL_TRIANGLE_FAN);
 
-            tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
-            tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
-            tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
-            tnsPackAs(GL_LINE_LOOP);
-        }
+                if (Ranged){
+                    int L1 = _L;
+                    int R1 = (real)(Data[i] - min) / (real)(max - min) * (real)(Seg) + L1;
+                    tnsUseNoTexture(); real* color=laThemeColor(bt,LA_BT_TEXT);
+                    tnsColor4d(LA_COLOR3(color),0.3);
+                    tnsVertex2d(L1, _U); tnsVertex2d(R1, _U);
+                    tnsVertex2d(R1, _B); tnsVertex2d(L1, _B);
+                    tnsPackAs(GL_TRIANGLE_FAN);
+                }
 
-        if (ui->Extra->On == i + 1 && ui->Extra->Edit){
-            uint32_t *buf = strGetCursorLine(ui->Extra->Edit, 0)->Buf;
-            int LL = _L + (Seg - tnsStringGetWidthU(buf, 0, ui->Flags&LA_TEXT_MONO)) / 2;
-            tnsColor4dv(laThemeColor(bt, LA_BT_TEXT));
-            la_SingleLineStringDrawSelection(ui, _L, _U, bt, buf, ui->Extra->Edit);
+                tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+                tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
+                tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
+                tnsPackAs(GL_LINE_LOOP);
+            }
+            if (ui->Extra->On == i + 1 && ui->Extra->Edit){
+                uint32_t *buf = strGetCursorLine(ui->Extra->Edit, 0)->Buf;
+                int LL = _L + (Seg - tnsStringGetWidthU(buf, 0, ui->Flags&LA_TEXT_MONO)) / 2;
+                tnsColor4dv(laThemeColor(bt, LA_BT_TEXT));
+                la_SingleLineStringDrawSelection(ui, _L, _U, bt, buf, ui->Extra->Edit);
 
-            tnsDrawStringM(0, buf, laThemeColor(bt, LA_BT_TEXT_ACTIVE), _L + bt->LM, _R - bt->RM, _U, ui->Flags);
-        }else{
-            tnsDrawStringWithPriority(NoLabel?0:buf2, buf,
-                IsDisabled?laThemeColor(bt, LA_BT_DISABLED):laThemeColor(bt, LA_BT_TEXT|ui->State), bt->TextAlign, _L + bt->LM, _R - bt->RM, _U, ui->Flags);//, ui->ExtraInstructions);
+                tnsDrawStringM(0, buf, laThemeColor(bt, LA_BT_TEXT_ACTIVE), _L + bt->LM, _R - bt->RM, _U, ui->Flags);
+            }else{
+                tnsDrawStringWithPriority(NoLabel?0:buf2, buf,
+                    IsDisabled?laThemeColor(bt, LA_BT_DISABLED):laThemeColor(bt, LA_BT_TEXT|ui->State), bt->TextAlign, _L + bt->LM, _R - bt->RM, _U, ui->Flags);//, ui->ExtraInstructions);
+            }
         }
+
         if (ui->Extra && ui->Extra->On == i + 1) ui->State = Original;
     }
 
@@ -657,6 +687,7 @@ void la_FloatDraw(laUiItem *ui, int h){
     int IsRad=ui->PP.LastPs->p->IsRadAngle;
     int NoLabel=ui->Flags&LA_UI_FLAGS_NO_LABEL;
     int IsDisabled=ui->Flags&LA_UI_FLAGS_DISABLED;
+    int IsKnob = ui->Flags&LA_UI_FLAGS_NODE_CONTAINER;
 
     if (laIsPropertyReadOnly(&ui->PP) && !NoDecal) ui->State = LA_BT_DISABLED;
 
@@ -687,39 +718,68 @@ void la_FloatDraw(laUiItem *ui, int h){
             ui->State = LA_UI_ACTIVE;
         }
 
-        if(!NoDecal){
+        if(IsKnob && !ui->Extra->Edit){
+            real ctrx = (real)(_L+_R)/2.0f, ctry=(real)(_U+_B)/2.0f, radius=(real)(_B-_U)/2.0f;
             tnsUseNoTexture();
+            real verts[52];//24*2+ctr+overlap
+            tnsMakeCircle2d(&verts[2],24,ctrx,ctry,radius,0);
+            tnsVectorCopy2d(&verts[2],&verts[50]);
+            verts[0]=ctrx; verts[1]=ctry;
             tnsColor4dv(laThemeColor(bt,ui->State));
-            tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
-            tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
-            tnsPackAs(GL_TRIANGLE_FAN);
-
-            if (Ranged){
-                int L1 = _L;
-                int R1 = (real)(Data[i] - min) / (real)(max - min) * (real)(Seg) + L1;
-                tnsUseNoTexture(); real* color=laThemeColor(bt,LA_BT_TEXT);
-                tnsColor4d(LA_COLOR3(color),0.3);
-                tnsVertex2d(L1, _U); tnsVertex2d(R1, _U);
-                tnsVertex2d(R1, _B); tnsVertex2d(L1, _B);
-                tnsPackAs(GL_TRIANGLE_FAN);
+            tnsVertexArray2d(verts,26); tnsPackAs(GL_TRIANGLE_FAN);
+            tnsMakeCircle2d(verts,24,ctrx,ctry,radius,0);
+            tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+            tnsVertexArray2d(verts,24); tnsPackAs(GL_LINE_LOOP);
+            if(Ranged){
+                real arc[52];  int arcindex[26]; // 12 slices 13 ends
+                real range = (real)(Data[i] - min) / (real)(max - min) * TNS_PI * 1.5;
+                tnsMakeArc2d(arc,12,ctrx,ctry,radius,TNS_PI*2.75, TNS_PI*2.75 + range);
+                tnsMakeArc2d(&arc[26],12,ctrx,ctry,radius*0.6,TNS_PI*2.75, TNS_PI*2.75 + range);
+                tnsMakeBridgedIndex(arcindex, 13, 0, 0);
+                real *color = laThemeColor(bt,LA_BT_TEXT|ui->State); //tnsColor4d(LA_COLOR3(color),0.3);
+                tnsColor4dv(color);
+                tnsVertexArray2d(arc,26); tnsIndexArray(arcindex,26); tnsPackAs(GL_TRIANGLE_STRIP);
+                tnsMakeArc2d(&arc[2],12,ctrx,ctry,radius*0.6,TNS_PI*2.75, TNS_PI*4.25);
+                arc[0] = ctrx + cos(TNS_PI*2.75) * radius; arc[1] = ctry + sin(TNS_PI*2.75) * radius;
+                arc[28] =ctrx + cos(TNS_PI*4.25) * radius; arc[29] =ctry + sin(TNS_PI*4.25) * radius;
+                tnsVertexArray2d(arc,15);
+                tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+                tnsPackAs(GL_LINE_STRIP);
             }
+        }else{
+            if(!NoDecal){
+                tnsUseNoTexture();
+                tnsColor4dv(laThemeColor(bt,ui->State));
+                tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
+                tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
+                tnsPackAs(GL_TRIANGLE_FAN);
 
-            tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
-            tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
-            tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
-            tnsPackAs(GL_LINE_LOOP);
-        }
+                if (Ranged){
+                    int L1 = _L;
+                    int R1 = (real)(Data[i] - min) / (real)(max - min) * (real)(Seg) + L1;
+                    tnsUseNoTexture(); real* color=laThemeColor(bt,LA_BT_TEXT);
+                    tnsColor4d(LA_COLOR3(color),0.3);
+                    tnsVertex2d(L1, _U); tnsVertex2d(R1, _U);
+                    tnsVertex2d(R1, _B); tnsVertex2d(L1, _B);
+                    tnsPackAs(GL_TRIANGLE_FAN);
+                }
 
-        if (ui->Extra->On == i + 1 && ui->Extra->Edit){
-            uint32_t *buf = strGetCursorLine(ui->Extra->Edit,0)->Buf;
-            int LL = _L + (Seg - tnsStringGetWidthU(buf, 0, ui->Flags&LA_TEXT_MONO)) / 2;
-            tnsColor4dv(laThemeColor(bt, LA_BT_TEXT));
-            la_SingleLineStringDrawSelection(ui, _L, _U, bt, buf, ui->Extra->Edit);
+                tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+                tnsVertex2d(_L, _U); tnsVertex2d(_R, _U);
+                tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
+                tnsPackAs(GL_LINE_LOOP);
+            }
+            if (ui->Extra->On == i + 1 && ui->Extra->Edit){
+                uint32_t *buf = strGetCursorLine(ui->Extra->Edit,0)->Buf;
+                int LL = _L + (Seg - tnsStringGetWidthU(buf, 0, ui->Flags&LA_TEXT_MONO)) / 2;
+                tnsColor4dv(laThemeColor(bt, LA_BT_TEXT));
+                la_SingleLineStringDrawSelection(ui, _L, _U, bt, buf, ui->Extra->Edit);
 
-            tnsDrawStringM(0, buf, laThemeColor(bt, LA_BT_TEXT_ACTIVE), _L + bt->LM, _R - bt->RM, _U, ui->Flags);
-        }else{
-            tnsDrawStringWithPriority(NoLabel?0:buf2, buf,
-                IsDisabled?laThemeColor(bt, LA_BT_DISABLED):laThemeColor(bt, LA_BT_TEXT|ui->State), bt->TextAlign, _L + bt->LM, _R - bt->RM, _U, ui->Flags);//, ui->ExtraInstructions);
+                tnsDrawStringM(0, buf, laThemeColor(bt, LA_BT_TEXT_ACTIVE), _L + bt->LM, _R - bt->RM, _U, ui->Flags);
+            }else{
+                tnsDrawStringWithPriority(NoLabel?0:buf2, buf,
+                    IsDisabled?laThemeColor(bt, LA_BT_DISABLED):laThemeColor(bt, LA_BT_TEXT|ui->State), bt->TextAlign, _L + bt->LM, _R - bt->RM, _U, ui->Flags);//, ui->ExtraInstructions);
+            }
         }
         if (ui->Extra && ui->Extra->On == i + 1) ui->State = Original;
     }
@@ -1957,6 +2017,7 @@ int OPMOD_IntArrayHorizon(laOperator *a, laEvent *e){
     int IsVertical=(ui->Flags&LA_UI_FLAGS_TRANSPOSE)!=0;
     int NoEvent = ui->Flags&LA_UI_FLAGS_NO_EVENT;
     int NoTooltip=ui->Flags&LA_UI_FLAGS_NO_TOOLTIP;
+    int IsKnob = ui->Flags&LA_UI_FLAGS_NODE_CONTAINER;
 
     if (!laIsInUiItem(ui, e->x, e->y) && !ui->Extra->On){
         ui->State = LA_UI_NORMAL;
@@ -1997,12 +2058,12 @@ int OPMOD_IntArrayHorizon(laOperator *a, laEvent *e){
     }
     if (e->Type == LA_MOUSEMOVE && ui->Extra->On){
         if(ui->Extra->Edit){ return LA_RUNNING; }
-        int dist=abs(e->x - uit->LastX);
+        int dist=abs(e->x - uit->LastX + uit->LastY - e->y);
         if (dist > MAIN.ValuatorThreshold || uit->Dragging){
-            int min,max,Ranged = laGetIntRange(&ui->PP, &min, &max); real step=ip->Step; if(Ranged){ step=(real)(max-min)/(ui->R-ui->L); }
+            int min,max,Ranged = laGetIntRange(&ui->PP, &min, &max); real step=ip->Step; if(Ranged&&!IsKnob){ step=(real)(max-min)/(ui->R-ui->L); }
             //laGetIntArray(&ui->PP, &TmpArr);
             //uit->TargetIndexVali = TmpArr[ui->Extra->On - 1];
-            int delta=e->x-uit->LastX; if(!uit->Dragging) delta=delta>0?1:-1;
+            int delta=e->x-uit->LastX; if(IsKnob){ int dy=uit->LastY-e->y; delta=delta+dy; } if(!uit->Dragging) delta=delta>0?1:-1;
             uit->TargetIndexValf +=  delta*step;
             laSetIntArraySingle(&ui->PP, ui->Extra->On - 1, uit->TargetIndexValf);
             uit->LastX = e->x;
@@ -2078,6 +2139,7 @@ int OPMOD_FloatArrayHorizon(laOperator *a, laEvent *e){
     int NoEvent = ui->Flags&LA_UI_FLAGS_NO_EVENT;
     int IsRad=ui->PP.LastPs->p->IsRadAngle;
     int NoTooltip=ui->Flags&LA_UI_FLAGS_NO_TOOLTIP;
+    int IsKnob = ui->Flags&LA_UI_FLAGS_NODE_CONTAINER;
 
     if (!laIsInUiItem(ui, e->x, e->y) && !ui->Extra->On && !NoTooltip){
         ui->State = LA_UI_NORMAL;
@@ -2119,12 +2181,12 @@ int OPMOD_FloatArrayHorizon(laOperator *a, laEvent *e){
     }
     if (e->Type == LA_MOUSEMOVE && ui->Extra->On){
         if(uit->Edit){ return LA_RUNNING; }
-        int dist=abs(e->x - uit->LastX);
+        int dist=abs(e->x - uit->LastX - e->y + uit->LastY);
         if (dist > MAIN.ValuatorThreshold || uit->Dragging){
-            real min,max; int Ranged = laGetFloatRange(&ui->PP, &min, &max); real step=fp->Step; if(Ranged){ step=(max-min)/(ui->R-ui->L); }
+            real min,max; int Ranged = laGetFloatRange(&ui->PP, &min, &max); real step=fp->Step; if(Ranged&&!IsKnob){ step=(max-min)/(ui->R-ui->L); }
             laGetFloatArray(&ui->PP, &TmpArr);
             uit->TargetIndexValf = TmpArr[ui->Extra->On - 1];
-            int delta=e->x-uit->LastX; if(!uit->Dragging) delta=delta>0?1:-1;
+            int delta=e->x-uit->LastX; if(IsKnob){ int dy=uit->LastY-e->y; delta=delta+dy; }  if(!uit->Dragging) delta=delta>0?1:-1;
             uit->TargetIndexValf +=  delta*step;
             laSetFloatArraySingle(&ui->PP, ui->Extra->On - 1, uit->TargetIndexValf);
             uit->LastX = e->x;
@@ -2313,6 +2375,8 @@ int OPMOD_EnumSelector(laOperator *a, laEvent *e){
     laEnumItem *Data[32];
     int NoEvent = ui->Flags&LA_UI_FLAGS_NO_EVENT;
     int NoTooltip=ui->Flags&LA_UI_FLAGS_NO_TOOLTIP;
+    int IsCycle = ui->Flags&LA_UI_FLAGS_CYCLE;
+    int IsMomentary = IsCycle && ui->Flags&LA_UI_FLAGS_NODE_CONTAINER;
     // norm      e1
     // expand    e1 e2 e3
     // expand vertical  (vertical)
@@ -2328,9 +2392,9 @@ int OPMOD_EnumSelector(laOperator *a, laEvent *e){
         return LA_RUNNING;
     }
 
-    if (!laIsInUiItem(ui, e->x, e->y)){ return LA_FINISHED_PASS; }
+    if (!laIsInUiItem(ui, e->x, e->y) && !uit->On){ return LA_FINISHED_PASS; }
     
-    if (e->Type == LA_TIME_IDLE && !NoTooltip){
+    if (e->Type == LA_TIME_IDLE && !NoTooltip && !uit->On){
         int GX = e->x, GY = e->y; laLocalToWindow(a, a->ToPanel, &GX, &GY);
         laPanel *p = laEnableIdlePanel(a->ToPanel, a, 0, 0, &ui->PP, GX, GX + 150, GY, 600, 200, e);
         return LA_RUNNING;
@@ -2338,12 +2402,11 @@ int OPMOD_EnumSelector(laOperator *a, laEvent *e){
 
     if(NoEvent){ return LA_RUNNING_PASS; }
 
-    if (e->Type == LA_L_MOUSE_DOWN){
+    if (e->Type == LA_L_MOUSE_DOWN || (IsMomentary && e->Type == LA_L_MOUSE_UP)){
         if (laIsPropertyReadOnly(&ui->PP)) return LA_RUNNING_PASS;
 
         int IsVertical = ui->Flags&LA_UI_FLAGS_TRANSPOSE;
         int IsExpand = ui->Flags&LA_UI_FLAGS_EXPAND;
-        int IsCycle = ui->Flags&LA_UI_FLAGS_CYCLE;
         int ArrLen = laGetArrayLength(&ui->PP);
         if(ArrLen==1){ if(!IsExpand) IsVertical=0; }else{ IsExpand=1; }
         int EnumLen = (IsExpand&&(!IsCycle)) ? laGetEnumEntryLen(&ui->PP) : 1;
@@ -2377,6 +2440,7 @@ int OPMOD_EnumSelector(laOperator *a, laEvent *e){
             laEnumItem* ei=laGetEnumArrayIndexed(&ui->PP, ArrTarget);
             if(!ei){ ei=((laEnumProp*)ui->PP.LastPs->p)->Items.pFirst; }
             ei = ei->Item.pNext?ei->Item.pNext:((laEnumProp*)ui->PP.LastPs->p)->Items.pFirst;
+            if(IsMomentary){  uit->On = 1; if(e->Type==LA_L_MOUSE_UP){ ei = ei=((laEnumProp*)ui->PP.LastPs->p)->Items.pFirst; uit->On = 0;} }
             laSetEnumArrayIndexed(&ui->PP, ArrTarget, ei->Index);
             laConfirmInt(a,EnumTarget,LA_CONFIRM_DATA);
             laRecordAndPushProp(&ui->PP,0); laMarkPropChanged(&ui->PP);