*/}}
Browse Source

Animation working slightly better?

YimingWu 1 year ago
parent
commit
624c46933c

+ 96 - 40
la_animation.c

@@ -21,7 +21,6 @@
 extern LA MAIN;
 extern tnsMain* T;
 
-int la_GetKeyablePropertyStorageSize(laProp* p);
 void la_AnimationEvaluateActions(int ClampOffsets);
 void laget_AnimationActionHolderCategory(void* a_unused, laActionHolder* ah, char* copy, char** ptr);
 void laui_AnimationActionHolder(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *ExtraColumns, int context);
@@ -47,13 +46,16 @@ void laAnimationUpdateHolderList(){
 
     for(laActionHolderPath* ahp=MAIN.Animation->ActionHolderPaths.pFirst;ahp;ahp=ahp->Item.pNext){
         la_StepPropPack(&ahp->PP); laSubProp* pa=ahp->PP.LastPs->p; la_EnsureSubTarget(pa,0); laPropIterator pi={0};
-        int ListOffset=0; laSubProp* paa=la_PropLookup(&pa->Base.SubProp->Props,"__actions__");
-        if((!paa) || (!paa->ListHandleOffset)){ continue; } ListOffset=paa->ListHandleOffset;
+        int ListOffset=0,PropOffset=0;
+        laSubProp* paa=la_PropLookup(&pa->Base.SubProp->Props,"__actions__");
+        laSubProp* paap=la_PropLookup(&pa->Base.SubProp->Props,"__action_props__");
+        if((!paa) || (!paap) || (!paa->ListHandleOffset) || (!paap->ListHandleOffset)){ continue; }
+        ListOffset=paa->ListHandleOffset; PropOffset=paap->ListHandleOffset;
         void* inst=laGetInstance(ahp->PP.LastPs->p,ahp->PP.LastPs->UseInstance,&pi); int FirstIn=1;
         while(inst){
             ah=memAcquire(sizeof(laActionHolder));
             memAssignRef(ah,&ah->Instance,inst);
-            ah->ListHandleOffset=ListOffset;
+            ah->ActionOffset=ListOffset; ah->PropOffset=PropOffset;
             ah->Container=ahp->PP.LastPs->p->SubProp;
             char _id[64]="unamed", *id=_id;
             laTryGetInstanceIdentifier(inst,pa->Base.SubProp,_id,&id);
@@ -70,21 +72,20 @@ laAction* laAnimiationNewAction(laActionHolder* ah, char* Name){
     laAction* aa=memAcquire(sizeof(laAction));
     if(!Name || !Name[0]){ Name="New Action"; }
     strSafeSet(&aa->Name,Name);
-    aa->Length=2; aa->FrameCount=24; aa->HolderContainer=ah->Container;
+    aa->Length=2; aa->FrameCount=24; memAssignRef(aa,&aa->Holder,ah);
     memAssignRef(aa,&aa->HolderInstance,ah->Instance);
     memAssignRef(MAIN.Animation,&MAIN.Animation->CurrentAction,aa);
-    void* lh=((uint8_t*)ah->Instance)+ah->ListHandleOffset;
+    void* lh=((uint8_t*)ah->Instance)+ah->ActionOffset;
     lstAppendItem(lh,aa);
     laNotifyInstanceUsers(ah->Instance);
     laNotifyUsers("la.animation.current_action");
     return aa;
 }
-laActionProp* laAnimationEnsureProp(void* hyper1, laProp* p){
-    int DataSize=la_GetKeyablePropertyStorageSize(p); if(!DataSize) return 0;
-    for(laActionProp* ap=MAIN.Animation->Props.pFirst;ap;ap=ap->Item.pNext){
-        if(ap->For==hyper1 && ap->Prop==p){ return ap; }
-    }
-    laActionProp* ap = memAcquire(sizeof(laActionProp)); lstAppendItem(&MAIN.Animation->Props,ap);
+laActionProp* laAnimationEnsureProp(laAction* aa, void* hyper1, laProp* p){
+    int DataSize=la_GetKeyablePropertyStorageSize(p); if(!DataSize || !aa->HolderInstance || !aa->Holder) return 0;
+    laListHandle *pl=((uint8_t*)aa->HolderInstance)+aa->Holder->PropOffset;
+    for(laActionProp* ap=pl->pFirst;ap;ap=ap->Item.pNext){ if(ap->For==hyper1 && ap->Prop==p){ return ap; } }
+    laActionProp* ap = memAcquire(sizeof(laActionProp)); lstAppendItem(pl,ap);
     memAssignRef(ap,&ap->For,hyper1); ap->Prop=p;
     ap->DataSize = DataSize;
     char _name[128]={0}, *name=_name; laTryGetInstanceIdentifier(hyper1, p->Container,_name,&name);
@@ -92,10 +93,8 @@ laActionProp* laAnimationEnsureProp(void* hyper1, laProp* p){
     return ap;
 }
 laActionChannel* laAnimationEnsureChannel(laAction* aa, void* hyper1, laProp* p){
-    laActionProp* ap=laAnimationEnsureProp(hyper1,p); if(!ap) return 0; laActionChannel* ac;
-    for(ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
-        if(ac->AP==ap) return ac;
-    }
+    laActionProp* ap=laAnimationEnsureProp(aa, hyper1,p); if(!ap) return 0; laActionChannel* ac;
+    for(ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){ if(ac->AP==ap) return ac; }
     ac=memAcquire(sizeof(laActionChannel)); ac->AP=ap;
     lstAppendItem(&aa->Channels,ac); laNotifyUsers("la.animation.current_action.channels");
     return ac;
@@ -106,8 +105,8 @@ laActionChannel* laAnimationEnsureFrame(laActionChannel* ac, int frame){
         if(ak->At==frame){ akf=ak; break; } if(ak->At>frame){ beforeakf=ak; break; }
     }
     if(!akf){
-        akf=memAcquireSimple(sizeof(laActionKey)-sizeof(uint64_t)+ac->AP->DataSize);
-        akf->At=frame;
+        akf=memAcquireSimple(sizeof(laActionKey));
+        akf->At=frame; akf->Data=memAcquireSimple(ac->AP->DataSize); akf->DataSize=ac->AP->DataSize;
         if(beforeakf){ lstInsertItemBefore(&ac->Keys, akf, beforeakf); }
         else{ lstAppendItem(&ac->Keys, akf); }
     }
@@ -117,17 +116,17 @@ void laAnimationStoreKeyValue(laActionChannel* ac, laActionKey* ak){
     laPropPack PP={0}; laPropStep PS={0}; laActionProp* ap=ac->AP; if(!ap) return;
     PS.p=ap->Prop; PS.Type='.'; PS.UseInstance=ap->For; PP.LastPs=&PS; PP.EndInstance=ap->For;
     switch(ap->Prop->PropertyType){
-    case LA_PROP_INT: case LA_PROP_INT | LA_PROP_ARRAY:     laGetIntArray(&PP,(int*)&ak->Data); break;
-    case LA_PROP_FLOAT: case LA_PROP_FLOAT | LA_PROP_ARRAY: laGetFloatArray(&PP,(real*)&ak->Data); break;
-    case LA_PROP_ENUM: case LA_PROP_ENUM | LA_PROP_ARRAY:   laGetEnumArray(&PP,(laEnumItem**)&ak->Data); break;
+    case LA_PROP_INT: case LA_PROP_INT | LA_PROP_ARRAY:     laGetIntArray(&PP,(int*)ak->Data); break;
+    case LA_PROP_FLOAT: case LA_PROP_FLOAT | LA_PROP_ARRAY: laGetFloatArray(&PP,(real*)ak->Data); break;
+    case LA_PROP_ENUM: case LA_PROP_ENUM | LA_PROP_ARRAY:   laGetEnumArray(&PP,(laEnumItem**)ak->Data); break;
     case LA_PROP_SUB: case LA_PROP_OPERATOR: case LA_PROP_STRING: case LA_PROP_RAW: default: return;
     }
 }
 laActionKey* laAnimationInsertKeyFrame(laAction* aa, void* hyper1, laProp* p, int* error){
     if(error) *error=0; if(!aa) return 0;
-    if(!aa->HolderInstance||!aa->HolderContainer){ if(error) *error=1; return 0; }
-    if(aa->HolderContainer->ActionHolderVerify){
-        if(!aa->HolderContainer->ActionHolderVerify(aa->HolderInstance,aa->HolderContainer,hyper1,p->Container)){
+    if(!aa->HolderInstance||!aa->Holder||!aa->Holder->Container){ if(error) *error=1; return 0; }
+    if(aa->Holder->Container->ActionHolderVerify){
+        if(!aa->Holder->Container->ActionHolderVerify(aa->HolderInstance,aa->Holder->Container,hyper1,p->Container)){
             if(error) *error=2; return 0;
         }
     }
@@ -173,7 +172,9 @@ int OPMOD_AnimationNewAction(laOperator *a, laEvent *e){
 
     if(a->ConfirmData->Mode == LA_CONFIRM_DATA){
         if (!np || !np->SelectedHolder){ if(np) memFree(np); return LA_CANCELED; }
-        laAnimiationNewAction(np->SelectedHolder,0); memFree(np);
+        laAnimiationNewAction(np->SelectedHolder,0);
+        laRecordInstanceDifferences(np->SelectedHolder->Instance,np->SelectedHolder->Container->Identifier);
+        laRecordDifferences(0,"la.animation"); laPushDifferences("New action",0); memFree(np);
         return LA_FINISHED;
     }
 
@@ -196,6 +197,50 @@ void laui_AnimationSelectAction(laUiList *uil, laPropPack *This, laPropPack *Ope
     }
 }
 
+void laAnimationRemoveFrame(laActionChannel* ac, laActionKey* ak){
+    ak->DataSize=0; memLeave(ak->Data); ak->Data=0;
+    lstRemoveItem(&ac->Keys,ak); memLeave(ak);
+}
+void laAnimationRemoveChannel(laAction* aa, laActionChannel* ac){
+    while(ac->Keys.pFirst){ laAnimationRemoveFrame(ac,ac->Keys.pFirst); }
+    lstRemoveItem(&aa->Channels,ac); memLeave(ac);
+}
+void laAnimationRemoveAction(laAction* aa){
+    while(aa->Channels.pFirst){ laAnimationRemoveChannel(aa,aa->Channels.pFirst); }
+    strSafeDestroy(&aa->Name);
+    if(aa=MAIN.Animation->CurrentAction){
+        if(aa->Item.pNext){ memAssignRef(MAIN.Animation,&MAIN.Animation->CurrentAction,aa->Item.pNext); }
+        else{ memAssignRef(MAIN.Animation,&MAIN.Animation->CurrentAction,aa->Item.pPrev); }
+    }
+    if(!aa->Holder || !aa->HolderInstance){ goto action_remove_clean; }
+    laListHandle* al=((uint8_t*)aa->HolderInstance)+aa->Holder->ActionOffset;
+    lstRemoveItem(al,aa);
+action_remove_clean:
+    memLeave(aa);
+}
+
+int OPCHK_AnimationRemoveAction(laPropPack *This, laStringSplitor *Instructions){
+    laPropContainer* pc; return LA_VERIFY_THIS_TYPE(This,pc,"la_animation_action");
+}
+int OPINV_AnimationRemoveAction(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_CANCELED; }
+    laAction* aa=a->This->EndInstance;
+    char str[256]; sprintf(str,"Will remove action \"%s\".",SSTR(aa->Name));
+    laEnableYesNoPanel(a,0,"Remove?",str,e->x,e->y,200,e);
+    return LA_RUNNING;
+}
+int OPMOD_AnimationRemoveAction(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return LA_CANCELED; }
+    laAction* aa=a->This->EndInstance;
+
+    if(a->ConfirmData && a->ConfirmData->Mode==LA_CONFIRM_OK){
+        laAnimationRemoveAction(aa); laNotifyUsers("la.animation");
+        laRecordInstanceDifferences(aa->HolderInstance,aa->Holder->Container->Identifier);
+        laRecordDifferences(0,"la.animation"); laPushDifferences("Remove action",0);
+    }
+
+    return LA_FINISHED;
+}
 
 int OPINV_AnimationPlayAction(laOperator *a, laEvent *e){
     char* str=strGetArgumentString(a->ExtraInstructionsP, "mode");
@@ -208,7 +253,7 @@ int OPINV_AnimationPlayAction(laOperator *a, laEvent *e){
 
 void la_AnimationActionSetOwnFrame(laAction* aa, int frame){
     if(!aa) return;
-    aa->Offset+=(aa->PlayHead-(real)frame/aa->FrameCount)*aa->Length-1e-4;
+    aa->Offset+=(aa->PlayHead-(real)frame/aa->FrameCount)*aa->Length;
     la_AnimationEvaluateActions(0);
     laNotifyUsers("la.animation.current_action");
 }
@@ -230,18 +275,21 @@ laActionChannel* laAnimationGetFrame(laActionChannel* ac, int frame){
 }
 
 void la_AnimationMarkPropReset(){
-    for(laActionProp* ap=MAIN.Animation->Props.pFirst;ap;ap=ap->Item.pNext){ ap->Reset=1; }
+    for(laActionHolder* ah=MAIN.Animation->ActionHolders.pFirst;ah;ah=ah->Item.pNext){
+        if(!ah->Instance){ continue; } laListHandle* lp=((uint8_t*)ah->Instance)+ah->PropOffset;
+        for(laActionProp* ap=lp->pFirst;ap;ap=ap->Item.pNext){ ap->Reset=1; }
+    }
 }
 void la_AnimationInterpolateKeys(laActionChannel* ac, laActionKey* ak1, laActionKey* ak2, real PlayHead_mul_Length, void** data){
     int* id1,*id2,*iret=(*data); real* fd1,*fd2,*fret=(*data);
-    if(!ak2){ *data=&ak1->Data; return; } laActionProp* ap=ac->AP; if(!ap) return;
+    if(!ak2){ *data=ak1->Data; return; } laActionProp* ap=ac->AP; if(!ap) return;
     int arrlen=ap->Prop->Len?ap->Prop->Len:1;
     real fac=tnsGetRatiod(ak1->At,ak2->At,PlayHead_mul_Length); TNS_CLAMP(fac,0,1);
     switch(ap->Prop->PropertyType){
     case LA_PROP_INT: case LA_PROP_INT|LA_PROP_ARRAY:
-        id1=&ak1->Data;id2=&ak2->Data; for(int i=0;i<arrlen;i++){ iret[i]=tnsLinearItp(id1[i],id2[i],fac); } break;
+        id1=ak1->Data;id2=ak2->Data; for(int i=0;i<arrlen;i++){ iret[i]=tnsLinearItp(id1[i],id2[i],fac); } break;
     case LA_PROP_FLOAT: case LA_PROP_FLOAT|LA_PROP_ARRAY:
-        fd1=&ak1->Data;fd2=&ak2->Data; for(int i=0;i<arrlen;i++){ fret[i]=tnsLinearItp(fd1[i],fd2[i],fac); } break;
+        fd1=ak1->Data;fd2=ak2->Data; for(int i=0;i<arrlen;i++){ fret[i]=tnsLinearItp(fd1[i],fd2[i],fac); } break;
     default:
         *data=ak1->Data; break;
     }
@@ -261,7 +309,7 @@ void la_AnimationSetPropValue(laActionProp* ap){
     }
 }
 void la_AnimationMixChannelValue(laActionChannel* ac, void* data, int MixMode, real Factor){
-    laActionProp* ap=ac->AP; if(!ap) return;  int* ip=&ap->Data,*ipd=data; real* fp=&ap->Data,*fpd=data;
+    laActionProp* ap=ac->AP; if(!ap) return;  int* ip=ap->Data,*ipd=data; real* fp=ap->Data,*fpd=data;
     int arrlen=ap->Prop->Len?ap->Prop->Len:1;
     switch(ap->Prop->PropertyType){
     case LA_PROP_INT:  case LA_PROP_INT|LA_PROP_ARRAY: for(int i=0;i<arrlen;i++){ 
@@ -292,16 +340,16 @@ void la_AnimationEvaluateActions(int ClampOffsets){
     la_AnimationMarkPropReset();
     for(laActionHolder* ah=MAIN.Animation->ActionHolders.pFirst;ah;ah=ah->Item.pNext){
         if(!ah->Instance) continue;
-        laListHandle* lh=((uint8_t*)ah->Instance)+ah->ListHandleOffset;
+        laListHandle* lh=((uint8_t*)ah->Instance)+ah->ActionOffset;
         for(laAction* aa=lh->pFirst;aa;aa=aa->Item.pNext){
             real preoffset=0,postoffset=aa->Offset/aa->Length;
-            if(ClampOffsets || (MAIN.Animation->PlayStatus!=LA_ANIMATION_STATUS_PAUSED) || (aa!=MAIN.Animation->CurrentAction)){
-                while(aa->Offset>aa->Length){ aa->Offset-=aa->Length; }
-                while(aa->Offset<-1e-6){ aa->Offset+=aa->Length; }
+            if(ClampOffsets || (MAIN.Animation->PlayStatus!=LA_ANIMATION_STATUS_PAUSED)){
+                while(aa->Offset>aa->Length*2){ aa->Offset-=aa->Length*2; }
+                while(aa->Offset<0){ aa->Offset+=aa->Length*2; }
                 preoffset=aa->Offset; postoffset=0;
             }
             real UseTime=MAIN.Animation->PlayHead-preoffset;
-            int repeats=UseTime/aa->Length;
+            int repeats=(real)(UseTime/aa->Length);
             real remaining=UseTime-repeats*aa->Length;
             while(remaining<0){ remaining+=aa->Length; }
             if(aa->PlayMode==LA_ANIMATION_PLAY_MODE_REPEAT){ aa->PlayHead=remaining/aa->Length-postoffset; }
@@ -312,8 +360,11 @@ void la_AnimationEvaluateActions(int ClampOffsets){
         }
     }
     
-    for(laActionProp* ap=MAIN.Animation->Props.pFirst;ap;ap=ap->Item.pNext){ if(ap->Reset){ continue; }
-        la_AnimationSetPropValue(ap);
+    for(laActionHolder* ah=MAIN.Animation->ActionHolders.pFirst;ah;ah=ah->Item.pNext){
+        if(!ah->Instance){ continue; } laListHandle* lp=((uint8_t*)ah->Instance)+ah->PropOffset;
+        for(laActionProp* ap=lp->pFirst;ap;ap=ap->Item.pNext){ if(ap->Reset){ continue; }
+            la_AnimationSetPropValue(ap);
+        }
     }
     if(any) laNotifyUsers("la.animation");
 }
@@ -596,9 +647,9 @@ void la_AnimationActionDrawCanvas(laBoxedTheme *bt, laAction *aa, laUiItem* ui){
         if(!aa){
             tnsDrawStringAuto("No action selected",laThemeColor(bt,LA_BT_TEXT),ll+bt->LP,lr-bt->RP,ui->U+bt->BP,0);
         }else{ int row=1;
-            tnsDrawStringAuto(aa->Name->Ptr,laThemeColor(bt,LA_BT_TEXT),ll+bt->LP,lr-bt->RP,ui->U+bt->BP,0);
+            tnsDrawStringAuto(SSTR(aa->Name),laThemeColor(bt,LA_BT_TEXT),ll+bt->LP,lr-bt->RP,ui->U+bt->BP,0);
             for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){ if(!ac->AP){ continue; }
-                tnsDrawStringAuto(ac->AP->CachedName->Ptr,laThemeColor(bt,LA_BT_TEXT),ll+bt->LP,lr-bt->RP,ui->U+bt->TP+row*LA_RH-ex->PanY,0);
+                tnsDrawStringAuto(SSTR(ac->AP->CachedName),laThemeColor(bt,LA_BT_TEXT),ll+bt->LP,lr-bt->RP,ui->U+bt->TP+row*LA_RH-ex->PanY,0);
                 row++;
             }
         }
@@ -647,6 +698,9 @@ void la_AnimationActionDrawOverlay(laUiItem *ui, int h){
     return;
 }
 
+void laui_AnimationActionMenu(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
+
+}
 
 void la_RegisterAnimationResources(){
     laPropContainer *pc; laProp *p; laOperatorType *at; laEnumProp *ep;
@@ -658,6 +712,8 @@ void la_RegisterAnimationResources(){
     laSubGroupExtraFunctions(p,0,0,0,0,laget_AnimationActionHolderCategory);
     at->UiDefine=laui_AnimationNewAction;
 
+    at=laCreateOperatorType("LA_animation_remove_action", "Remove Action", "Remove this action",OPCHK_AnimationRemoveAction,0,0,OPINV_AnimationRemoveAction,OPMOD_AnimationRemoveAction,U'🞫',0);
+
     at=laCreateOperatorType("LA_animation_select_action", "Select Action", "Select an action",0,0,0,OPINV_AnimationSelectAction,OPMOD_FinishOnData,U'⯆',0);
     at->UiDefine=laui_AnimationSelectAction;
 

+ 1 - 1
la_data.c

@@ -2024,7 +2024,7 @@ void* laGetRaw(laPropPack *pp, int* r_size, int* return_is_a_copy){
 }
 int laSetRaw(laPropPack *pp, void* data, int _size){
     if (pp->LastPs->p->PropertyType == LA_PROP_RAW){ laRawProp* rp=pp->LastPs->p;
-        if(rp->RawSet){ rp->RawSet(pp->LastPs->UseInstance, data, _size); return; }
+        if(rp->RawSet){ rp->RawSet(pp->LastPs->UseInstance, data, _size); return 1; }
         if(rp->Base.OffsetIsPointer){ void** target=(((char*)pp->LastPs->UseInstance)+rp->Base.Offset); if(*target) free(*target);
             void* newd=_size?calloc(1,_size):0; if(_size)memcpy(newd, data, _size); (*target)=newd;
         }

+ 9 - 6
la_data.h

@@ -645,7 +645,7 @@ STRUCTURE(laDiffPost){
 NEED_STRUCTURE(laAction);
 STRUCTURE(laActionHolder){
     laListItem Item;
-    void* Instance; int ListHandleOffset;
+    void* Instance; int ActionOffset,PropOffset;
     laPropContainer* Container;
     laSafeString* Name;
     laSafeString* CategoryTitle;
@@ -660,7 +660,6 @@ STRUCTURE(laAnimation){
     laAction*    CurrentAction;
     laListHandle ActionHolders;
     laListHandle ActionHolderPaths;
-    laListHandle Props;
     real PlayHead;
     int PlayStatus;
     laTimeRecorder TimeOrigin;
@@ -668,7 +667,7 @@ STRUCTURE(laAnimation){
 STRUCTURE(laAction){
     laListItem Item;
     laSafeString* Name;
-    void* HolderInstance; laPropContainer* HolderContainer; // only for verification
+    void* HolderInstance; laActionHolder* Holder; // for verification
     laListHandle Channels;
     int FrameCount;
     real Length;
@@ -695,11 +694,11 @@ STRUCTURE(laActionKey){
     laListItem Item;
     int At,OriginaAt;
     int Selected;
-    uint64_t Data; // variable size depending on property;
+    int DataSize;
+    void* Data;
 };
 
-#define LA_ACTION_FRAME(aa)\
-    (aa->PlayHead*(real)aa->FrameCount)
+#define LA_ACTION_FRAME(aa) (((aa)->PlayHead+FLT_EPSILON)*(real)((aa)->FrameCount))
 
 /* Only little endian are supported right now */
 #define LA_UDF_IDENTIFIER "UDF_LE"
@@ -1029,6 +1028,10 @@ void laRedo();
 #define LA_ANIMATION_STATUS_PLAY_FWD 1
 #define LA_ANIMATION_STATUS_PLAY_REV 2
 
+int la_GetKeyablePropertyStorageSize(laProp* p);
+
+void la_AnimationEvaluateActions(int ClampOffsets);
+
 laAction* laAnimiationNewAction(laActionHolder* ah, char* Name);
 laActionChannel* laAnimationEnsureChannel(laAction* aa, void* hyper1, laProp* p);
 laActionChannel* laAnimationEnsureFrame(laActionChannel* ac, int frame);

+ 5 - 0
la_interface.h

@@ -2241,6 +2241,10 @@ laOperatorType *laCreateOperatorType(const char *ID, const char *Name, const cha
 
 laPropContainer* laDefineOperatorProps(laOperatorType* ot, int HyperLevel);
 
+#define LA_VERIFY_THIS_TYPE(this,pc,pc_identifier) \
+    ((this)&&(this)->EndInstance&& \
+    (this)->LastPs->p->PropertyType==LA_PROP_SUB&& \
+    ((pc)=la_EnsureSubTarget((this)->LastPs->p,(this)->EndInstance))&&strSame((pc)->Identifier,pc_identifier))
 
 laNodeOutSocket* laCreateOutSocket(void* NodeParentOptional, char* label, int DataType);
 laNodeInSocket* laCreateInSocket(char* label, int DataType);
@@ -2316,6 +2320,7 @@ void laui_DefaultMenuBarActual(laUiList *uil, laPropPack *pp, laPropPack *actins
 void laui_DefaultMenuBar(laWindow *w);
 void laui_DefaultSubWindowMenuBar(laWindow *w);
 void laui_DefaultPropUiDefine(laUiList *uil, laPropPack *This, laPropPack *OperatorProps, laColumn *UNUSED, int context);;
+void laui_PropOperatorUiDefine(laUiList *uil, laPropPack *This, laPropPack *Unused, laColumn *UNUSED, int context);
 
 int laCopyToClipboard(unsigned char * text);
 

+ 7 - 11
la_kernel.c

@@ -4933,10 +4933,8 @@ void la_AddRowNode(laRowInfo* ri, laUiItem* ui, laBoxedTheme* bt, int H){
     rn->H=H;
     rn->Expand=ui->Expand;
     lstAppendItem(&ri->Elements, rn);
-    if(ri->LastNoHeight){
-        ri->LastNoHeight=0; rn->UseLast=1; return; }
-    if(ui->Flags&LA_UI_FLAGS_UNDERNEATH){
-        ri->LastNoHeight=1; };
+    if(ri->LastNoHeight){ ri->LastNoHeight=0; rn->UseLast=1; return; }
+    if(ui->Flags&LA_UI_FLAGS_UNDERNEATH){ ri->LastNoHeight=1; };
 
     if(!ri->UnitMinW){ri->UnitMinW=LA_RH;}
     ri->TotalPadding += (bt->LP+bt->RP);
@@ -5001,10 +4999,8 @@ int la_CalculateRowExpand(laRowInfo* ri, laUiItem* ui_end, int WaitAnimation){
         }
         if(ui->Type==_LA_UI_NODE_SOCKET){ la_RecordSocketRuntimePosition(ui); }
     }
-    ui_end->TB = ui_end->Flags&LA_UI_FLAGS_UNDERNEATH?ui_end->TU:ri->MaxB;
-    while(rn=lstPopItem(&ri->Elements)){
-        FreeMem(rn);
-    }
+    ui_end->TB = ui_end->Flags&LA_UI_FLAGS_UNDERNEATH?ri->U:ri->MaxB;
+    while(rn=lstPopItem(&ri->Elements)){ FreeMem(rn); }
     memset(ri, 0, sizeof(laRowInfo));
 }
 void la_RecordSocketRuntimePosition(laUiItem* ui){
@@ -5357,7 +5353,7 @@ int la_UpdateUiListRecursive(laUiList *uil, int U, int L, int R, int B, int Fast
                     SubB = la_UpdateUiListRecursive(ui->Page,
                         ui->TB + (ui->State == LA_UI_ACTIVE ? 0 : LA_RH)+(NoDecal?0:bt->TM), ui->TL+(NoDecal?0:bt->LM), ui->TR-(NoDecal?0:bt->RM)-scrollw, B, Fast, ParentPanel);
                     ui->TB = (ui->Page->HeightCoeff > 0 ? ui->TU + ui->Page->HeightCoeff * LA_RH :
-                            (ui->Page->HeightCoeff < 0 ? B + (ui->Page->HeightCoeff+1) * LA_RH + (_PB+_PT) : SubB)) + (NoDecal?0:bt->BM);
+                            (ui->Page->HeightCoeff < 0 ? B + (ui->Page->HeightCoeff+1) * LA_RH + (_PB+_PT)+(ui->Page->HeightCoeff==-2?_PB:0) : SubB)) + (NoDecal?0:bt->BM);
                     int subh = ui->TB-ui->TU-LA_RH-bt->TM-bt->BM;
                     if((ui->Page->TR>ui->TR-(NoDecal?0:bt->RM) && (!ui->Page->ScrollerShownH)) ||
                         (ui->Page->TR<=ui->TR-(NoDecal?0:bt->RM)  && ui->Page->ScrollerShownH)){
@@ -5365,7 +5361,7 @@ int la_UpdateUiListRecursive(laUiList *uil, int U, int L, int R, int B, int Fast
                     }
                     if(ui->Page->AllowScale){ui->Page->ScrollerShownH=1;}
                     if(ui->Page->ScrollerShownH){subh-=LA_SCROLL_W-bt->BM;}
-                    if((GB && ui->TB >= GB)||first_in){ printf("a\n"); ui->Page->PanY=(SubB-ui->TB-bt->BM); if(ui->Page->PanY<0)ui->Page->PanY=0; }
+                    if((GB && ui->TB >= GB)||first_in){ ui->Page->PanY=(SubB-ui->TB-bt->BM); if(ui->Page->PanY<0)ui->Page->PanY=0; }
                     if(ui->Page->HeightCoeff){
                         if((subh<ui->Page->TB-ui->Page->TU && (!ui->Page->ScrollerShownV)) ||
                             (subh>=ui->Page->TB-ui->Page->TU && ui->Page->ScrollerShownV)){
@@ -5393,7 +5389,7 @@ int la_UpdateUiListRecursive(laUiList *uil, int U, int L, int R, int B, int Fast
             }else ui->TB = ui->TU + H;
         }
 
-        if (ui->TB > Lowest) Lowest = ui->TB + (bt ? (NoGap?_PB:bt->BP) : 0);
+        if (ui->TB > Lowest && (!NoHeight)) Lowest = ui->TB + (bt ? (NoGap?_PB:bt->BP) : 0);
         if(!RowMode){
             if(!NoHeight) la_CalcUiItemInfluence(&uil->Columns, ui);
         }else{

+ 4 - 3
la_tns.h

@@ -547,8 +547,6 @@ STRUCTURE(tnsObject){
 
     laRackPageCollection* Drivers; // rack
 
-    laListHandle Actions;
-
     tnsEvaluateData Evaluated; // runtime
     tnsEvaluateData EvaluatedPlay; int LuaCacheID;
     tnsObject* PlayDuplicate;
@@ -593,8 +591,11 @@ STRUCTURE(tnsMaterial){
 STRUCTURE(tnsRootObject){
     tnsObject Base;
 
-    int Is2D;
+    laListHandle Actions;
+    laListHandle ActionProps;
+
     tnsObject* ActiveCamera;
+    int Is2D;
 };
 
 #define TNS_INSTANCER_HOOK_TL 1

+ 1 - 0
la_tns_kernel.c

@@ -3601,6 +3601,7 @@ int tnsSizeOfObject(tnsObject* o){
     case TNS_OBJECT_MESH: return sizeof(tnsMeshObject);
     case TNS_OBJECT_CAMERA: return sizeof(tnsCamera);
     case TNS_OBJECT_LIGHT: return sizeof(tnsLight);
+    case TNS_OBJECT_SHAPE: return sizeof(tnsShapeObject);
     default: return sizeof(tnsObject);
     }
 }

+ 1 - 0
la_util.h

@@ -670,6 +670,7 @@ void strSafePrintV(laSafeString **ss, char *Format, va_list arg);
 
 void strSafeDump();
 
+#define SSTR(str) (((str) && (str)->Ptr)?(str)->Ptr:"")
 
 void strBeginEdit(laStringEdit** se, char * FullStr);
 char* strGetEditString(laStringEdit *se, int SelectionOnly);

+ 1 - 1
resources/la_nodes_basic.c

@@ -1210,7 +1210,7 @@ void la_RegisterInputMapperOperators(){
     laCreateOperatorType("LA_delete_node", "Delete Node", "Delete this node",0,0,0,OPINV_DeleteNode,0,0,0);
     laCreateOperatorType("LA_move_rack", "Move Rack", "Move this rack",0,0,0,OPINV_MoveRack,0,0,0);
     laCreateOperatorType("LA_insert_rack", "Insert Rack", "Insert a new rack",0,0,0,OPINV_InsertRack,0,0,0);
-    at=laCreateOperatorType("LA_delete_rack", "Delete Rack", "Delete a rack",0,0,0,OPINV_DeleteRack,OPMOD_FinishOnData,U'',0);
+    at=laCreateOperatorType("LA_delete_rack", "Delete Rack", "Delete a rack",0,0,0,OPINV_DeleteRack,OPMOD_FinishOnData,U'🞫',0);
     at->UiDefine=laui_DeleteRack;
     laCreateOperatorType("LA_remove_input_mapping_page", "Remove Page", "Remove a input mapper page",OPCHK_RemoveInputMappingPage,0,0,OPINV_RemoveInputMappingPage,OPMOD_FinishOnData,L'🗴',0)
         ->UiDefine=laui_RemoveInputMappingPage;

+ 6 - 6
resources/la_operators.c

@@ -895,7 +895,7 @@ int OPINV_PropInsertKey(laOperator *a, laEvent *e){
         laEnableMessagePanel(a,0,"Error","Action holder info missing.\nThis should not happen.",e->x,e->y,100,e);
     }elif(err==2){
         char msg[1024]; char _id[128]="unknown container",*id=_id,_idc[128]="unknown object",*idc=_idc;
-        laTryGetInstanceIdentifier(aa->HolderInstance,aa->HolderContainer,_id,&id);
+        laTryGetInstanceIdentifier(aa->HolderInstance,aa->Holder->Container,_id,&id);
         laTryGetInstanceIdentifier(a->This->LastPs->UseInstance,a->This->LastPs->p->Container,_idc,&idc);
         sprintf(msg,"Can't insert key frame into current action.\n\"%s\" doesn't come from an action under \"%s\".",idc,id);
         laEnableMessagePanel(a,0,"Error",msg,e->x,e->y,100,e);
@@ -2034,7 +2034,7 @@ void la_RegisterBuiltinOperators(){
     laCreateOperatorType("LA_block_tear_off_panel", "Tear Off", "Tear off current panel in the block",
                           OPCHK_BlockHasMorePanels, 0, 0, OPINV_BlockTearOffPanel, 0, U'🗗', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_block_close_panel", "Close Panel", "Close current panel in the block",
-                          OPCHK_BlockHasMorePanels, 0, 0, OPINV_BlockClosePanel, OPMOD_BlockClosePanel, U'', LA_ACTUATOR_SYSTEM);
+                          OPCHK_BlockHasMorePanels, 0, 0, OPINV_BlockClosePanel, OPMOD_BlockClosePanel, U'🞫', LA_ACTUATOR_SYSTEM);
 
     laCreateOperatorType("LA_canvas_ui_maximize", "Maximize", "Maximize this UI item", 0, 0, 0, OPINV_CanvasUiMaximize, 0, U'⮼', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_hide_menu_bar", "Hide Menu Bar", "Hide menu bar of the window", 0, 0, 0, OPINV_HideMenuBar, 0, U'⯅', LA_ACTUATOR_SYSTEM);
@@ -2044,7 +2044,7 @@ void la_RegisterBuiltinOperators(){
     laCreateOperatorType("LA_new_layout", "New Layout", "Create a new layout in the window",
                           0, 0, 0, OPINV_NewLayout, 0, U'🞦', LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
     laCreateOperatorType("LA_remove_layout", "Remove Layout", "Remove current layout in the window",
-                          OPCHK_RemoveLayout, 0, 0, OPINV_RemoveLayout, 0, U'', LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
+                          OPCHK_RemoveLayout, 0, 0, OPINV_RemoveLayout, 0, U'🞫', LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
     laCreateOperatorType("LA_panel_operator", "Panel Operator", "Handle Events On The Panel Level",
                           0, 0, OPEXT_Panel, OPINV_Panel, OPMOD_Panel, U'🖦', LA_EXTRA_TO_PANEL | LA_ACTUATOR_SYSTEM | LA_ACTUATOR_HIDDEN);
     laCreateOperatorType("LA_modal_panel_operator", "Modal Panel Operator", "Handle Events On Modal Panels Like Yes-No Boxes",
@@ -2150,17 +2150,17 @@ void la_RegisterBuiltinOperators(){
     laCreateOperatorType("LA_add_resource_folder", "Add Resource Folder", "Add a resource folder entry for searching UDF references",
                                0, 0, 0, OPINV_AddResourceFolder, 0, U'🞧', LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_remove_resource_folder", "Remove Resource Folder", "Remove a resource folder entry",
-                               0, 0, 0, OPINV_RemoveResourceFolder, 0, U'', LA_ACTUATOR_SYSTEM);
+                               0, 0, 0, OPINV_RemoveResourceFolder, 0, U'🞫', LA_ACTUATOR_SYSTEM);
 
     laCreateOperatorType("LA_save_user_preferences", "Save Preferences", "Save user preferences", 0, 0, 0, OPINV_SaveUserPreferences, 0, 0, LA_ACTUATOR_SYSTEM);
     laCreateOperatorType("LA_restore_factory", "Restore Factory Settings", "Restore factory settings", 0, 0, 0, OPINV_RestoreFactorySettings, OPMOD_RestoreFactorySettings, 0, LA_ACTUATOR_SYSTEM);
 
     laCreateOperatorType("LA_confirm", "Confirm", "Confirm The Statement", 0, 0, 0, OPINV_DoNothing, 0, U'✔', LA_ACTUATOR_SYSTEM)
         ->ExtraInstructions = "feedback=CONFIRM;";
-    laCreateOperatorType("LA_cancel", "Cancel", "Ignore The Statement", 0, 0, 0, OPINV_DoNothing, 0, U'', LA_ACTUATOR_SYSTEM)
+    laCreateOperatorType("LA_cancel", "Cancel", "Ignore The Statement", 0, 0, 0, OPINV_DoNothing, 0, U'🞫', LA_ACTUATOR_SYSTEM)
         ->ExtraInstructions = "feedback=CANCEL;";
     laCreateOperatorType("LA_pure_yes_no", "Yes Or No", "Show Yes Or No Box", 0, 0, 0, OPINV_PureYesNo, 0, U'❓', LA_ACTUATOR_SYSTEM);
     
     laCreateOperatorType("LA_delete_theme", "Delete Theme", "Delete a theme",
-                               0, 0, 0, OPINV_DeleteTheme, 0, U'', LA_ACTUATOR_SYSTEM);
+                               0, 0, 0, OPINV_DeleteTheme, 0, U'🞫', LA_ACTUATOR_SYSTEM);
 }

+ 37 - 9
resources/la_properties.c

@@ -944,10 +944,34 @@ laPropContainer* tnsget_ObjectType(tnsObject* o){
 void laget_AnimationActionHolderCategory(void* a_unused, laActionHolder* ah, char* copy, char** ptr){
     if(ah->CategoryTitle&&ah->CategoryTitle->Ptr) *ptr=ah->CategoryTitle->Ptr;
 }
-
 int laget_AnimationActionCurrentFrame(laAction* aa){
     return LA_ACTION_FRAME(aa);
 }
+void laget_AnimationPropStr(laActionProp* ap, char* str, char** here){
+    sprintf(str,"%s.%s",ap->Prop->Container->Identifier,ap->Prop->Identifier);
+}
+void laread_AnimationPropStr(laActionProp* ap, char* str){
+    if(!str || !str[0]) return;
+    laStringSplitor* ss=strSplitPath(str,0); if(ss->NumberParts!=2) goto anim_set_prop_str_cleanup;
+    laStringPart* sp1=ss->parts.pFirst,*sp2=ss->parts.pLast;
+    laPropContainer* pc=la_ContainerLookup(sp1->Content); if(!pc) goto anim_set_prop_str_cleanup;
+    laProp* p=la_PropLookup(&pc->Props,sp2->Content); if(!p) goto anim_set_prop_str_cleanup;
+    ap->Prop=p;
+anim_set_prop_str_cleanup:
+    strDestroyStringSplitor(&ss);
+}
+void* lagetraw_ActionKeyData(laActionKey* ak, int* r_size, int* ret_is_copy){
+    *r_size=ak->DataSize; *ret_is_copy=1;
+    void* data=malloc(ak->DataSize); memcpy(data,ak->Data,ak->DataSize);
+    return data;
+}
+void lasetraw_ActionKeyData(laActionKey* ak, void* data, int DataSize){
+    ak->DataSize=DataSize; ak->Data=memAcquireSimple(DataSize); memcpy(ak->Data,data,DataSize);
+}
+void laset_AnimationPlayHead(void* unused,real data){
+    if(data<0){ data=0; }
+    laAnimationSetPlayHead(data); la_AnimationEvaluateActions(1); laNotifyUsers("la.animation");
+}
 
 int laaction_VerifyRootObject(void* Parent, laPropContainer* ParentType, void* Child, laPropContainer* ChildType){
     if(ParentType!=ChildType) return 0;
@@ -1182,7 +1206,8 @@ void la_RegisterTNSProps(){
         TNS_PC_OBJECT_ROOT=p;
         laAddStringProperty(p, "name", "Object Name", "The Name Of The Object", 0,0,0,0,1, offsetof(tnsObject, Name), 0,0,0,0,LA_AS_IDENTIFIER);
         laAddSubGroup(p, "base", "Base", "Object base", "tns_object",0,0,0,0,0,0,0,0,0,0,0,LA_UDF_LOCAL);
-        laAddSubGroup(p, "__actions__", "Actions", "Animation actions", "la_animation_action",0,0,0,-1,0,laget_CurrentAnimationAction,0,laset_CurrentAnimationAction,0,0,offsetof(tnsObject, Actions), 0);
+        laAddSubGroup(p, "__actions__", "Actions", "Animation actions", "la_animation_action",0,0,0,-1,0,laget_CurrentAnimationAction,0,laset_CurrentAnimationAction,0,0,offsetof(tnsRootObject, Actions), 0);
+        laAddSubGroup(p, "__action_props__", "Action Props", "Action properties", "la_animation_prop",0,0,0,-1,0,0,0,0,0,0,offsetof(tnsRootObject, ActionProps), 0);
         laAddSubGroup(p, "active_camera", "Active Camera", "Active camera of this root object", "tns_object",0,0,0,offsetof(tnsRootObject, ActiveCamera),0,0,0,0,0,0,0,LA_UDF_REFER|LA_READ_ONLY);
         ep = laAddEnumProperty(p, "is_2d", "Is 2D", "Is 2D root object", 0,0,0,0,0,offsetof(tnsRootObject, Is2D), 0,tnsset_RootObjectIs2D,0,0,0,0,0,0,0,0);{
             laAddEnumItemAs(ep, "3D", "3D", "Root object is in 3D", 0, 0);
@@ -1346,6 +1371,7 @@ void la_RegisterInternalProps(){
             p = laDefineRoot();
 
             laAddPropertyContainer("any_pointer", "Any Pointer", "A pointer that is not exposed to access", 0,0,sizeof(void*), 0,0,LA_PROP_OTHER_ALLOC);
+            laAddPropertyContainer("any_pointer_h2", "Any Pointer (h2)", "A pointer that is not exposed to access", 0,0,sizeof(void*), 0,0,2);
             
             laAddSubGroup(p, "tns","TNS", "TNS Kernel Main Structure", "tns_main",0,0,0,-1, tnsget_TnsMain, 0,0,0,0,0,0,LA_UDF_SINGLE | LA_UDF_LOCAL);
 
@@ -1517,7 +1543,7 @@ void la_RegisterInternalProps(){
 
         p = laAddPropertyContainer("la_resource_folder", "Resource Folder", "A resource folder to search for UDF references.", 0,laui_ResourceFolderItem, sizeof(laResourceFolder), 0,0,1);{
             laAddStringProperty(p, "path", "Path", "Path", 0,0,0,0,1, offsetof(laResourceFolder, Path), 0,0,laset_ResourcePath, 0,0);
-            laAddOperatorProperty(p, "remove", "Remove", "Remove this resource folder entry", "LA_remove_resource_folder", U'', 0);
+            laAddOperatorProperty(p, "remove", "Remove", "Remove this resource folder entry", "LA_remove_resource_folder", U'🞫', 0);
         }
 
         p = laAddPropertyContainer("managed_udf", "Managed UDF", "Managed UDF files", U'🖹', laui_ManagedUDFItem, sizeof(laManagedUDF), 0,0,0);{
@@ -1726,12 +1752,12 @@ void la_RegisterInternalProps(){
             laAddIntProperty(p, "mode", "Mode", "Normal/Floating/Static/Modal etc.", 0,0,0,0,0,1, 0,0,offsetof(laPanel, Mode), 0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
             ep = laAddEnumProperty(p, "is_menu_panel", "Is Menu Panel", "Is Menu Panel", 0,0,0,0,0,offsetof(laPanel, IsMenuPanel), 0,0,0,0,0,0,0,0,0,0);{
                 ep->ElementBytes = 1;
-                laAddEnumItem(ep, "false", "False", "Not A Menu Panel", U'');
+                laAddEnumItem(ep, "false", "False", "Not A Menu Panel", U'🞫');
                 laAddEnumItem(ep, "true", "IsTrue", "Is A Menu Panel", U'🗩');
             }
             laAddOperatorProperty(p, "hide", "Hide", "Hide this panel", "LA_hide_panel", U'🗕', 0);
             laAddOperatorProperty(p, "dock", "Dock", "Dock this panel", "LA_dock_panel", U'🗖', 0);
-            laAddOperatorProperty(p, "close", "Close", "Close this panel", "LA_block_close_panel", U'', 0);
+            laAddOperatorProperty(p, "close", "Close", "Close this panel", "LA_block_close_panel", U'🞫', 0);
             //laAddSubGroup(p, "Detached Props", "Detached Props", "detached_prop",0,0,0,0,0,0,0,0,0,0,0,0,offsetof(laPanel, PropLinkContainer->Props), 0);
             laAddSubGroup(p, "uil","Ui List", "Panel Main Ui List", "ui_list",0,0,0,offsetof(laPanel, UI), 0,0,0,0,0,0,0,LA_UDF_IGNORE);
             laAddSubGroup(p, "title_uil","Title Ui List", "Panel Title Ui List", "ui_list",0,0,0, offsetof(laPanel, TitleBar), 0,0,0,0,0,0,0,LA_UDF_IGNORE);
@@ -1880,7 +1906,7 @@ void la_RegisterInternalProps(){
 
             laAddSubGroup(p, "current_action", "Current Action", "Current action","la_animation_action",0,0,0,offsetof(laAnimation,CurrentAction),0,0,0,0,0,0,0,LA_UDF_REFER|LA_READ_ONLY);
             
-            laAddFloatProperty(p, "play_head","Play Head","Animation viewer global playhead",0,0,"s",0,0,0.1,0,0,offsetof(laAnimation,PlayHead),0,0,0,0,0,0,0,0,0,0,0);
+            laAddFloatProperty(p, "play_head","Play Head","Animation viewer global playhead",0,0,"s",0,0,0.01,0,0,offsetof(laAnimation,PlayHead),0,laset_AnimationPlayHead,0,0,0,0,0,0,0,0,0);
             ep=laAddEnumProperty(p, "play_status", "Play Status", "Animation viewer global play status", 0,0,0,0,0,offsetof(laAnimation, PlayStatus),0,0,0,0,0,0,0,0,0,0);
             laAddEnumItemAs(ep, "PAUSED", "Paused", "Animation is playing", LA_ANIMATION_STATUS_PAUSED, U'⏸');
             laAddEnumItemAs(ep, "PLAY_FWD", "Playing", "File data is untouched", LA_ANIMATION_STATUS_PLAY_FWD,U'▶');
@@ -1911,6 +1937,7 @@ void la_RegisterInternalProps(){
                 laAddEnumItemAs(ep, "REPLACE", "Replace", "Replace previously set values", LA_ANIMATION_MIX_REPLACE,0);
                 laAddEnumItemAs(ep, "ADD", "Add", "Add on top of previously set values", LA_ANIMATION_MIX_ADD,0);
             }
+            laAddOperatorProperty(p,"remove","Remove","Remove this action","LA_animation_remove_action",L'🞫',0);
         }
         p = laAddPropertyContainer("la_animation_channel", "Channel", "Action channel",0,0,sizeof(laActionChannel),0,0,1);{
             laAddSubGroup(p, "keys", "Keys", "Key Frames", "la_animation_key",0,0,0,-1,0,0,0,0,0,0,offsetof(laActionChannel, Keys),0);
@@ -1918,13 +1945,14 @@ void la_RegisterInternalProps(){
         }
         p = laAddPropertyContainer("la_animation_prop", "Property", "Animation property",0,0,sizeof(laActionProp),0,0,1);{
             laAddIntProperty(p, "data_size","Data Size","Data size of the channel",0,0,0,0,0,0,0,0,offsetof(laActionProp,DataSize),0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
-            laAddSubGroup(p, "prop", "Property", "Property of this channel", "property_item",0,0,0,offsetof(laActionProp, Prop), 0,0,0,0,0,0,0,LA_UDF_REFER | LA_UDF_IGNORE);
-            // XXX: Prop needs a string "container.prop" to be able to be able to r/w.
+            laAddStringProperty(p,"prop_str","Property String","Property of this channel",0,0,0,0,0,0,0,laget_AnimationPropStr,0,laread_AnimationPropStr,LA_READ_ONLY);
+            laAddStringProperty(p,"cached_str","Cached String","Property name of this channel",0,0,0,0,1,offsetof(laActionProp,CachedName),0,0,0,0,LA_READ_ONLY|LA_AS_IDENTIFIER);
             laAddSubGroup(p, "for", "For", "Target data block", "any_pointer",0,0,0,offsetof(laActionProp, For), 0,0,0,0,0,0,0,LA_UDF_REFER);
+            laAddSubGroup(p, "for_h2", "For H2", "Target data block (hyper 2)", "any_pointer_h2",0,0,0,offsetof(laActionProp, For), 0,0,0,0,0,0,0,LA_UDF_REFER);
         }
         p = laAddPropertyContainer("la_animation_key", "key", "Action channel",0,0,sizeof(laActionKey),0,0,1);{
             laAddIntProperty(p, "at","At","Frame number of this key frame",0,0,0,0,0,0,0,0,offsetof(laActionKey,At),0,0,0,0,0,0,0,0,0,0,0);
-            // XXX: RAW for r/w.
+            laAddRawProperty(p,"data","Data","Data of this key frame",0,0,lagetraw_ActionKeyData,lasetraw_ActionKeyData,0);
         }
         p = laAddPropertyContainer("la_animation_action_holder", "Action Holder", "Action holder",0,0,sizeof(laActionHolder),0,0,1);{
             laAddStringProperty(p,"name","Name","Name of the action",0,0,0,0,1,offsetof(laActionHolder,Name),0,0,0,0,LA_AS_IDENTIFIER);

+ 32 - 15
resources/la_templates.c

@@ -116,11 +116,26 @@ void laui_DefaultPropUiDefine(laUiList *uil, laPropPack *This, laPropPack *Opera
     if (!This || !This->LastPs) return;
 
     c = laFirstColumn(uil); gp = This->LastPs->p;
-    for (p = gp->SubProp->Props.pFirst; p; p = p->Item.pNext){
+    laPropContainer* pc=la_EnsureSubTarget(gp,This->EndInstance);
+    laShowLabel(uil,c,gp->PropertyType==LA_PROP_SUB?pc->Name:gp->Name,0,0)->Flags|=LA_UI_FLAGS_DISABLED; 
+    for (p = pc->Props.pFirst; p; p = p->Item.pNext){
         //la_ShowGeneralPropItem(uil, c, This, gp, p, 0, 0, 0);
         laShowItem(uil, c, This, p->Identifier);
     }
 }
+void laui_PropOperatorUiDefine(laUiList *uil, laPropPack *This, laPropPack *Unused, laColumn *UNUSED, int context){
+    laColumn *c; laProp *p, *gp;
+
+    if (This && This->LastPs){
+        c = laFirstColumn(uil); gp = This->LastPs->p;
+        laPropContainer* pc=la_EnsureSubTarget(gp,This->EndInstance);
+        laShowLabel(uil,c,gp->PropertyType==LA_PROP_SUB?pc->Name:gp->Name,0,0)->Flags|=LA_UI_FLAGS_DISABLED; 
+        for (p = pc->Props.pFirst; p; p = p->Item.pNext){
+            if(p->PropertyType!=LA_PROP_OPERATOR) continue;
+            laShowItem(uil, c, This, p->Identifier);
+        }
+    }
+}
 void laui_StringPropUiDefine(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
     laColumn *c; laProp *p, *gp;
 
@@ -1701,8 +1716,8 @@ void laui_AnimationActions(laUiList *uil, laPropPack *This, laPropPack *Extra, l
     laColumn* c=laFirstColumn(uil),*cl, *cr; laSplitColumn(uil,c,0.5); cl=laLeftColumn(c,0); cr=laRightColumn(c,5);
     laUiItem* b,*ui;
 
-    ui=laShowItemFull(uil,cl,0,"la.animation.play_head",LA_WIDGET_INT_PLAIN,0,0,0);
-    ui->Flags|=LA_UI_FLAGS_NO_LABEL; ui->Expand=1;
+    ui=laShowItemFull(uil,cl,0,"la.animation.play_head",0,0,0,0);
+    ui->Flags|=LA_UI_FLAGS_NO_LABEL|LA_TEXT_ALIGN_CENTER; ui->Expand=1;
 
     laUiItem* row=laBeginRow(uil,cr,0,0);
     laShowItemFull(uil,cr,0,"LA_animation_reset_time",0,0,0,0)->Flags|=LA_UI_FLAGS_ICON|LA_UI_FLAGS_EXIT_WHEN_TRIGGERED;
@@ -1718,14 +1733,14 @@ void laui_AnimationActions(laUiList *uil, laPropPack *This, laPropPack *Extra, l
     laEndRow(uil,row);
 
     ui=laMakeEmptyGroup(uil,c,"Use Format",0); laUiList* gu=ui->Page; laColumn* gc=laFirstColumn(gu);
-    gu->HeightCoeff=-3; //ui->Flags|=LA_UI_FLAGS_NO_DECAL;
+    gu->HeightCoeff=-2; //ui->Flags|=LA_UI_FLAGS_NO_DECAL;
     for(laActionHolderPath* ahp=MAIN.Animation->ActionHolderPaths.pFirst;ahp;ahp=ahp->Item.pNext){
         laShowLabel(gu,gc,ahp->PP.LastPs->p->Name,0,0)->Flags|=LA_UI_FLAGS_DISABLED|LA_TEXT_MONO;
         laShowItemFull(gu,gc,0,ahp->OriginalPath,LA_WIDGET_COLLECTION,"feedback=NONE",laui_AnimationActionHolder,0)->Flags|=LA_UI_FLAGS_NO_DECAL|LA_UI_FLAGS_NO_GAP;
     }
 
     row=laBeginRow(uil,cl,0,0);
-    laShowItemFull(uil,cl,0,"LA_animation_new_action",0,"text=New",0,0);//->Flags|=LA_UI_FLAGS_ICON;
+    laShowItemFull(uil,cl,0,"LA_animation_new_action",0,"text=New",0,0);
     laEndRow(uil,row);
 }
 void laui_AnimationActionChannels(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
@@ -1749,25 +1764,27 @@ void laui_AnimationActionChannels(laUiList *uil, laPropPack *This, laPropPack *E
     laEndRow(uil,row);
 
     b2=laOnConditionThat(uil,c,laPropExpression(0,"la.animation.current_action"));{
+        laUiItem* ca=laShowInvisibleItem(uil,c,0,"la.animation.current_action");
         row=laBeginRow(uil,crl,0,0);
-        ui=laShowItemFull(uil,crl,0,"la.animation.current_action.play_head",LA_WIDGET_VALUE_METER,0,0,0);
+        ui=laShowItemFull(uil,crl,&ca->PP,"play_head",LA_WIDGET_VALUE_METER,0,0,0);
         ui->Flags|=LA_UI_FLAGS_NO_LABEL|LA_UI_FLAGS_UNDERNEATH; ui->Expand=1;
-        laShowItem(uil,crl,0,"la.animation.current_action.current_frame")->Flags|=LA_UI_FLAGS_NO_LABEL|LA_UI_FLAGS_NO_DECAL|LA_TEXT_ALIGN_CENTER;
+        laShowItem(uil,crl,&ca->PP,"current_frame")->Flags|=LA_UI_FLAGS_NO_LABEL|LA_UI_FLAGS_NO_DECAL|LA_TEXT_ALIGN_CENTER;
         laShowSeparator(uil,crl);
-        laShowItem(uil,crl,0,"la.animation.current_action.frame_count")->Flags|=LA_TEXT_ALIGN_CENTER;
-        laShowItem(uil,crl,0,"la.animation.current_action.length");
+        laShowItem(uil,crl,&ca->PP,"frame_count")->Flags|=LA_TEXT_ALIGN_CENTER;
+        laShowItem(uil,crl,&ca->PP,"length");
         laEndRow(uil,row);
         b3=laBeginRow(uil,crr,0,0);
-        laShowItem(uil,crr,0,"la.animation.current_action.name")->Expand=1;
+        laShowItem(uil,crr,&ca->PP,"name")->Expand=1;
         laShowItemFull(uil,crr,0,"LA_animation_select_action",0,0,0,0)->Flags|=LA_UI_FLAGS_ICON;
         laEndRow(uil,b3);
-        b=laOnConditionToggle(uil,clr,0,0,0,0,0);{
+        b=laOnConditionToggle(uil,clr,0,0,0,0,0);{ strSafeSet(&b->ExtraInstructions,"text=☰");
+            laShowItem(uil,cl,&ca->PP,"remove");
             row=laBeginRow(uil,cr,0,0);
-            laShowItem(uil,cr,0,"la.animation.current_action.mix_mode")->Flags|=LA_UI_FLAGS_EXPAND;
-            laShowItem(uil,cr,0,"la.animation.current_action.play_mode")->Flags|=LA_UI_FLAGS_EXPAND;
+            laShowItem(uil,cr,&ca->PP,"mix_mode")->Flags|=LA_UI_FLAGS_EXPAND;
+            laShowItem(uil,cr,&ca->PP,"play_mode")->Flags|=LA_UI_FLAGS_EXPAND;
             laShowSeparator(uil,cr);
-            laShowItem(uil,cr,0,"la.animation.current_action.solo")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
-            laShowItem(uil,cr,0,"la.animation.current_action.mute")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+            laShowItem(uil,cr,&ca->PP,"solo")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+            laShowItem(uil,cr,&ca->PP,"mute")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
             laEndRow(uil,row);
         }laEndCondition(uil,b);
     }laElse(uil,b2);{

+ 1 - 1
resources/la_widgets.c

@@ -2837,7 +2837,7 @@ int OPMOD_Collection(laOperator *a, laEvent *e) {
         ui->PP.EndInstance = uil->Instance;
         lx = e->x, ly = e->y;
         laLocalToWindow(a, a->ToPanel, &lx, &ly);
-        laEnablePropertyPanel(a->ToPanel, a, 0, 0, laui_IdentifierOnly, &ui->PP,
+        laEnablePropertyPanel(a->ToPanel, a, 0, 0, laui_PropOperatorUiDefine, &ui->PP,
                               lx, lx, ly, 0, 0, e);
       }
     }

+ 2 - 2
resources/la_widgets_viewers.c

@@ -1091,8 +1091,8 @@ void la_RegisterUiTypesViewerWidgets(){
         p = laAddEnumProperty(pc, "line_width_warning", "Line Width Warnning", "Show whether line width is acceptable by hardware", 0, 0, 0, 0, 0,
                                offsetof(laCanvasExtra, LineWidthWarning), 0, 0, 0, 0, 0, 0, 0, 0, 0, LA_READ_ONLY);{
             laAddEnumItem(p, "acceptable", "Acceptable", "Line width is acceptable by graphic hadware", U'✔');
-            laAddEnumItem(p, "too_wide", "Too Wide", "Line width is too wide for graphic hadware", U'');
-            laAddEnumItem(p, "too_thin", "Too Thin", "Line width is too thin for graphic hadware", U'');
+            laAddEnumItem(p, "too_wide", "Too Wide", "Line width is too wide for graphic hadware", U'🞫');
+            laAddEnumItem(p, "too_thin", "Too Thin", "Line width is too thin for graphic hadware", U'🞫');
         }
         p = laAddEnumProperty(pc, "clear_background", "Clear Background", "Clear Background", 0, 0, 0, 0, 0, offsetof(laCanvasExtra, ClearBackground), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);{
             laAddEnumItem(p, "no", "No", "Don't Clear Background", U'🌔');