Archive
TTween and css bindings

Tweens like clockwork
One thing that’s been missing from Smart’s TTween library is bindings. Well, fret ye no more because the next time you update Smart Mobile Studio you will get two shiny new units. Actually they are old units, they have been on Github for ages – but they will now be included with the RTL.
- SmartCL.Tween.pas
- SmartCL.Tween.Effects.pas
Bindings? What’s that?
If you follow the latest JavaScript developments you will know that – most modern tweening libraries can work directly with CSS styles. Meaning, that when you execute a tween – instead of you getting the values back through an event, the binding actually writes the data directly to the CSS style. It also uses translation objects to make sure values are written correctly depending on the datatype.
Well, this is now a part of Smart Mobile Studio’s RTL as well. So now you can do sexy stuff like:
procedure TForm1.W3Button1Click(Sender: TObject); begin // Create a widget tween effect object // and target a panel we have on the form var effect:=TWidgetEffect.Create(w3panel1); // Set some default values. These are copied into each // binding, but you can overwrite them (see below) // Also note that bindings read from the CSS on start (!) effect.Distance:=100; effect.Duration:=200; effect.Easing := ttQuartInOut; // Use requestAnimationFrame API effect.TweenEngine.SyncRefresh := true; // Bind the "left" css property using a pixel translator var LLeftBinding := effect.Bind('left',TCSSBindingType.sbPixels); LLeftBinding.Tween.Distance := 100; LLeftBinding.Tween.Easing := ttCubeInOut; LLeftBinding.Tween.Duration := 600; LLeftBinding.Tween.Behavior:=TTweenBehavior.tbOscillate; // bind the "top" css property as well var LTopBinding := effect.Bind('top',TCSSBindingType.sbPixels); LTopBinding.Tween.Distance := 50; LTopBinding.Tween.Easing := ttSineInOut; LTopBinding.Tween.Duration := 400; LTopBinding.Tween.Behavior:=TTweenBehavior.tbOscillate; // and execute both at once effect.Execute( procedure (sender:TObject) begin // All done? Destroy the effect instance in 200ms TW3Dispatch.DelayedDispatch( procedure () begin effect.free; effect := nil; end, 200); end); end;
This is really super cool and opens up for some awesome effects!
Performing silky smooth rotation is now a matter of just binding to a single CSS property and tweening from 0 to 365.
Color tweening is also fun 🙂
Dealing with swipe gestures
Someone asked me earlier about handling swipe gestures under Smart Pascal, so I thought I could deal with that in this post. I’m on sickleave with a bad back so I’m scribling this on my iPad (so heads up regarding typos). Cant say I’m to impressed with the Bluetooth keyboard, but it kinda works.
Describe a swipe
As luck would have it I already wrote a swipe controller way back in 2014, so I’ll be posting that here. But before we get into the code , let’s take a few seconds to think about what a swipe really is, because it’s not as simple as you may think.
Just stop and think about it: what is the difference between a normal touch-move and a swipe? Same thing right? Well yes and no (or kinda). What it all boils down to is time (!). So the difference between a normal touch-move operation and  a swipe, is that a swipe happens very quickly.
Latency and ranges
The time it takes from your finger touches the display until your finger leaves, is the latency of the swipe (see code below). Depending on the device this latency can vary. On an iPad you can have a latency as low as 10, while on iPhones you generally end up with 35 or 40. Anything lower and the swipe wont register.

The Microsoft Phone swipe header
Next there are ranges. A swipe involves your finger moving quickly in some direction, and the controller has some default ranges for measuring these things. These ranges are simple minimum and maximum values for the distance. If you have hardly moved your finger then it’s not really a swipe; and if you move your finger slowly over a long distance, that’s not a swipe either (well, it can be. Just adjust the latency factor to suit your needs). At least not for components designed to be flicked back and forth.
The swipe controller class was actually written for the Microsoft Mobile component package (will be included with Smart Mobile Studio in the future), which implements the most popular MS controls used on Windows 10 touch devices. Fully GPU powered, animated, skinned and working brilliantly.
They also look good under iOS. Everyone is skinning their app’s these days, so mixing and matching between platforms is a great benefit.
Controller vs. inheritance
A controller class does not need to be inherited from. Instead, you create an instance and attach it to an already existing visual control. This is pretty cool because it doesn’t mess with your event handlers, it just extends your control over the existing behavior. Controllers are handy in situations where inheritance will be tricky, require to much refactoring or where the source is off-limits.
Using the controller is super simple:
FSwiper := TSwipeController.Create(nil); FSwiper.Attach(self); FSwiper.OnSwipe := procedure (sender:TObject;const Direction:TSwipeControllerDirection) begin showmessage("Swipe detected @ " + ord(direction).toString); end;
To detach the controller just call the Detach() method, or free the object. It will dispose of everything automatically. Note: If you pass along the control in the constructor, it attaches automatically.
The code
Well here is the code. Enjoy! Also remember that you can check this out from GitHub.
unit swipecontroller; interface uses system.types, system.dateutils, SmartCL.System, SmartCL.Components; type TSwipeControllerDirection = ( sdNone=0, sdLeft, sdRight, sdDown, sdUp ); TSwipeControllerEvent = Procedure (sender:TObject; const Direction:TSwipeControllerDirection); TSwipeControllerInfo = class public begins: TDateTime; sX: Integer; sY: Integer; eX: Integer; eY: Integer; end; TSwipeRange = class private FMin: Integer; FMax: Integer; protected procedure SetMin(Value:Integer);virtual; procedure SetMax(Value:Integer);virtual; public property Minimum:Integer read FMin write SetMin; property Maximum:Integer read FMax write SetMax; constructor Create(AMin,AMax:Integer); end; TSwipeController = class(TObject) private FAttached: Boolean; FControl: TW3TagObj; FHRange: TSwipeRange; FVRange: TSwipeRange; FInfo: TSwipeControllerInfo; FDirection: TSwipeControllerDirection; protected FTouchHandleStart: THandle; FTouchHandleMove: THandle; FTouchHandleUp: THandle; procedure SetupGestures; procedure RemoveGestures; public Property Latency:Integer; property HRange:TSwipeRange read FHRange; property VRange:TSwipeRange read FVRange; Property Owner:TW3TagObj read FControl; property Attached:Boolean read FAttached; Property OnSwipe:TSwipeControllerEvent; procedure Attach(const AOwner:TW3TagObj); procedure Detach; constructor Create(const AOwner:TW3TagObj);virtual; destructor Destroy;Override; end; implementation //############################################################################ // TSwipeRange //############################################################################ constructor TSwipeRange.Create(AMin,AMax:Integer); begin inherited Create; FMin:=AMin; FMax:=AMax; end; procedure TSwipeRange.SetMin(Value:Integer); begin FMin := TInteger.EnsureRange(Value,10,1000); end; procedure TSwipeRange.SetMax(Value:Integer); begin FMax := TInteger.EnsureRange(Value,10,1000); end; //############################################################################ // TSwipeController //############################################################################ constructor TSwipeController.Create(const AOwner:TW3TagObj); begin inherited Create; FHRange:=TSwipeRange.Create(20,40); // [min]---X---[max] FVRange:=TSwipeRange.Create(20,40); // [min]---Y---[max] FInfo := TSwipeControllerInfo.Create; Latency := 35; if assigned(AOwner) then Attach(Aowner); end; destructor TSwipeController.Destroy; begin if FAttached then Detach; FHRange.free; FVRange.free; FInfo.free; inherited; end; procedure TSwipeController.Attach(const AOwner:TW3TagObj); begin if FAttached then Detach; if AOwner<>NIl then begin FControl := AOwner; FAttached := true; FControl.Handle.readyExecute( procedure () begin SetupGestures; end); end; end; procedure TSwipeController.Detach; begin if FAttached then begin try RemoveGestures; finally FAttached := false; end; end; end; procedure TSwipeController.RemoveGestures; begin FControl.Handle.removeEventListener(FTouchHandleStart); FControl.Handle.removeEventListener(FTouchHandleMove); FControl.Handle.removeEventListener(FTouchHandleUp); FTouchHandleStart := unassigned; FTouchHandleMove := unassigned; FTouchHandleUp := unassigned; end; procedure TSwipeController.SetupGestures; begin FTouchHandleStart:=FControl.Handle.addEventListener ('touchstart', procedure (e:variant) begin e.preventDefault; var t := e.touches[0]; FInfo.sx:=t.screenX; FInfo.sy:=t.screenY; end); FTouchHandleMove:=FControl.Handle.addEventListener ('touchmove', procedure (e:variant) begin e.preventDefault; if (e.touches) then Begin if e.touches.length>0 then begin var t := e.touches[0]; FInfo.eX:=t.screenX; FInfo.eY:=t.screenY; FInfo.begins:=now; end; end; end); FTouchHandleUp:=FControl.Handle.addEventListener ('touchend', procedure (e:variant) var mTicks: Integer; begin e.preventDefault; FDirection:=sdNone; (* How many Ms since touch and release? *) mTicks:=MillisecondsBetween(FInfo.begins,now); if (mTicks <= Latency) then begin if (FInfo.ex - FHRange.Minimum > FInfo.sx) or (FInfo.ex + FHRange.Minimum < FInfo.sx) then begin if (FInfo.ey < (FInfo.sy + FVRange.Maximum)) and (FInfo.sy > (FInfo.ey - FVRange.Maximum)) then begin if (FInfo.ex > FInfo.sx) then FDirection:=sdRight else FDirection:=sdLeft; end; end; if ((FInfo.ey - FVRange.Minimum) > FInfo.sy) or ((FInfo.ey + FVRange.Minimum) < FInfo.sY) then begin if (FInfo.ex < (FInfo.sx + FHRange.Maximum)) and (FInfo.sx > (FInfo.ex - FHRange.Maximum)) then begin if FInfo.ey > FInfo.sY then FDirection :=sdDown else FDirection :=sdUp; end; end; if assigned(OnSwipe) then begin if FDirection<>sdNone then OnSwipe(self,FDirection); end; end; end); end; end.
You must be logged in to post a comment.