Smart Mobile Studio
  • News
  • Forums
  • Download
  • Store
  • Showcases
    • Featured demos
    • The Smart Contest 2013, Round 1 – Graphics
  • Documentation
    • Get the book
    • System requirements
    • Prerequisites
    • Getting started
      • Introduction
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • Themes and styles
    • Project types
      • Visual project
      • Game project
      • Console project
    • Layout manager
    • Networking
      • TW3HttpRequest
      • TW3JSONP
      • Loading files
  • About

Creating a scrollbar

Posted on 04.03.2013 by Jon Lennart Posted in Developers log, News

So, what is a scrollbar? In short it consists of 4 parts: an up arrow, a region to move a handle, the handle, and a down arrow. The hard part is not to draw or setup the actual control, but to translate between screen coordinates and the values the scrollbar represents. The scrollbar might represent 100000 items of “something”, but since you have (for instance) only 400 pixels to represent that sum – you have to translate between the visual and the abstract.

The math…

function TW3CustomScrollBar.calcSizeOfHandle:Integer;
var
  mTemp:  Integer;
Begin
  mTemp:=TInteger.PercentOfValue(PageSize,Total);
  result:=round( mTemp * getArea / 100);
end;

function TW3CustomScrollBar.PositionToPixelOffset(aPosition:Integer):Integer;
var
  mTemp:  Integer;
Begin
  mTemp:=TInteger.PercentOfValue(aPosition,Total);
  result:=round( mTemp * getArea / 100);
end;

function TW3CustomScrollBar.PixelOffsetToPosition(aPxPos:Integer):Integer;
var
  mTemp: Integer;
Begin
  mTemp:=TInteger.PercentOfValue(aPxPos,getarea);
  result:=trunc( (mTemp * Total) / 100 );
end;

The baseclass…

type
  TW3ScrollbarLowerBtn  = Class(TW3CustomControl);
  TW3ScrollbarHigherBtn = class(TW3CustomControl);
  TW3ScrollbarHandle    = class(TW3CustomControl);

  TW3CustomScrollBar = Class(TW3CustomControl)
  private
    FUpBtn:     TW3ScrollbarLowerBtn;
    FDownBtn:   TW3ScrollbarHigherBtn;
    FHandle:    TW3ScrollbarHandle;
    FTotal:     Integer;
    FPageSize:  Integer;
    FPosition:  Integer;
  protected
    Procedure   setTotal(aValue:Integer);virtual;
    procedure   setPageSize(aValue:Integer);virtual;
    Procedure   setPosition(aValue:Integer);virtual;
    Procedure   setPositionNoCalc(aValue:Integer);virtual;
    procedure   InitializeObject; override;
    procedure   FinalizeObject; override;
    function    calcSizeOfHandle:Integer;
    function    PositionToPixelOffset(aPosition:Integer):Integer;
    function    PixelOffsetToPosition(aPxPos:Integer):Integer;
    Procedure   Recalculate;virtual;abstract;
    function    getArea:Integer;virtual;abstract;
  public
    class function supportAdjustment:Boolean;override;
    Property    MinButton:TW3ScrollbarLowerBtn read FUpBtn;
    property    MaxButton:TW3ScrollbarHigherBtn read FDownBtn;
    Property    DragHandle:TW3ScrollbarHandle read FHandle;
  published
    Property    Total:Integer read FTotal write setTotal;
    Property    PageSize:Integer read FPageSize write setPageSize;
    Property    Position:Integer read FPosition write setPosition;
  End;

The advantage of having a custom control like that is that you can style them. Smart automatically maps the classnames to the styles in the style sheet. So if you define a CSS class called TW3ScrollbarLowerBtn, it will be automatically applied to that control. This means you can alter the scrollbars with your own global theme if you have some css skills.

The implementation of the baseclass…

//###########################################################################
// TW3CustomScrollBar
//###########################################################################

procedure TW3CustomScrollBar.InitializeObject;
Begin
  inherited;
  FUpBtn:=TW3ScrollbarLowerBtn.Create(self);
  FDownBtn:=TW3ScrollbarHigherBtn.Create(self);
  FHandle:=TW3ScrollbarHandle.Create(self);
end;

procedure TW3CustomScrollBar.FinalizeObject;
Begin
  FUpBtn.free;
  FDownBtn.free;
  FHandle.free;
  inherited;
end;

class function TW3CustomScrollBar.supportAdjustment:Boolean;
Begin
  result:=true;
end;

Procedure TW3CustomScrollBar.setTotal(aValue:Integer);
Begin
  aValue:=TInteger.EnsureRange(aValue,0,MAX_INT);
  if aValue<>FTotal then
  Begin
    Ftotal:=aValue;

    if FPageSize>FTotal then
    FPageSize:=FTotal;

    if FPosition>FTotal-FPageSize then
    Begin
      if (FTotal-FPageSize)      FPosition:=0 else
      FPosition:=FTotal-FPageSize;
    end;
    ReCalculate;
    LayoutChildren;
  end;
end;

procedure TW3CustomScrollBar.setPageSize(aValue:Integer);
Begin
  aValue:=TInteger.EnsureRange(aValue,0,FTotal);
  if aValue<>FPageSize then
  Begin
    FPageSize:=aValue;
    if FTotal>0 then
    Begin
      ReCalculate;
      LayoutChildren;
    end;
  end;
end;

Procedure TW3CustomScrollBar.setPosition(aValue:Integer);
Begin
  aValue:=TInteger.EnsureRange(aValue,0,FTotal-FPageSize);
  if aValue<>FPosition then
  Begin
    FPosition:=aValue;
    if FTotal>0 then
    Begin
      ReCalculate;
      LayoutChildren;
    end;
  end;
end;

Procedure TW3CustomScrollBar.setPositionNoCalc(aValue:Integer);
Begin
  aValue:=TInteger.EnsureRange(aValue,0,FTotal-FPageSize);
  if aValue<>FPosition then
  FPosition:=aValue;
end;

function TW3CustomScrollBar.calcSizeOfHandle:Integer;
var
  mTemp:  Integer;
Begin
  mTemp:=TInteger.PercentOfValue(PageSize,Total);
  result:=round( mTemp * getArea / 100);
end;

function TW3CustomScrollBar.PositionToPixelOffset(aPosition:Integer):Integer;
var
  mTemp:  Integer;
Begin
  mTemp:=TInteger.PercentOfValue(aPosition,Total);
  result:=round( mTemp * getArea / 100);
end;

function TW3CustomScrollBar.PixelOffsetToPosition(aPxPos:Integer):Integer;
var
  mTemp: Integer;
Begin
  mTemp:=TInteger.PercentOfValue(aPxPos,getarea);
  result:=trunc( (mTemp * Total) / 100 );
end;

The vertical bar

Deriving from this skeleton class, we can now easily implement both horizontal and vertical scrollbars. Let’s start with the vertical. This one is a bit more complex since we need to dig into JavaScript events directly. I did this not to hijack the published events (like mousedown etc) making sure these can still be used.

Interface:

TW3VerticalScrollbar = Class(TW3CustomScrollBar)
private
  FDragSize:  Integer;
  FDragPos:   Integer;
  FMoving:    Boolean;
  FEntry:     Integer;
 
  procedure   jsmousedown(eventObj:JMouseEvent);
  procedure   jsmousemove(eventObj:JMouseEvent);
  procedure   jsmouseup(eventObj:JMouseEvent);
 
  Procedure   doMouseDown(button:TMouseButton;
              shiftState:TShiftState;x,y:Integer);
  procedure   doMouseUp(button:TMouseButton;
              shiftState:TShiftState;x,y:Integer);
  procedure   doMouseMove(shiftState:TShiftState;x,y:Integer);
protected
  procedure InitializeObject; override;
  procedure Resize;Override;
  function  getArea:Integer;override;
  Procedure Recalculate;override;
End;

Implementation:

procedure TW3VerticalScrollbar.InitializeObject;
Begin
  inherited;
  (* minbutton.color:=clGreen;
  maxButton.color:=clCyan;
  draghandle.Color:=clRed; *)
 
  (* Avoid using up the exposed event handlers *)
  handle.addEventListener('mousedown',@jsmousedown,false);
  handle.addEventListener('mousemove',@jsmousemove,false);
  handle.addEventListener('mouseup',@jsmouseup,false);
end;
 
procedure TW3VerticalScrollbar.jsmousedown(eventObj:JMouseEvent);
Begin
  eventObj.preventDefault();
  var sr := ScreenRect;
  var shiftState := TShiftState.Current;
  shiftState.MouseButtons := shiftState.MouseButtons or (1 shl eventObj.button);
  shiftState.MouseEvent := eventObj;
  doMouseDown(eventObj.button, shiftState,
           eventObj.clientX-sr.Left, eventObj.clientY-sr.Top);
end;
 
procedure TW3VerticalScrollbar.jsmousemove(eventObj:JMouseEvent);
Begin
  eventObj.preventDefault();
  var sr := ScreenRect;
  var shiftState := TShiftState.Current;
  shiftState.MouseEvent := eventObj;
  doMouseMove(shiftState, eventObj.clientX-sr.Left, eventObj.clientY-sr.Top);
end;
 
procedure TW3VerticalScrollbar.jsmouseup(eventObj:JMouseEvent);
Begin
  eventObj.preventDefault();
  var sr := ScreenRect;
  var shiftState := TShiftState.Current;
  shiftState.MouseButtons := shiftState.MouseButtons and not (1 shl eventObj.button);
  shiftState.MouseEvent := eventObj;
  doMouseUp(eventObj.button, shiftState,
         eventObj.clientX-sr.Left, eventObj.clientY-sr.Top);
end;
 
function TW3VerticalScrollbar.getArea:Integer;
Begin
  result:=Height;
 
  result:=MaxButton.top - minbutton.boundsRect.bottom;
  exit;
 
  if minButton.Visible then
  dec(result,MinButton.Height);
 
  if maxButton.Visible then
  dec(result,MaxButton.height);
end;
 
Procedure TW3VerticalScrollbar.doMouseDown(button:TMouseButton;
          shiftState:TShiftState;x,y:Integer);
Begin
  if button=mbLeft then
  begin
    if  dragHandle.BoundsRect.ContainsPos(x,y) then
    begin
      FEntry:=y - dragHandle.top;
      FMoving:=True;
    end;
  end;
end;
 
procedure TW3VerticalScrollbar.doMouseMove(shiftState:TShiftState;
          x,y:Integer);
var
  mNewPos:  Integer;
  dy: Integer;
Begin
  if FMoving then
  Begin
    (* take offset on draghandle into account *)
    dy:=y - FEntry;
 
    (* position draghandle *)
    draghandle.top:=TInteger.EnsureRange(dy,
      minButton.top + minButton.height,
      maxButton.top - FDragSize);
 
    (* Update position based on draghandle *)
    mNewPos:=PixelOffsetToPosition(draghandle.top-MinButton.BoundsRect.Bottom);
    setpositionNoCalc(mNewPos);
  end;
end;
 
procedure TW3VerticalScrollbar.doMouseUp(button:TMouseButton;
          shiftState:TShiftState;x,y:Integer);
Begin
  if FMoving then
  Begin
    FMoving:=False;
    setPosition(PixelOffsetToPosition
    (draghandle.top-MinButton.BoundsRect.Bottom));
  end;
end;
 
procedure TW3VerticalScrollbar.Resize;
var
  mTop: Integer;
Begin
  inherited;
  MinButton.SetBounds(0,0,width,width);
  MaxButton.setBounds(0,(height-width),width,width);
 
  Recalculate;
 
  mTop:=MinButton.top + minButton.Height + FDragPos;
  DragHandle.SetBounds(2,mTop,width-4,FDragSize + Border.getVSpace);
end;
 
Procedure TW3VerticalScrollbar.Recalculate;
Begin
  FDragSize:=calcSizeOfHandle;
  FDragPos:=PositionToPixelOffset(Position);
end;

The style:

.TW3ScrollbarLowerBtn, .TW3ScrollbarHigherBtn {
  border: 2px ridge rgba(0,0,0,0.3);
  background-color: #FFFFFF;
  overflow: hidden;
  border-radius: 25px;
  -webkit-border-radius: 25px;
     -moz-border-radius: 25px;
      -ms-border-radius: 25px;
       -o-border-radius: 25px;
}
 
.TW3ScrollbarHandle {
  border: 2px ridge rgba(0,0,0,0.3);
  background-color: #FFFFFF;
  overflow: hidden;
  border-radius: 25px;
  -webkit-border-radius: 25px;
     -moz-border-radius: 25px;
      -ms-border-radius: 25px;
       -o-border-radius: 25px;
}
 
.TW3VerticalScrollbar {
  border: 2px ridge rgba(0,0,0,0.3);
  background-color: #FFFFFF;
  overflow: hidden;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  border-radius: 25px;
  -webkit-border-radius: 25px;
     -moz-border-radius: 25px;
      -ms-border-radius: 25px;
       -o-border-radius: 25px;
}
« Creating a toggle switch
Creating a TScrollbox »

Pages

  • About
  • Feature Matrix
  • Forums
  • News
  • Release History
  • Download
  • Showcases
    • The Smart Contest 2013, Round 1 – Graphics
  • Store
  • Documentation
    • Creating your own controls
    • Debugging, exceptions and error handling
    • Differences between Delphi and Smart
    • Get the book
    • Getting started
      • Introduction
      • Local storage, session storage and global storage
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • pmSmart Box Model
      • Themes and styles
    • Layout manager
    • Networking
      • Loading files
      • TW3HttpRequest
      • TW3JSONP
    • Prerequisites
    • Real data, talking to sqLite
    • System requirements
    • Project types
      • Visual project
      • Game project
      • Console project

Archives

  • December 2019
  • December 2018
  • November 2018
  • July 2018
  • June 2018
  • February 2018
  • September 2017
  • April 2017
  • November 2016
  • October 2016
  • September 2016
  • April 2016
  • March 2016
  • January 2016
  • October 2015
  • September 2015
  • July 2015
  • April 2015
  • January 2015
  • December 2014
  • October 2014
  • September 2014
  • August 2014
  • July 2014
  • June 2014
  • March 2014
  • February 2014
  • January 2014
  • December 2013
  • November 2013
  • October 2013
  • August 2013
  • July 2013
  • June 2013
  • May 2013
  • April 2013
  • March 2013
  • February 2013
  • January 2013
  • December 2012
  • November 2012
  • August 2012
  • July 2012
  • June 2012
  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • November 2011
  • October 2011
  • September 2011

Categories

  • Announcements (25)
  • Developers log (119)
  • Documentation (26)
  • News (104)
  • News and articles (16)

WordPress

  • Register
  • Log in
  • WordPress

Subscribe

  • Entries (RSS)
  • Comments (RSS)
© Optimale Systemer AS