Jump to content

Do we need a TSplitter


Recommended Posts

  • Moderators

Do we need a TSplitter. Obviously I had a need for one, so for me the answer is yes.

I found a JSFiddle here (http://jsfiddle.net/kxr96dzg/1/) which does what I wanted. It is a standard front-end javascript/html/css fiddle where dom-layout, styling and positioning and logic is divided into 3 separate files.

I find it always a bit hard to have to look in 3 different places to understand some piece of code, but luckily in Smart this can be centralised. This post is about converting this fiddle in a standard Smart new-alpha component, without making any major changes.

Converting Html : The splitter consists of a background, a left-side panel, a right-side panel and a 'dragger' to drag the splitter around.

  Splitter := TW3Panel.Create(self);

  PanelLeft  := TW3Panel.Create(Splitter);
  PanelRight := TW3Panel.Create(Splitter);
  ReSizer    := TW3Panel.Create(PanelRight);

Converting CSS. Styling and positioning

    Splitter.SetBounds(30,30,400,250);

    PanelLeft.SetBounds (0, 0, Splitter.width, Splitter.Height);
    PanelLeft.handle.style.backgroundColor := 'whitesmoke';
    PanelRight.SetBounds(trunc(Splitter.width/2), 0, trunc(Splitter.width/2), Splitter.Height);

    ReSizer.handle.style.backgroundColor := '#ccc';
    ReSizer.handle.style.cursor := 'w-resize';
    ReSizer.SetBounds (0, 0, 3, Splitter.Height);

Converting JavaScript logic. This can be greatly simplified from the javascript in this fiddle :

    ReSizer.handle.onmousedown := procedure(e: variant)
    begin
      Splitter.handle.onmousemove := procedure(e: variant)
      begin
        PanelRight.left := e.clientX - 30;
        PanelRight.width := self.Width - PanelRight.Left;
        PanelLeft.handle.style.cursor := 'w-resize';
        PanelRight.handle.style.cursor := 'w-resize';
      end;
    end;
    Splitter.handle.onmouseup := procedure
    begin
      PanelLeft.handle.style.cursor := 'default';
      PanelRight.handle.style.cursor := 'default';
      Splitter.handle.onmousemove := procedure begin end;   //nullify mousemove
    end;
  end);

When the 'mousedown' event fires on the dragger, a 'mousemove' eventhandler takes care of changing the 'left' and 'width' parameters of the right-hand panel. This handler is cancelled on 'mouseup'. (sort of, would be more elegant to re-code this in calls to addEventListener and removeEventListener, but hey - this works well enough).

A bit different from how visual components are usually created. 

Anyway, this works for me and the total of about 40 lines in the jsfiddle has even been reduced to about 30 Smart statements.

Here is the code, which compiles fine in the latest alpha. 

unit Form1;

interface

uses 
  //System.Types,
  //System.Types.Convert,
  //System.Objects,
  //System.Time,
  SmartCL.System,
  //SmartCL.Time,
  //SmartCL.Graphics,
  //SmartCL.Components,
  //SmartCL.FileUtils,
  SmartCL.Forms,
  //SmartCL.Fonts,
  //SmartCL.Theme,
  //SmartCL.Borders,
  SmartCL.Application,
  SmartCL.Controls.Panel,
  SmartCL.Controls.Label;

type
  TForm1 = class(TW3Form)
  private
    {$I 'Form1:intf'}
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
    Splitter, PanelLeft, PanelRight, ReSizer: TW3Panel;
    Label: TW3Label;
  end;

implementation

{ TForm1 }

procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}

  Splitter := TW3Panel.Create(self);

  PanelLeft  := TW3Panel.Create(Splitter);
  PanelRight := TW3Panel.Create(Splitter);
  ReSizer    := TW3Panel.Create(PanelRight);
  Label      := TW3Label.Create(PanelLeft);

  Splitter.handle.ReadyExecute( procedure ()
  begin
    Splitter.SetBounds(30,30,400,250);

    PanelLeft.SetBounds (0, 0, Splitter.width, Splitter.Height);
    PanelLeft.handle.style.backgroundColor := 'whitesmoke';
    PanelRight.SetBounds(trunc(Splitter.width/2), 0, trunc(Splitter.width/2), Splitter.Height);

    ReSizer.handle.style.backgroundColor := '#ccc';
    ReSizer.handle.style.cursor := 'w-resize';
    ReSizer.SetBounds (0, 0, 3, Splitter.Height);

    Label.SetBounds(20,20,80,25);
    Label.Caption := 'Some text';
//
// event handling splitter movement
//
    ReSizer.handle.onmousedown := procedure(e: variant)
    begin
      Splitter.handle.onmousemove := procedure(e: variant)
      begin
        PanelRight.left := e.clientX - 30;
        PanelRight.width := self.Width - PanelRight.Left;
        PanelLeft.handle.style.cursor := 'w-resize';
        PanelRight.handle.style.cursor := 'w-resize';
      end;
    end;
    Splitter.handle.onmouseup := procedure
    begin
      PanelLeft.handle.style.cursor := 'default';
      PanelRight.handle.style.cursor := 'default';
      Splitter.handle.onmousemove := procedure begin end;   //nullify mousemove
    end;
  end);
end;
 
procedure TForm1.Resize;
begin
  inherited;
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.

 

(Note that optionally most of the standard Smart units are not needed here.)

.

 

 


 

 

 

Link to post
Share on other sites

interesting,  I would like to see it wrapped up into a component, so then you could do something as such  :)

Splitter := TW3Splitter.Create(self);
Splitter.Resizer.Width:= 10;
Splitter.Orientation:= soHorizontal;

I ran out of time to finish the OnMouseMove event, but Ill see if I can fit it in tomorrow

e.g.

unit UntSplitter;

interface

uses 
  System.Types,
  System.Types.Convert,
  SmartCL.System,
  SmartCl.Layout,
  SmartCL.Components,
  SmartCL.Controls.Panel;

type

 TW3SplitterOrientation = (soVertical, soHorizontal);

 TW3Splitter = Class (TW3Panel)
  private
   fStartX: Integer;
   fStartY: Integer;
   fOrientation: TW3SplitterOrientation;
   fCanResize: Boolean;
   fVertLayout: TLayout;
   fHorizLayout: TLayout;
   fPanelLeft: TW3Panel;
   fPanelRight: TW3Panel;
   fReSizer: TW3Panel;
   function getOrientation: TW3SplitterOrientation;
   procedure setOrientation(Value: TW3SplitterOrientation);

   procedure MoveResizerLeft(X: Integer);
   procedure MoveResizerRight(X: Integer);
   procedure MoveResizerUp(Y: Integer);
   procedure MoveResizerDown(Y: Integer);

  protected
    procedure HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    procedure HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: integer);
    procedure HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    procedure InitializeObject; override;
    procedure Resize; override;
  public
  published
    property Orientation: TW3SplitterOrientation  read getOrientation write setOrientation default soVertical;
    property LeftPanel: TW3Panel read fPanelLeft write fPanelLeft;
    property RightPanel: TW3Panel read fPanelRight write fPanelRight;
    property Resizer: TW3Panel read fReSizer write fResizer;
  end;


implementation

procedure TW3Splitter.MoveResizerLeft(X: Integer);
begin
end;

procedure TW3Splitter.MoveResizerRight(X: Integer);
begin
end;

procedure TW3Splitter.MoveResizerUp(Y: Integer);
begin
end;

procedure TW3Splitter.MoveResizerDown(Y: Integer);
begin
end;

function TW3Splitter.getOrientation: TW3SplitterOrientation;
begin
 result:= fOrientation;
end;

procedure TW3Splitter.setOrientation(Value: TW3SplitterOrientation);
begin
 if fOrientation <> Value then
 begin
  fOrientation:= Value;
  if fOrientation = soVertical then
  begin
   if Assigned(fVertLayout) then
   begin
    fReSizer.handle.style.cursor := 'ns-resize';
    fVertLayout.Resize(self);
   end;
  end
  else
   if Assigned(fHorizLayout) then
   begin
    fReSizer.handle.style.cursor := 'ew-resize';
    fHorizLayout.Resize(self);
   end;
 end;
end;

procedure TW3Splitter.HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
 if Button = mbLeft then
 begin
  fCanResize:= True;
  fStartX:= X;
  fStartY:= Y;
 end;
end;

procedure TW3Splitter.HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: integer);
begin
 if fCanResize then
 begin
  if fOrientation = soVertical then
  begin
   if X < fStartX then
    MoveResizerLeft(X)
   else
    MoveResizerRight(X);
  end
  else
  begin
   if Y < fStartY then
    MoveResizerUp(Y)
   else
    MoveResizerDown(Y);
  end;
 end;
end;

procedure TW3Splitter.HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
 fCanResize:= False;
end;

procedure TW3Splitter.Resize;
begin
  inherited;
  if  not (Handle.Valid) and (csReady in ComponentState) then
  exit;

  if fOrientation = soVertical then
  begin
   fReSizer.handle.style.cursor := 'ew-resize';
   fVertLayout:= Layout.Client(
                         [Layout.Left(Layout.Width((ClientWidth DIV 2)-(ReSizer.Width DIV 2)), fPanelLeft),
                         Layout.Right(Layout.Width((ClientWidth DIV 2)-(ReSizer.Width DIV 2)), fPanelRight),
                         Layout.Client(fResizer)]
                         );

   if Assigned(fVertLayout) then
   begin
    fVertLayout.Resize(self);
   end;
  end
  else
  begin
   fReSizer.handle.style.cursor := 'ns-resize';
   fHorizLayout:= Layout.Client(
                         [Layout.Top(Layout.Height((ClientHeight DIV 2)-(ReSizer.Width DIV 2)), fPanelLeft),
                         Layout.Bottom(Layout.Height((ClientHeight DIV 2)-(ReSizer.Width DIV 2)), fPanelRight),
                         Layout.Client(fResizer)]
                         );

   if Assigned(fHorizLayout) then
   begin
    fHorizLayout.Resize(self);
   end;
  end;
end;

procedure TW3Splitter.InitializeObject;
begin
  inherited;
  handle.addEventListener('devicemotion', @Resize, false);
  fOrientation:= soVertical;
  fPanelLeft  := TW3Panel.Create(self);
  fPanelLeft.handle.style.backgroundColor := 'whitesmoke';

  fPanelRight := TW3Panel.Create(self);
  fPanelRight.handle.style.backgroundColor := 'whitesmoke';

  fReSizer    := TW3Panel.Create(self);
  fReSizer.OnMouseDown:= HandleMouseDown;
  fReSizer.OnMouseMove:= HandleMouseMove;
  fReSizer.OnMouseUp:= HandleMouseUp;
  fReSizer.handle.style.backgroundColor := '#ccc';
  fReSizer.handle.style.cursor := 'ew-resize';  //default
end;

end.

 

Link to post
Share on other sites
  • Moderators

Isn't too hard to wrap this in a component, as you're doing above. :)

Code needs to cleaned up a bit too, positioning is a couple of pixels off here and there. Please feel free to make any changes

Btw : I did add a JSplitter component to the native/shoestring framework.

Might contain something useful : http://www.lynkfs.com/Experiments/shoestring/index.sproj    (JSplitter unit, usage see Form1)  and ....shoestring/www for kitchensink

 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...