Home > Delphi, JavaScript, Object Pascal, Smart Mobile Studio > Edge sense controller, Smart style

Edge sense controller, Smart style

December 27, 2015 Leave a comment Go to comments

Yesterday I posted a short low-down on how to use TW3Dataset in your Smart Mobile Studio applications; I also included the source code for a CSS powered, heavy duty DB grid I’m working on.

Today I’m going to extract on one of the concepts I use in that DB grid, isolate it as a separate class and explain what you can do with it.

Edge sense

Being able to sense when the mouse-pointer is near the edge of a control (any edge) is very useful, especially when dealing with presentation of columns and rows you want to make re-sizable. It can however be a bit tricky to master.

RaDwB

Below you will find the working source-code for a completely separate controller class which adds edge-sense functionality. I have written it as a controller and not a part of TW3Customcontrol (or inheriting from any other visual component) to make sure it can be used more generally. As it stands now it will happily attach itself to any TW3MovableControl based visual component, create its own event handlers and update the mouse-pointer automatically.

Why controller?

Well, imagine you are creating a DB grid header component. Naturally you want your users to be able to resize the columns as they see fit. Well, this can be achieved in two ways. The first is that you implement edge-sensing directly into the column components themselves. But that means a lot of extra work. You would have to derive your column from a baseclass, hook mouse events and well, basically implement exactly what you get for free below.

The second option, which I have used here, is to have a separate “sense” object which keeps track of everything without interfering with any events or behavior. This means a bit more code to achieve but once you have isolated it as a class, you can apply it to nearly all visual controls.

A few words about abstraction

When writing your own custom components, always derive your own class-names even though you dont alter behavior. This should always be respected. First of all because of styling (css styles wont collide, and you can now safely style every aspect of your control) but also because of clarity.

So if your grid-header is made up of labels (which makes sense, considering a header with columns display, well, text) dont just use TW3Label. Derive your own class from TW3label and use that, like this:

type

TDBHeaderItem = class(TW3Label)
end;

TW3HeaderControl = class(TW3CustomControl)
protected
  procedure InitializeObject;Override;
end;

procedure TW3HeaderControl.InitializeObject;
begin
  inherited;
  var first := TDBHeaderItem.Create(self);
  var second := TDBHeaderItem.Create(self);
  ..
  handle.ReadyExecute( procedure ()
    begin
      Resize;
    end);
end;

In the above code, if we had just created a series of TW3Label’s, there would be no way of styling the columns without ruining the style for *ALL* TW3Label elements in your entire application. By simply deriving your own class and using that, you can now safely a style in the stylesheet which affects just your header-item(s).

The edge sense controller

Here is the code. To use it, simply give it the visual component you want to have sense-control in the constructor, and that’s basically it. You must expand the class yourself to add size/move functionality but that should be easy (and self explanatory):


type

  TEdgeRegions    = (scLeft,scTop,scRight,scBottom,scNone);
  TEdgeSenseEdges = set of TEdgeRegions;

  TEdgeSenseController = class(TW3OwnedObject)
  const
    CN_LEFT   = 0;
    CN_TOP    = 1;
    CN_RIGHT  = 2;
    CN_BOTTOM = 3;
  private
    FEdgeSize:Integer;
    FEdges:   TEdgeSenseEdges;
    FRects:   Array[0..3] of TRect;
    FEdgeId:  Integer;
    FBound:   Boolean;

    procedure CBMouseMove(eventObj : JMouseEvent);
    procedure CBMouseEnter(eventObj : JMouseEvent);
    procedure CBMouseExit(eventObj : JMouseEvent);

    procedure SetEdgeSize(const Value:Integer);
    Procedure SetSenseEdges(const Value:TEdgeSenseEdges);
    function  GetActiveEdge:TEdgeRegions;

    procedure CheckCorners(const x,y:Integer);
    function  GetActiveEdgeOffset(x,y:Integer):TPoint;

  protected
    procedure MouseMove(shiftState:TShiftState;x,y:Integer);
    procedure MouseEnter(shiftState:TShiftState;x,y:Integer);
    procedure MouseExit(shiftState:TShiftState;x,y:Integer);
  public

    procedure BindToControl;
    procedure UnBindFromControl;
    procedure UpdateCursor;

    procedure   Update;

    Constructor Create(AOwner:TW3MovableControl);reintroduce;
    Destructor  Destroy;Override;

    property  EdgeSize:Integer read FEdgeSize write SetEdgeSize;
    Property  ActiveEdge:TEdgeRegions read getActiveEdge;
    Property  SenseEdges:TEdgeSenseEdges read FEdges write SetSenseEdges;
  end;

Constructor TEdgeSenseController.Create(AOwner:TW3MovableControl);
begin
  inherited Create(AOwner);
  FEdges:=[scLeft,scTop,scRight,scBottom];
  FEdgeSize:=10;
  BindToControl;
end;

Destructor TEdgeSenseController.Destroy;
begin
  UnBindFromControl;
  inherited;
end;

procedure TEdgeSenseController.Update;
Begin
  if FBound then
  begin
    var LOwner := TW3MovableControl(Owner);
    FRects[0]:=TRect.CreateSized(0,EdgeSize,EdgeSize,LOwner.ClientHeight-EdgeSize);
    FRects[1]:=TRect.CreateSized(0,0,LOwner.ClientWidth,EdgeSize);
    FRects[2]:=TRect.CreateSized(LOwner.ClientWidth-EdgeSize,EdgeSize,LOwner.Clientwidth,LOwner.ClientHeight-EdgeSize);
    FRects[3]:=TRect.CreateSized(0,LOwner.ClientHeight-EdgeSize,LOwner.ClientWidth,EdgeSize);
  end else
  begin
    FRects[0]:=TRect.NullRect;
    FRects[1]:=TRect.NullRect;
    FRects[2]:=TRect.NullRect;
    FRects[3]:=TRect.NullRect;
  end;
end;

procedure TEdgeSenseController.UpdateCursor;
var
  mCursor:  string;
  LOwner: TW3MovableControl;

  procedure doDefault;
  begin
    if mCursor<>'default' then
    w3_setStyle(LOwner.Handle,'cursor','default');
  end;

begin
  LOwner := TW3MovableControl(Owner);

  mCursor:=w3_getStyleAsStr(LOwner.Handle,'cursor').LowerCase;

  case FEdgeId of
    CN_LEFT:
      Begin
        if (scLeft in FEdges) then
        begin
          if mCursor<>'col-resize' then
          w3_setStyle(LOwner.Handle,'cursor','col-resize');
        end else
        doDefault;
      end;
    CN_RIGHT:
      Begin
        if (scRight in FEdges) then
        begin
          if mCursor<>'col-resize' then
          w3_setStyle(LOwner.Handle,'cursor','col-resize');
        end else
        doDefault;
      end;
    CN_TOP:
      begin
        if (scTop in FEdges) then
        begin
          if mCursor<>'row-resize' then
          w3_setStyle(LOwner.Handle,'cursor','row-resize');
        end else
        doDefault;
      end;
    CN_BOTTOM:
      begin
        if (scBottom in FEdges) then
        begin
          if mCursor<>'row-resize' then
          w3_setStyle(LOwner.Handle,'cursor','row-resize');
        end else
        doDefault;
      end;
    else
    doDefault;
  end;
end;

procedure TEdgeSenseController.CheckCorners(const x,y:Integer);
var
  mItem:TRect;
  mIndex: Integer;
begin
  FEdgeId:=-1;
  for mItem in FRects do
  begin
    if mItem.ContainsPos(x,y) then
    begin
      FEdgeId:=mIndex;
      break;
    end;
    inc(mIndex);
  end;
end;

function TEdgeSenseController.GetActiveEdgeOffset(x,y:Integer):TPoint;
begin
  if FEdgeId>=0 then
  result:=TPoint.Create
    ( x - FRects[FEdgeId].Left,
      y - FRects[FEdgeId].Top
    );
end;

function TEdgeSenseController.getActiveEdge:TEdgeRegions;
begin
  if (FEdgeId>=0) and (FEdgeId<=3) then
  result:=TEdgeRegions(FEdgeid) else
  result:=scNone;
end;

Procedure TEdgeSenseController.SetSenseEdges(const Value:TEdgeSenseEdges);
begin
  if (scLeft in Value) then Include(FEdges,scLeft) else Exclude(FEdges,scLeft);
  if (scTop in Value) then include(FEdges,scTop) else Exclude(FEdges,scTop);
  if (scRight in Value) then Include(FEdges,scRight) else
  Exclude(FEdges,scRight);
  if (scBottom in Value) then include(FEdges,scBottom) else
  Exclude(FEdges,scBottom);
end;

procedure TEdgeSenseController.SetEdgeSize(const Value:Integer);
begin
  FEdgeSize := TInteger.EnsureRange(Value,4,32);
end;

procedure TEdgeSenseController.BindToControl;
begin
  if not FBound then
  begin
    var LOwner := TW3MovableControl(Owner);
    LOwner.handle.addEventListener('mousemove',@CBMouseMove,true);
    LOwner.handle.addEventListener('mouseover',@CBMouseMove,true);
    LOwner.handle.addEventListener('mouseout',@CBMouseMove,true);
    FBound := True;
  end;
end;

procedure TEdgeSenseController.UnBindFromControl;
begin
  if FBound then
  begin
    var LOwner := TW3MovableControl(Owner);
    LOwner.handle.removeEventListener('mousemove',@CBMouseMove,true);
    LOwner.handle.removeEventListener('mouseover',@CBMouseMove,true);
    LOwner.handle.removeEventListener('mouseout',@CBMouseMove,true);
    FBound := false;
  end;
end;

procedure TEdgeSenseController.MouseMove(shiftState:TShiftState;x,y:Integer);
begin
  if FBound then
  begin
    Update;
    CheckCorners(x,y);
    UpdateCursor;
  end;
end;

procedure TEdgeSenseController.MouseEnter(shiftState:TShiftState;x,y:Integer);
begin
  if FBound then
  begin
    Update;
    CheckCorners(x,y);
    UpdateCursor;
  end;
end;

procedure TEdgeSenseController.MouseExit(shiftState:TShiftState;x,y:Integer);
begin
  if FBound then
  begin
    Update;
    CheckCorners(x,y);
    UpdateCursor;
  end;
end;

procedure TEdgeSenseController.CBMouseMove(eventObj : JMouseEvent);
begin
  var LOwner := TW3MovableControl(Owner);
  var sr := LOwner.ScreenRect;
  var shiftState := TShiftState.Current;
  shiftState.MouseEvent := eventObj;
  MouseMove(shiftState, eventObj.clientX - sr.Left, eventObj.clientY - sr.Top);
end;

procedure TEdgeSenseController.CBMouseEnter(eventObj : JMouseEvent);
begin
  var LOwner := TW3MovableControl(Owner);
  var sr := LOwner.ScreenRect;
  var shiftState := TShiftState.Current;
  shiftState.MouseEvent := eventObj;
  MouseEnter(shiftState, eventObj.clientX - sr.Left, eventObj.clientY - sr.Top);
end;

procedure TEdgeSenseController.CBMouseExit(eventObj : JMouseEvent);
begin
  var LOwner := TW3MovableControl(Owner);
  var sr := LOwner.ScreenRect;
  var shiftState := TShiftState.Current;
  shiftState.MouseEvent := eventObj;
  MouseExit(shiftState, eventObj.clientX - sr.Left, eventObj.clientY - sr.Top);
end;

  1. No comments yet.
  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 )

Connecting to %s

%d bloggers like this: