*/}}
浏览代码

Action widget improvements and mixing

YimingWu 1 年之前
父节点
当前提交
f13b1d50d7
共有 5 个文件被更改,包括 266 次插入74 次删除
  1. 205 47
      la_animation.c
  2. 15 3
      la_data.h
  3. 17 10
      resources/la_properties.c
  4. 5 4
      resources/la_templates.c
  5. 24 10
      resources/la_widgets.c

+ 205 - 47
la_animation.c

@@ -21,30 +21,37 @@
 extern LA MAIN;
 
 int la_GetKeyablePropertyStorageSize(laProp* p);
+void la_AnimationEvaluateActions(int ClampOffsets);
 
 laAction* laAnimiationNewAction(char* Name){
     laAction* aa=memAcquire(sizeof(laAction));
     if(!Name || !Name[0]){ Name="New Action"; }
     strSafeSet(&aa->Name,Name);
-    aa->Length=5; aa->FrameCount=120;
+    aa->Length=2; aa->FrameCount=24;
     memAssignRef(MAIN.Animation,&MAIN.Animation->CurrentAction,aa);
     lstAppendItem(&MAIN.Animation->Actions,aa); laNotifyUsers("la.animation.actions");
     return aa;
 }
-laActionChannel* laAnimationEnsureChannel(laAction* aa, void* hyper1, laProp* p){
-    laActionChannel* acf=0;
+laActionProp* laAnimationEnsureProp(void* hyper1, laProp* p){
     int DataSize=la_GetKeyablePropertyStorageSize(p); if(!DataSize) return 0;
-    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
-        if(ac->For==hyper1 && ac->Prop==p){ acf=ac; break; }
+    for(laActionProp* ap=MAIN.Animation->Props.pFirst;ap;ap=ap->Item.pNext){
+        if(ap->For==hyper1 && ap->Prop==p){ return ap; }
     }
-    if(acf) return acf;
-    acf=memAcquire(sizeof(laActionChannel));
-    memAssignRef(acf,&acf->For,hyper1); acf->Prop=p;
-    acf->DataSize = DataSize;
+    laActionProp* ap = memAcquire(sizeof(laActionProp)); lstAppendItem(&MAIN.Animation->Props,ap);
+    memAssignRef(ap,&ap->For,hyper1); ap->Prop=p;
+    ap->DataSize = DataSize;
     char _name[128]={0}, *name=_name; laTryGetInstanceIdentifier(hyper1, p->Container,_name,&name);
-    strSafeSet(&acf->CachedName,name); strSafePrint(&acf->CachedName," ⯈ %s",p->Identifier);
-    lstAppendItem(&aa->Channels,acf); laNotifyUsers("la.animation.current_action.channels");
-    return acf;
+    strSafeSet(&ap->CachedName,name); strSafePrint(&ap->CachedName," ⯈ %s",p->Identifier);
+    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;
+    }
+    ac=memAcquire(sizeof(laActionChannel)); ac->AP=ap;
+    lstAppendItem(&aa->Channels,ac); laNotifyUsers("la.animation.current_action.channels");
+    return ac;
 }
 laActionChannel* laAnimationEnsureFrame(laActionChannel* ac, int frame){
     laActionKey* akf=0,*beforeakf=0;
@@ -52,7 +59,7 @@ 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->DataSize);
+        akf=memAcquireSimple(sizeof(laActionKey)-sizeof(uint64_t)+ac->AP->DataSize);
         akf->At=frame;
         if(beforeakf){ lstInsertItemBefore(&ac->Keys, akf, beforeakf); }
         else{ lstAppendItem(&ac->Keys, akf); }
@@ -60,9 +67,9 @@ laActionChannel* laAnimationEnsureFrame(laActionChannel* ac, int frame){
     return akf;
 }
 void laAnimationStoreKeyValue(laActionChannel* ac, laActionKey* ak){
-    laPropPack PP={0}; laPropStep PS={0};
-    PS.p=ac->Prop; PS.Type='.'; PS.UseInstance=ac->For; PP.LastPs=&PS; PP.EndInstance=ac->For;
-    switch(ac->Prop->PropertyType){
+    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;
@@ -71,7 +78,7 @@ void laAnimationStoreKeyValue(laActionChannel* ac, laActionKey* ak){
 }
 laActionKey* laAnimationInsertKeyFrame(laAction* aa, void* hyper1, laProp* p){
     if(!aa) return 0;
-    laActionChannel* ac=laAnimationEnsureChannel(aa,hyper1,p); if(!ac || !ac->For) return;
+    laActionChannel* ac=laAnimationEnsureChannel(aa,hyper1,p); if(!ac || !ac->AP || !ac->AP->For) return;
     int frame=LA_ACTION_FRAME(aa);
     laActionKey* ak=laAnimationEnsureFrame(ac,frame);
     laAnimationStoreKeyValue(ac,ak);
@@ -101,8 +108,18 @@ int OPINV_AnimationPlayAction(laOperator *a, laEvent *e){
     laAnimationSetPlayStatus(PlayStatus);
     return LA_FINISHED;
 }
+
+void la_AnimationActionSetOwnFrame(laAction* aa, int frame){
+    if(!aa) return;
+    aa->Offset+=(aa->PlayHead-(real)frame/aa->FrameCount)*aa->Length-1e-4;
+    la_AnimationEvaluateActions(0);
+    laNotifyUsers("la.animation.current_action");
+}
+
 int OPINV_AnimationResetTime(laOperator *a, laEvent *e){
-    laAnimationSetPlayHead(0);
+    char* arg=strGetArgumentString(a->ExtraInstructionsP,"current");
+    if(strSame(arg,"true")){ la_AnimationActionSetOwnFrame(MAIN.Animation->CurrentAction,0); return LA_FINISHED; }
+    laAnimationSetPlayHead(0); la_AnimationEvaluateActions(1);
     return LA_FINISHED;
 }
 
@@ -110,17 +127,20 @@ int OPINV_AnimationResetTime(laOperator *a, laEvent *e){
 laActionChannel* laAnimationGetFrame(laActionChannel* ac, int frame){
     if(ac->Keys.pFirst==ac->Keys.pLast){ return ac->Keys.pFirst; }
     for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){
-        if(ak->At<=frame){ return ak; }
+        if(ak->At<=frame && ((!ak->Item.pNext) ||ak->Item.pNext && ((laActionKey*)ak->Item.pNext)->At>frame)){ return ak; }
     }
     return ac->Keys.pFirst;
 }
 
+void la_AnimationMarkPropReset(){
+    for(laActionProp* ap=MAIN.Animation->Props.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; }
-    int arrlen=ac->Prop->Len?ac->Prop->Len:1;
+    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(ac->Prop->PropertyType){
+    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;
     case LA_PROP_FLOAT: case LA_PROP_FLOAT|LA_PROP_ARRAY:
@@ -129,10 +149,11 @@ void la_AnimationInterpolateKeys(laActionChannel* ac, laActionKey* ak1, laAction
         *data=ak1->Data; break;
     }
 }
-void la_AnimationSetChannelValue(laActionChannel* ac, void* data){
-    laPropPack PP={0}; laPropStep PS={0};
-    PS.p=ac->Prop; PS.Type='.'; PS.UseInstance=ac->For; PP.LastPs=&PS; PP.EndInstance=ac->For;
-    switch(ac->Prop->PropertyType){
+void la_AnimationSetPropValue(laActionProp* ap){
+    void* data=ap->Data;
+    laPropPack PP={0}; laPropStep PS={0}; 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: laSetInt(&PP,*((int*)data)); break;
     case LA_PROP_INT|LA_PROP_ARRAY: laSetIntArrayAllArray(&PP,(int*)data); break;
     case LA_PROP_FLOAT: laSetFloat(&PP,*((real*)data)); break;
@@ -142,6 +163,22 @@ void la_AnimationSetChannelValue(laActionChannel* ac, void* data){
     default: break;
     }
 }
+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;
+    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++){ 
+            ip[i]=ap->Reset?ipd[i]:(MixMode==LA_ANIMATION_MIX_REPLACE?ipd[i]:(ipd[i]+ip[i]));
+        } break;
+    case LA_PROP_FLOAT: case LA_PROP_FLOAT|LA_PROP_ARRAY: for(int i=0;i<arrlen;i++){ 
+            fp[i]=ap->Reset?fpd[i]:(MixMode==LA_ANIMATION_MIX_REPLACE?fpd[i]:(fpd[i]+fp[i]));
+        } break;
+    case LA_PROP_ENUM: ip[0]=ipd[0]; break;
+    //case LA_PROP_ENUM|LA_PROP_ARRAY: laSetEnumArrayAllArray(&PP,(real*)data); break; //doesnt work
+    default: break;
+    }
+    ap->Reset=0;
+}
 void la_AnimationEvaluateActionChannels(laAction* aa){
     int64_t _data[16]; void* data=_data;
     int frame=LA_ACTION_FRAME(aa); real totframe=aa->PlayHead*aa->FrameCount;
@@ -149,33 +186,43 @@ void la_AnimationEvaluateActionChannels(laAction* aa){
         laActionKey* ak1=laAnimationGetFrame(ac,frame); if(!ak1) continue;
         laActionKey* ak2=ak1->Item.pNext;
         la_AnimationInterpolateKeys(ac,ak1,ak2,totframe,&data);
-        la_AnimationSetChannelValue(ac, data);
+        la_AnimationMixChannelValue(ac, data, aa->MixMode, 0);
     }
 }
-void la_AnimationEvaluateActions(){
+void la_AnimationEvaluateActions(int ClampOffsets){
     int any=0;
+    la_AnimationMarkPropReset();
     for(laAction* aa=MAIN.Animation->Actions.pFirst;aa;aa=aa->Item.pNext){
-        real UseTime=MAIN.Animation->PlayHead-aa->Offset;
+        real preoffset=0,postoffset=aa->Offset/aa->Length;
+        if(ClampOffsets || (MAIN.Animation->PlayStatus!=LA_ANIMATION_STATUS_PAUSED)){
+            while(aa->Offset>aa->Length){ aa->Offset-=aa->Length; }
+            while(aa->Offset<-1e-6){ aa->Offset+=aa->Length; }
+            preoffset=aa->Offset; postoffset=0;
+        }
+        real UseTime=MAIN.Animation->PlayHead-preoffset;
         int repeats=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; }
-        elif(aa->PlayMode==LA_ANIMATION_PLAY_MODE_HOLD){ aa->PlayHead=(UseTime>aa->Length)?1.0:(UseTime<0?0:UseTime/aa->Length); }
-        elif(aa->PlayMode==LA_ANIMATION_PLAY_MODE_BOUNCE){ real t=remaining/aa->Length; aa->PlayHead=(repeats%2)?(1-t):t; }
+        if(aa->PlayMode==LA_ANIMATION_PLAY_MODE_REPEAT){ aa->PlayHead=remaining/aa->Length-postoffset; }
+        elif(aa->PlayMode==LA_ANIMATION_PLAY_MODE_HOLD){ aa->PlayHead=((UseTime>aa->Length)?1.0:(UseTime<0?0:UseTime/aa->Length))-postoffset; }
+        elif(aa->PlayMode==LA_ANIMATION_PLAY_MODE_BOUNCE){ real t=remaining/aa->Length; aa->PlayHead=((repeats%2)?(1-t):t)-postoffset; }
         any=1;
         la_AnimationEvaluateActionChannels(aa);
     }
+    for(laActionProp* ap=MAIN.Animation->Props.pFirst;ap;ap=ap->Item.pNext){ if(ap->Reset){ continue; }
+        la_AnimationSetPropValue(ap);
+    }
     if(any) laNotifyUsers("la.animation");
 }
 
 void la_AnimationPreFrame(){
     if(MAIN.Animation->PlayHead<0){
         MAIN.Animation->PlayHead=0; laAnimationSetPlayStatus(0);
-        la_AnimationEvaluateActions();
+        la_AnimationEvaluateActions(1);
         laNotifyUsers("la.animation.play_head");
     }
     if(MAIN.Animation->PlayStatus){
-        la_AnimationEvaluateActions();
+        la_AnimationEvaluateActions(0);
     }
 }
 void la_AnimationPostFrame(){
@@ -188,8 +235,73 @@ void la_AnimationPostFrame(){
     }
 }
 
-void laget_AnimationChannelName(laActionChannel *p, char *result, char** here){
-    
+void la_ActionDeselectFrames(laAction* aa){
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){
+            ak->Selected=0;
+        }
+    }
+}
+void la_ActionSelectFrame(laAction* aa, int Channel, real At, real zoomx){
+    int tc=0;laActionChannel* ac;
+    for(ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){ if(tc==Channel) break; tc++; }
+    if(!ac) return;
+    real ClosestDist=FLT_MAX; laActionKey* ClosestKey=0;
+    for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){
+        real d=fabs(ak->At-At+0.5); if(d*zoomx<LA_RH*2 && d<ClosestDist){ ClosestDist=d; ClosestKey=ak; }
+    }
+    if(ClosestKey){ ClosestKey->Selected=1; }
+}
+int la_ActionSaveKeyOriginalAt(laAction* aa){
+    int any=0;
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){ if(!ak->Selected) continue; any=1; ak->OriginaAt=ak->At; }
+    }
+    return any;
+}
+void la_ActionRestoreKeyOriginalAt(laAction* aa){
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){ if(!ak->Selected) continue; ak->At=ak->OriginaAt; }
+    }
+    la_ActionEnsureFrameOrder(aa);
+}
+void la_ActionEnsureFrameOrder(laAction* aa){
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        laListHandle lst={0};
+        laActionKey* ak; while(ak=lstPopItem(&ac->Keys)){ int done=0;
+            if(!lst.pFirst){ lstAppendItem(&lst,ak); continue; }
+            laActionKey* ak2=0;
+            for(ak2=lst.pFirst;ak2;ak2=ak2->Item.pNext){
+                if(ak2->At>=ak->At){ lstInsertItemBefore(&lst,ak,ak2); done=1; break; }
+            }
+            if(!done) lstAppendItem(&lst,ak);
+        }
+        lstCopyHandle(&ac->Keys,&lst);
+    }
+}
+void la_ActionRemoveDuplicatedFrames(laAction* aa){
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        laActionKey* ak2; for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){
+            while((ak2=ak->Item.pNext)&&ak->At==ak2->At){ lstRemoveItem(&ac->Keys,ak2); memLeave(ak2); }
+        }
+    }
+}
+void la_ActionMoveSelectedFrames(laAction* aa, int Delta){
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){ if(!ak->Selected) continue;
+            ak->At=ak->OriginaAt+Delta;
+        }
+    }
+    la_ActionEnsureFrameOrder(aa);
+}
+int la_ActionDeleteSelectedFrames(laAction* aa){
+    int any=0;
+    for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
+        laActionKey* NextAk; for(laActionKey* ak=ac->Keys.pFirst;ak;ak=NextAk){ NextAk=ak->Item.pNext;if(!ak->Selected) continue;
+            lstRemoveItem(&ac->Keys, ak); memLeave(ak); any=1;
+        }
+    }
+    return any;
 }
 
 int LAMOD_AnimationActionsCanvas(laOperator *a, laEvent *e){
@@ -213,13 +325,27 @@ int LAMOD_AnimationActionsCanvas(laOperator *a, laEvent *e){
             ex->PanY-=e->y-ex->ClickedY; ex->PanX-=e->x-ex->ClickedX;
             ex->ClickedX=e->x; ex->ClickedY=e->y; Modal=1;
         }
-    }elif(ex->UiMode==2){
+    }elif(ex->UiMode==2){ Modal=1;
         if(e->Type&LA_MOUSE_EVENT){
-            ex->LW=e->x-ui->L+LA_SEAM_W; ex->ClickedX=e->x; ex->ClickedY=e->y; Modal=1;
+            ex->LW=e->x-ui->L+LA_SEAM_W; ex->ClickedX=e->x; ex->ClickedY=e->y;
             if(ex->LW<LA_RH*3 && ex->LW>=LA_RH/2){ ex->LW=LA_RH*3; }
             if(ex->LW<LA_RH/2 && ex->Dragging!=12){ ex->ShowLegend=0; ex->LW=0; }
             if(ex->LW<LA_RH*3){ ex->LW=LA_RH*3;}
         }
+    }elif(ex->UiMode==3){ Modal=1;
+        if(e->Type&LA_MOUSE_EVENT){ ex->ClickedX=e->x; ex->ClickedY=e->y;
+            int SetFrame=(real)(e->x-tl+ex->PanX)/ex->ZoomX/FW;
+            la_AnimationActionSetOwnFrame(aa,SetFrame);
+        }
+    }elif(ex->UiMode==4){ Modal=1;
+        if(e->Type==LA_R_MOUSE_DOWN || (e->Type==LA_ESCAPE_DOWN)){ ex->UiMode=0; ex->Dragging=0;
+            la_ActionRestoreKeyOriginalAt(aa); laNotifyUsers("la.animation.current_action");
+        }elif(e->Type==LA_L_MOUSE_DOWN){ la_ActionRemoveDuplicatedFrames(aa);
+            laNotifyUsers("la.animation.current_action"); ex->UiMode=0; ex->Dragging=0;
+        }elif(e->Type&LA_MOUSE_EVENT){ Modal=1;
+            int ToFrame=(real)(e->x-tl+ex->PanX)/ex->ZoomX/FW,FromFrame=(real)(ex->ClickedX-tl+ex->PanX)/ex->ZoomX/FW;
+            la_ActionMoveSelectedFrames(aa,ToFrame-FromFrame); laNotifyUsers("la.animation.current_action");
+        }
     }
 
     int MaxDN=LA_RH*(Channels+1)-H+bt->TP+bt->BP; if(MaxDN<0) MaxDN=0;
@@ -232,17 +358,36 @@ int LAMOD_AnimationActionsCanvas(laOperator *a, laEvent *e){
                     ex->UiMode=2; ex->ClickedX=e->x; ex->ClickedY=e->y; Modal=1; ex->Dragging=10; Modal=1;
                 }
             }else{ if(ex->TargetIndexVali){ ex->TargetIndexVali=0; Modal=1; } }
-        }else{
+        }elif(aa){
             if((!ex->ShowLegend)&&e->x<=ll+LA_SEAM_W*2){
                 if(!ex->TargetIndexVali){ ex->TargetIndexVali=1; Modal=1; }
                 if(e->Type==LA_L_MOUSE_DOWN){ ex->LW=LA_RH*4; ex->Dragging=12; ex->ShowLegend=1; Modal=1; ex->UiMode=2; ex->TargetIndexVali=1; }
             }else{ if(ex->TargetIndexVali){ ex->TargetIndexVali=0; Modal=1; } }
             if(e->Type==LA_MOUSE_WHEEL_DOWN){ ex->ZoomX*=0.9; ex->PanX+=mid; ex->PanX*=0.9; ex->PanX-=mid; Modal=1; }
-            if(e->Type==LA_MOUSE_WHEEL_UP){ ex->ZoomX*=1.1; ex->PanX+=mid; ex->PanX*=1.1; ex->PanX-=mid; Modal=1; }
+            elif(e->Type==LA_MOUSE_WHEEL_UP){ ex->ZoomX*=1.1; ex->PanX+=mid; ex->PanX*=1.1; ex->PanX-=mid; Modal=1; }
+            elif(e->Type==LA_L_MOUSE_DOWN){
+                ex->UiMode=3; int SetFrame=(real)(e->x-tl+ex->PanX)/ex->ZoomX/FW;
+                la_AnimationActionSetOwnFrame(aa,SetFrame); Modal=1;
+            }elif(e->Type==LA_R_MOUSE_DOWN){
+                int row=(e->y-ui->U-bt->BM-LA_RH)/LA_RH; real at=(real)(e->x-tl+ex->PanX)/ex->ZoomX/FW;
+                if(!(e->SpecialKeyBit&LA_KEY_SHIFT)){ la_ActionDeselectFrames(aa); }
+                la_ActionSelectFrame(aa,row,at,ex->ZoomX);
+                laNotifyUsers("la.animation.current_action");
+            }elif(e->Type==LA_KEY_DOWN&&e->key=='g'){
+                int any=la_ActionSaveKeyOriginalAt(aa);
+                if(any){ ex->ClickedX=e->x; ex->ClickedY=e->y; ex->UiMode=4; Modal=1; ex->Dragging=13;
+                    laNotifyUsers("la.animation.current_action");
+                }
+            }elif(e->Type==LA_KEY_DOWN&&e->key=='x'){
+                int any=la_ActionDeleteSelectedFrames(aa);
+                if(any){
+                    laNotifyUsers("la.animation.current_action");
+                }
+            }
         }
     }
     if(ex->PanY>MaxDN){ ex->PanY=MaxDN; } if(ex->PanY<0){ ex->PanY=0; }
-    if(ex->PanX<0){ ex->PanX=0; }
+    //if(ex->PanX<0){ ex->PanX=0; }
 
     if(ex->TargetIndexVali){ laSetWindowCursor(LA_LEFT_AND_RIGHT); }else{ laSetWindowCursor(LA_ARROW); }
 
@@ -302,7 +447,8 @@ void la_AnimationActionDrawCanvas(laBoxedTheme *bt, laAction *aa, laUiItem* ui){
             real ku=ui->U+bt->TP+row*LA_RH-ex->PanY,kb=ku+LA_RH;
             for(laActionKey* ak=ac->Keys.pFirst;ak;ak=ak->Item.pNext){
                 real kl=tl+ak->At*FW*ex->ZoomX-ex->PanX, kr=tl+(ak->At+1)*FW*ex->ZoomX-ex->PanX;
-                if(curframe==ak->At) tnsColor4d(LA_COLOR3(txt),txt[3]*0.6);
+                if(ak->Selected) tnsColor4dv(laAccentColor(LA_BT_TEXT_ACTIVE));
+                elif(curframe==ak->At) tnsColor4d(LA_COLOR3(txt),txt[3]*0.6);
                 else tnsColor4dv(laThemeColor(bt,LA_BT_ACTIVE));
                 tnsVertex2d(kl, ku); tnsVertex2d(kr, ku);
                 tnsVertex2d(kr, kb); tnsVertex2d(kl, kb);
@@ -316,14 +462,25 @@ void la_AnimationActionDrawCanvas(laBoxedTheme *bt, laAction *aa, laUiItem* ui){
         }
         tnsFlush(); glLineWidth(1);
 
+        real end=tl+aa->FrameCount*FW*ex->ZoomX-ex->PanX;
+        tnsColor4d(0,0,0,0.3);
+        tnsVertex2d(end, ui->U+LA_RH); tnsVertex2d(end+1e6, ui->U+LA_RH);
+        tnsVertex2d(end+1e6, ui->B); tnsVertex2d(end, ui->B);
+        tnsPackAs(GL_TRIANGLE_FAN); 
+        tnsVertex2d(tl-ex->PanX, ui->U+LA_RH); tnsVertex2d(tl-ex->PanX-1e6, ui->U+LA_RH);
+        tnsVertex2d(tl-ex->PanX-1e6, ui->B); tnsVertex2d(tl-ex->PanX, ui->B);
+        tnsPackAs(GL_TRIANGLE_FAN); 
+
         tnsColor4dv(laAccentColor(LA_BT_TEXT_ACTIVE));
-        cx=tl+aa->PlayHead*aa->FrameCount*FW*ex->ZoomX-ex->PanX; tnsVertex2d(cx, ui->U); tnsVertex2d(cx, ui->B);
+        int FrameFix=aa->PlayHead<0?1:0;
+        cx=tl+(aa->PlayHead*aa->FrameCount+FrameFix)*FW*ex->ZoomX-ex->PanX; tnsVertex2d(cx, ui->U); tnsVertex2d(cx, ui->B);
         tnsPackAs(GL_LINES); glLineWidth(3); tnsFlush(); glLineWidth(1);
+
+        char buf[32]; sprintf(buf,"%d",curframe); real tlen=tnsStringGetDimension(buf,0,0,0,0,0)+bt->LP+bt->RP;
         tnsColor4dv(laAccentColor(LA_BT_TEXT_ACTIVE));
-        tnsVertex2d(cx, ui->U+LA_RH); tnsVertex2d(cx+LA_RH, ui->U+LA_RH);
-        tnsVertex2d(cx+LA_RH, ui->U); tnsVertex2d(cx, ui->U);
+        tnsVertex2d(cx, ui->U+LA_RH); tnsVertex2d(cx+tlen, ui->U+LA_RH);
+        tnsVertex2d(cx+tlen, ui->U); tnsVertex2d(cx, ui->U);
         tnsPackAs(GL_TRIANGLE_FAN);
-        char buf[32]; sprintf(buf,"%d",curframe);
         tnsDrawStringAuto(buf,laThemeColor(bt,LA_BT_TEXT_ACTIVE),cx+bt->LP,cx+LA_RH*2,ui->U+bt->TP,0);
         tnsPackAs(GL_TRIANGLE_FAN); tnsUseNoTexture();
     }
@@ -336,8 +493,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);
             for(laActionChannel* ac=aa->Channels.pFirst;ac;ac=ac->Item.pNext){
-                tnsDrawStringAuto(ac->CachedName->Ptr,laThemeColor(bt,LA_BT_TEXT),ll+bt->LP,lr-bt->RP,ui->U+bt->TP+row*LA_RH-ex->PanY,0);
+                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);
                 row++;
             }
         }

+ 15 - 3
la_data.h

@@ -657,11 +657,15 @@ STRUCTURE(laDiffPost){
 #define LA_ANIMATION_PLAY_MODE_HOLD   1
 #define LA_ANIMATION_PLAY_MODE_BOUNCE 2
 
+#define LA_ANIMATION_MIX_REPLACE 0
+#define LA_ANIMATION_MIX_ADD     1
+
 NEED_STRUCTURE(laAction);
 STRUCTURE(laAnimation){
     real _PAD;
     laAction*    CurrentAction;
     laListHandle Actions;
+    laListHandle Props;
     real PlayHead;
     int PlayStatus;
     laTimeRecorder TimeOrigin;
@@ -675,18 +679,26 @@ STRUCTURE(laAction){
     real PlayHead,Offset;
     int PlayMode;
     int Solo, Mute;
+    int MixMode;
 };
-STRUCTURE(laActionChannel){
+STRUCTURE(laActionProp){
     laListItem Item;
     void* For;
     laProp* Prop;
-    laSafeString* CachedName;
     int DataSize; // sizeof
+    laSafeString* CachedName;
+    uint64_t Data[16];
+    int Reset;
+};
+STRUCTURE(laActionChannel){
+    laListItem Item;
+    laActionProp* AP;
     laListHandle Keys;
 };
 STRUCTURE(laActionKey){
     laListItem Item;
-    int At;
+    int At,OriginaAt;
+    int Selected;
     uint64_t Data; // variable size depending on property;
 };
 

+ 17 - 10
resources/la_properties.c

@@ -826,7 +826,7 @@ laPropContainer* tnsget_ObjectType(tnsObject* o){
 }
 
 int laget_AnimationActionCurrentFrame(laAction* aa){
-    return (aa->PlayHead*(real)aa->FrameCount+0.5);
+    return LA_ACTION_FRAME(aa);
 }
 
 void lareset_Main(void* Unused){
@@ -1426,31 +1426,38 @@ void la_RegisterInternalProps(){
         p = laAddPropertyContainer("la_animation_action", "Action", "Animation action",0,0,sizeof(laAction),0,0,1);{
             laAddStringProperty(p,"name","Name","Name of the action",0,0,0,0,1,offsetof(laAction,Name),0,0,0,0,LA_AS_IDENTIFIER);
             laAddSubGroup(p, "channels", "Channels", "Action channels", "la_animation_channel",0,0,0,-1,0,0,0,0,0,0,offsetof(laAction, Channels),0);
-            laAddFloatProperty(p, "length","Length","Length of the action in seconds",0,0,"s",30,0,0.1,5,0,offsetof(laAction,Length),0,0,0,0,0,0,0,0,0,0,0);
+            laAddFloatProperty(p, "length","Length","Length of the action in seconds",0,0,"s",30,0,0.1,2,0,offsetof(laAction,Length),0,0,0,0,0,0,0,0,0,0,0);
             laAddFloatProperty(p, "play_head","Play Head","Play Head position",0,0,0,1.0,0,0.01,0,0,offsetof(laAction,PlayHead),0,0,0,0,0,0,0,0,0,0,0);
             laAddFloatProperty(p, "offset","Offset","Play head offset from global timing",0,0,0,1.0,0,0.01,0,0,offsetof(laAction,Offset),0,0,0,0,0,0,0,0,0,0,0);
-            laAddIntProperty(p, "frame_count","Frame Count","Total frame count in the whole length of the action",0,0,0,0,0,0,120,0,offsetof(laAction,FrameCount),0,0,0,0,0,0,0,0,0,0,0);
+            laAddIntProperty(p, "frame_count","Frame Count","Total frame count in the whole length of the action",0,0,0,960,1,0,24,0,offsetof(laAction,FrameCount),0,0,0,0,0,0,0,0,0,0,0);
             laAddIntProperty(p, "current_frame","Current Frame","Current frame in this action",0,0,0,0,0,0,0,0,0,laget_AnimationActionCurrentFrame,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
             ep = laAddEnumProperty(p, "mute", "Mute", "Mute this action", 0,0,0,0,0,offsetof(laAction, Mute),0,0,0,0,0,0,0,0,0,0);{
-                laAddEnumItemAs(ep, "NONE", "None", "Not muted", 0,U'M');
-                laAddEnumItemAs(ep, "MUTE", "Mute", "Muted", 1,U'🄼');
+                laAddEnumItemAs(ep, "NONE", "None", "Not muted", 0,0);
+                laAddEnumItemAs(ep, "MUTE", "Mute", "Muted", 1,0);
             }
             ep = laAddEnumProperty(p, "solo", "Solo", "Solo play this action", 0,0,0,0,0,offsetof(laAction, Solo),0,0,0,0,0,0,0,0,0,0);{
-                laAddEnumItemAs(ep, "NONE", "None", "Play this action with", 0,U'S');
-                laAddEnumItemAs(ep, "SOLO", "Solo", "Solo play", 1,U'🅂');
+                laAddEnumItemAs(ep, "NONE", "None", "Play this action with", 0,0);
+                laAddEnumItemAs(ep, "SOLO", "Solo", "Solo play", 1,0);
             }
             ep = laAddEnumProperty(p, "play_mode", "Play Mode", "How to play this action", 0,0,0,0,0,offsetof(laAction, PlayMode),0,0,0,0,0,0,0,0,0,0);{
                 laAddEnumItemAs(ep, "REPEAT", "Repeat", "Play action in repeat", LA_ANIMATION_PLAY_MODE_REPEAT, U'⮆');
                 laAddEnumItemAs(ep, "HOLD", "Hold", "Hold end values when time is outside time range", LA_ANIMATION_PLAY_MODE_HOLD,U'⭲');
                 laAddEnumItemAs(ep, "BOUNCE", "Bounce", "Play action back and forth", LA_ANIMATION_PLAY_MODE_BOUNCE,U'⮀');
             }
+            ep = laAddEnumProperty(p, "mix_mode", "Mix", "How this action is mixed with existing ones", 0,0,0,0,0,offsetof(laAction, MixMode),0,0,0,0,0,0,0,0,0,0);{
+                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);
+            }
         }
         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);
-            laAddIntProperty(p, "data_size","Data Size","Data size of the channel",0,0,0,0,0,0,0,0,offsetof(laActionChannel,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(laActionChannel, Prop), 0,0,0,0,0,0,0,LA_UDF_REFER | LA_UDF_IGNORE);
+            laAddSubGroup(p, "ap", "Animation Property", "Animation property reference", "la_animation_prop",0,0,0,offsetof(laActionChannel, AP), 0,0,0,0,0,0,0,LA_UDF_REFER);
+        }
+        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.
-            laAddSubGroup(p, "for", "For", "Target data block", "any_pointer",0,0,0,offsetof(laActionChannel, For), 0,0,0,0,0,0,0,LA_UDF_REFER);
+            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);
         }
         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);

+ 5 - 4
resources/la_templates.c

@@ -1575,8 +1575,8 @@ void laui_AnimationActionSimple(laUiList *uil, laPropPack *This, laPropPack *Ext
         laUiItem* ui=laShowItemFull(uil,c,This,"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,c,This,"name")->Flags|=LA_UI_FLAGS_NO_DECAL|LA_UI_FLAGS_NO_EVENT;
-        laShowItemFull(uil,c,This,"solo",0,0,0,0)->Flags=LA_UI_FLAGS_ICON|LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
-        laShowItemFull(uil,c,This,"mute",0,0,0,0)->Flags=LA_UI_FLAGS_ICON|LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+        laShowItemFull(uil,c,This,"solo",0,"icon=S;",0,0)->Flags=LA_UI_FLAGS_ICON|LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+        laShowItemFull(uil,c,This,"mute",0,"icon=M;",0,0)->Flags=LA_UI_FLAGS_ICON|LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
     }
     laEndRow(uil,b);
 }
@@ -1629,7 +1629,7 @@ void laui_AnimationActionChannels(laUiList *uil, laPropPack *This, laPropPack *E
     laUiItem* b,*ui,*b2;
 
     laUiItem* row=laBeginRow(uil,cll,0,0);
-    laShowItemFull(uil,cll,0,"LA_animation_reset_time",0,0,0,0)->Flags|=LA_UI_FLAGS_ICON|LA_UI_FLAGS_EXIT_WHEN_TRIGGERED;
+    laShowItemFull(uil,cll,0,"LA_animation_reset_time",0,"current=true;text=1",0,0)->Flags|=LA_UI_FLAGS_EXIT_WHEN_TRIGGERED;
     b=laOnConditionThat(uil,cll,laPropExpression(0,"la.animation.play_status"));{
         ui=laShowItemFull(uil,cll,0,"LA_animation_set_play_status",0,"text=❚❚;",0,0);
         ui->Flags|=LA_UI_FLAGS_EXIT_WHEN_TRIGGERED|LA_TEXT_ALIGN_CENTER; ui->Expand=1;
@@ -1645,7 +1645,7 @@ void laui_AnimationActionChannels(laUiList *uil, laPropPack *This, laPropPack *E
         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->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;
+        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;
         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");
@@ -1654,6 +1654,7 @@ void laui_AnimationActionChannels(laUiList *uil, laPropPack *This, laPropPack *E
         laShowItemFull(uil,crrr,0,"la.animation.actions",LA_WIDGET_COLLECTION_SELECTOR,0,laui_AnimationActionListItem,0);
         b=laOnConditionToggle(uil,clr,0,0,0,0,0);{
             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;
             laShowSeparator(uil,cr);
             laShowItem(uil,cr,0,"la.animation.current_action.solo")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;

+ 24 - 10
resources/la_widgets.c

@@ -793,9 +793,17 @@ void la_EnumSelectorDraw(laUiItem *ui, int h){
         if(IsExpand){ _W/=EnumLen; }
         else{ _W/=ArrLen; }
     }
+    
+    int ico=0; char buft[48]={0}; int HasText=0;
+    if (ui->ExtraInstructions){
+        if (ui->Type->OperatorType->ParseArgs){
+            ui->Type->OperatorType->ParseArgs(ui->Instructions, &ico, buft);
+        }
+        sprintf(buf,"%s",transLate(buft)); HasText=1;
+    }
     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++){ buf[0]=0;
+        for(int j=0;j<EnumLen;j++){ if(!HasText)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; }
@@ -822,16 +830,11 @@ void la_EnumSelectorDraw(laUiItem *ui, int h){
                 tnsPackAs(GL_LINE_LOOP);
             }
 
-            if (ui->ExtraInstructions){
-                if (ui->Type->OperatorType->ParseArgs){
-                    char buft[48]={0}; int ico; ui->Type->OperatorType->ParseArgs(ui->Instructions, &ico, buft); 
-                    sprintf(buf,"%s",transLate(buft));
-                }
-            }
-            if(Highlight){ if(!buf[0] && ArrLen==1) strcpy(buf, transLate(ui->PP.LastPs->p->Name)); else{ if(i<8)strcat(buf, &prefix[i]); } }
-            if(!buf[0]) strcpy(buf, transLate(use_ei->Name));
+            if(Highlight){ if(!HasText && ArrLen==1) strcpy(buf, transLate(ui->PP.LastPs->p->Name)); else{ if(i<8)strcat(buf, &prefix[i]); } }
+            if((!HasText) && (!buf[0])) strcpy(buf, transLate(use_ei->Name));
             int iconR; if(IconOnly)iconR=TNS_MAX2(_L+LA_RH,_R);else{ iconR=TNS_MIN2(_L+LA_RH,_R); }
-            if (use_ei->IconID) tnsDrawIcon(use_ei->IconID, laThemeColor(bt, LA_BT_TEXT|ExtraState), _L,iconR, _U, LA_TEXT_ALIGN_CENTER);
+            if (use_ei->IconID && !ico){ ico=use_ei->IconID; }
+            if (ico) tnsDrawIcon(ico, laThemeColor(bt, LA_BT_TEXT|ExtraState), _L,iconR, _U, LA_TEXT_ALIGN_CENTER);
             if(!IconOnly){
                 int UseFlags=ui->Flags; if(!HasIcon && IsExpand){ if(!(UseFlags&LA_TEXT_ALIGN)); UseFlags|=LA_TEXT_ALIGN_CENTER; }
                 tnsDrawStringAuto(buf, laThemeColor(bt, LA_BT_TEXT|ExtraState), _L+bt->LM+(HasIcon?LA_RH:0), _R-bt->RM, _U, UseFlags);
@@ -1441,6 +1444,10 @@ void la_ValueMeterDraw(laUiItem *ui, int h){
         tnsVertex2d(_R, _B); tnsVertex2d(_L, _B);
         tnsPackAs(GL_TRIANGLE_FAN);
 
+        int oflow=0,ofhigh=0;
+        if(Data[i]<min){ oflow=1; Data[i]=min; }
+        if(Data[i]>max){ ofhigh=1; Data[i]=max; }
+        
         int R1=_R,U1=_U;
         if(IsVertical){ U1 = (real)(max - Data[i]) / (real)(max - min) * (real)(_B-_U) + _U; }
         else{ R1 = (real)(Data[i] - min) / (real)(max - min) * (real)(_R-_L) + _L; }
@@ -1459,6 +1466,13 @@ void la_ValueMeterDraw(laUiItem *ui, int h){
             if(Len==1){ sprintf(buf,ui->PP.LastPs->p->Name); } else{ if(i<8)sprintf(buf,prefix[i]); }
             if(buf[0]) tnsDrawStringAuto(buf, laThemeColor(bt, LA_BT_TEXT), _L + bt->LM, _R - bt->RM, _U, ui->Flags);//, ui->ExtraInstructions);
         }
+        if(IsVertical){
+            if(oflow){ tnsDrawStringAuto("⯆",laThemeColor(bt, LA_BT_TEXT), _L+bt->LM,_R-bt->RM, _U, LA_TEXT_ALIGN_CENTER); }
+            elif(ofhigh){ tnsDrawStringAuto("⯅",laThemeColor(bt, LA_BT_TEXT), _L+bt->LM,_R-bt->RM, _B-LA_RH, LA_TEXT_ALIGN_CENTER); }
+        }else{
+            if(oflow){ tnsDrawStringAuto("⯇",laThemeColor(bt, LA_BT_TEXT), _L+bt->LM,_R-bt->RM, _U, LA_TEXT_ALIGN_LEFT); }
+            elif(ofhigh){ tnsDrawStringAuto("⯈",laThemeColor(bt, LA_BT_TEXT), _L+bt->LM,_R-bt->RM, _U, LA_TEXT_ALIGN_RIGHT); }
+        }
     }
 
     tnsFlush();