Archive
Archive for June 18, 2016
Scroll controllers, ah, i love ’em!
June 18, 2016
2 comments
Just a quick follow up on the scrolling controllers. What I have done is essentially to rip out the code that performs scrolling, and isolated it in separate non-visual components. This allows you to pick the scrolling you want for a particular TW3ScrollControl. From momentum to vanilla, to fully fledged bouncing iScroll5!
Watch the video here!: https://www.youtube.com/watch?v=JcUCV-8WKiU
Plain, vanilla scrolling is reduced to:
unit SmartCL.Scroll.Plain; interface {.$DEFINE USE_LATENCY_CACHE} uses System.Types, System.Types.Convert, System.Time, System.Events, SmartCl.Events, SmartCL.System, SmartCL.Components, SmartCL.Effects, SmartCL.Scroll; type TW3PlainScrollOptions = set of (psHorizontal,psVertical); TW3PlainScrollController = class(TW3ScrollController) protected procedure MoveBegins(const XPos, YPos: integer); override; procedure MoveUpdate(const XPos, YPos: integer); override; procedure MoveEnds(const XPos, YPos: integer); override; protected procedure InitializeObject; override; published property Options: TW3PlainScrollOptions; end; implementation //############################################################################# // TW3PlainScrollController //############################################################################# procedure TW3PlainScrollController.InitializeObject; begin inherited; Options := [psHorizontal, psVertical]; end; procedure TW3PlainScrollController.MoveBegins(const XPos, YPos: integer); begin end; procedure TW3PlainScrollController.MoveUpdate(const XPos, YPos: integer); function GetBottomMax: integer; begin result := ScrollContent.height - ScrollControl.ClientHeight; end; var LVDiff: integer; dy: integer; begin LVDiff := TInteger.Diff(YPos, Startpos.y); if (Ypos < Startpos.y) then begin (* Move downwards *) dy := ScrollContent.top - LVDiff; if dy < (-GetBottomMax) then dy := -GetBottomMax; ScrollContent.top := dy; end else begin (* Move upwards *) dy := ScrollContent.top + LVDiff; if dy > 0 then dy := 0; ScrollContent.top :=dy; end; end; procedure TW3PlainScrollController.MoveEnds(const XPos, YPos: integer); begin end; end.
And momentum is as simple as:
unit SmartCL.Scroll.Momentum; interface uses System.Types, System.Types.Convert, System.Time, System.Events, SmartCl.Events, SmartCL.System, SmartCL.Components, SmartCL.Effects, SmartCL.Scroll; type TW3MomentumData = record mdStart: integer; mdOffset: integer; mdTarget: integer; mdTimestamp: integer; mdFrame: double; mdVelocity: double; mdAmplitude: double; mdTimeConstant: double; end; TW3MomentumScrollController = class(TW3ScrollController) private FVRange: TW3Range; FHRange: TW3Range; FVData: TW3MomentumData; FTicker: TW3DispatchHandle; procedure AutoScroll; procedure ScrollY(const NewTop: integer); procedure Track; protected procedure SessionBegins; override; procedure SessionEnds; override; protected procedure MoveBegins(const XPos, YPos: integer); override; procedure MoveUpdate(const XPos, YPos: integer); override; procedure MoveEnds(const XPos, YPos: integer); override; public procedure Attach(const ScrollControl: TW3ScrollControl); override; end; implementation //############################################################################# // TW3MomentumScrollController //############################################################################# procedure TW3MomentumScrollController.Attach(const ScrollControl: TW3ScrollControl); begin inherited Attach(ScrollControl); FVData.mdTimeConstant := 325; end; procedure TW3MomentumScrollController.Track; var LNow: integer; Elapsed: integer; Delta: double; begin LNow := TW3Dispatch.JsNow.now(); Elapsed := LNow - FVData.mdTimestamp; FVData.mdTimestamp := LNow; Delta := FVData.mdOffset - FVData.mdFrame; FVData.mdFrame := FVData.mdTarget; FVData.mdVelocity := 0.8 * (1000 * Delta / (1 + Elapsed)) + 0.2 * FVData.mdVelocity; end; procedure TW3MomentumScrollController.AutoScroll; var Elapsed: integer; Delta: double; begin (* Scrolled passed end-of-document ? *) if (FVData.mdOffset >= FVRange.Maximum) then begin TW3Dispatch.ClearInterval(FTicker); FTicker := unassigned; ScrollY(ScrollContent.Height-ScrollControl.ClientHeight); exit; end; (* Scrolling breaches beginning of document? *) if (FVData.mdOffset < 0) then begin TW3Dispatch.ClearInterval(FTicker); FTicker := unassigned; ScrollY(0); exit; end; if (FVData.mdAmplitude <> 0) then begin Elapsed := TW3Dispatch.JsNow.now() - FVData.mdTimestamp; Delta := -FVData.mdAmplitude * Exp(-Elapsed / FVData.mdTimeConstant); end; if (delta > 5) or (delta < -5) then begin ScrollY(FVData.mdTarget + Delta); TW3Dispatch.RequestAnimationFrame(AutoScroll); end else begin ScrollY(FVData.mdTarget); end; end; procedure TW3MomentumScrollController.ScrollY(const NewTop: integer); var LGPU: string; begin if not (csDestroying in ScrollControl.ComponentState) then begin if (csReady in ScrollControl.ComponentState) then begin (* Use GPU scrolling to position the content *) FVData.mdOffset := FVRange.ClipTo(NewTop); LGPU := "translateY(" + (-FVData.mdOffset).ToString() + "px)"; ScrollContent.Handle.style[BrowserAPI.Prefix("Transform")] := LGPU; end; end; end; procedure TW3MomentumScrollController.SessionBegins; begin ScrollControl.UpdateContent; end; procedure TW3MomentumScrollController.SessionEnds; begin end; procedure TW3MomentumScrollController.MoveBegins(const XPos, YPos: integer); begin TW3Dispatch.ClearInterval(FTicker); FVRange := TW3Range.Create(0, ScrollContent.Height - ScrollControl.ClientHeight); FHRange := TW3Range.Create(0, ScrollContent.Width - ScrollControl.ClientWidth); FVData.mdStart := YPos; FVData.mdVelocity := 0; FVData.mdAmplitude := 0; FVData.mdFrame := FVData.mdOffset;// FOffset; FVData.mdTimeStamp := TW3Dispatch.JsNow.now(); FTicker := TW3Dispatch.SetInterval(Track,100); end; procedure TW3MomentumScrollController.MoveUpdate(const XPos, YPos: integer); var delta: integer; begin delta := (FVData.mdStart - YPos); if (Delta > 2) or (Delta < -2) then begin FVData.mdStart := YPos; ScrollY(FVData.mdOffset + Delta); end; end; procedure TW3MomentumScrollController.MoveEnds(const XPos, YPos: integer); begin TW3Dispatch.ClearInterval(FTicker); if (FVData.mdVelocity > 10) or (FVData.mdVelocity < -10) then begin FVData.mdAmplitude := 0.8 * FVData.mdVelocity; FVData.mdTarget := FVData.mdOffset + Round(FVData.mdAmplitude); FVData.mdTimestamp := TW3Dispatch.JsNow.Now(); TW3Dispatch.RequestAnimationFrame(Autoscroll); end; end; end.
Neat, simple, easy to use and more important: easy to expand!
You must be logged in to post a comment.