Home > Delphi, iScroll, JavaScript, Object Pascal, OP4JS, Smart Mobile Studio > Scroll controllers, ah, i love ’em!

Scroll controllers, ah, i love ’em!

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:

momentum

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!

Advertisements
  1. June 19, 2016 at 12:13 am

    We can not reproduce the above example, we can not find the base class TW3ScrollController.

    Another thing: Look at this experimental “SMS Employee Directory”
    ===========
    PREVIEW: http://rawgit.com/smartpascal/smartms/master/games/projEmployeeDirectory/www/preview.html

    I ended up spending a decent amount of time trying different things to implement in memory data JsonStore in Smart. To save some headaches, I decided to create my own data store.

    Since data Json store are important in some projects, I couldn’t find a helper class to make creating data.Stores from JSON data easier in SMS.

    A simple Data Store should have agnostic functions like findById(Get the Record with the specified id), findExact( fieldName, value ) (finds the index of the first matching Record in this store by a specific field value)

    How would you implement such “Collection Source of Json data store in SMS”?

    • Jon Lennart Aasenden
      July 14, 2016 at 1:53 pm

      Ofcourse it cant be reproduced, its made with the next RTL. Im posting as demonstration of future updates.
      However, in some cases you can easily back-port it, but again – its just meant for demonstrating progress on various topics.

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: